0

I'm trying to verify if a subdomain entered by a user is valid, but whatever I pass in, it's never valid. I know the regex is ok, so the problem is my "if" logic, however I'm new to shell/bash

#!/bin/bash
#

echo Enter the subdomain\'s name to configure.
read SUBDOMAIN

if [[ ! $SUBDOMAIN =~ [A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])? ]]; then
    echo "$SUBDOMAIN is not a valid domain"
fi

Examples:
Would be accepted (regular subdomain names): test
Would not be accepted (invalid subdomain name): -
Would not be accepted (invalid subdomain name): (Empty)
Would not be accepted (invalid subdomain name): #$??&@#&?$##$

I would prefer using shell, but the parentheses in the regex make the script throw an error.

I'm not sure if it can be done with grep, but I never understood how to use grep and it always confused me.

2

If you're trying to match "alphanumeric" followed by "alphanumeric or dash", ensuring there's not a dash at the end, such that there is a total of 1..62 characters, this RE will work for you

^[[:alnum:]](([[:alnum:]]|-){0,61}[[:alnum:]])?$

This binds to the beginning and end of the string, so the RE must match the string in its entirety.

  • Start of line ^
  • A single alphanumeric, any case [[:alnum:]]
  • An optional block (bracketed (...) and terminated with ?)
    • [[:alnum:]] or a dash -, repeated 0..60 times
    • [[:alnum:]]
  • End of line $

As has been recommended in the comments under this answer, I should point out that the [[:alnum:]] range is affected by the current locale. If you want to ensure that it matches only "ASCII" A-Z, a-z and 0-9 you need to ensure it's running with LANG=C. Otherwise you may find that additional characters are accepted, such as á é ø ß and others.

  • Thanks friend! Your regex looks much better! I just have to change the regex a bit so subdomains can't end with a dash as well and It's all good :) – NaturalBornCamper Apr 30 '18 at 16:21
  • @NaturalBornCamper that's actually a little more complicated than it sounds – roaima Apr 30 '18 at 16:23
  • Nope, what you gave me got me started, I just changed your answer a bit and it's working: if [[ ! $SUBDOMAIN =~ ^[[:alnum:]]([[:alnum:]]|-){0,61}[[:alnum:]]$ ]]; – NaturalBornCamper Apr 30 '18 at 16:26
  • @NaturalBornCamper that will fail with a single character entry. It will also accept a 63 character string. Please see the amended answer for my suggestion. – roaima Apr 30 '18 at 16:27
  • 1
    @roaima Since you are writing an answer about what you do know it follows that it is reasonable that you should make a note about the a-z ranges matching many UNICODE characters and not leave that hidden. – Isaac May 1 '18 at 7:40
0

Abstract:

Range: You need to change LANG in a subshell: (LANG=C; echo "${a//[^a-zA-Z]}")

List: Explicit characters: r1=$(printf '%s' {a..z} {A..Z} {0-9})
Build regex in parts: r2="[$r1]"; r3="[$r1-]"; reg="^$r2($r3{0,61}$r2)?$"

Use the final regex as: if [[ ! $SUBDOMAIN =~ $reg ]]; then


Ranges

Matching the range a-z or A-Z hides several surprises.

A simple regex (or glob) like [a-z] will match many other characters:

$ a='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
$ echo "${a//[^a-z]}"
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY

Note that only the Z was removed. The locale can change that:

$ LANG=C
$ echo "${a//[^a-z]}"
abcdefghijklmnopqrstuvwxyz

It is even worse if the [[:alnum:]] range is used:

$ LANG=en_US.utf8
$ a='aáàäåbcdeéèëfAÁÀÄBßCRS§TUVWXYZ'
$ echo "${a//[^a-z]}      ${a//[[:alnum:]]}"
aáàäåbcdeéèëfAÁÀÄBßCRSTUVWXY      aáàäåbcdeéèëfAÁÀÄBßCRSTUVWXYZ

The first range removed §Z, alnum only removed the §.
With LANG=C it gets better:

$ LANG=C
$ a='aáàäåbcdeéèëfAÁÀÄBßCRS§TUVWXYZ'
$ echo "${a//[^a-z]}      ${a//[^[:alnum:]]}"
abcdef      áàäåéèëÁÀÄߧ

A range to include the Z might work better.

$ LANG=en_US.utf8
$ a='ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz'
$ echo "${a//[^a-Z]}"
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

But it will still include many other UNICODE characters.

$ LANG=en_US.utf8
$ a='AÁÀÄBßCRSTUVWXYZ[]^_`aáàäåbcdeéèëf-ěžíňř'
$ echo "${a//[^a-Z]}"
AÁÀÄBßCRSTUVWXYZaáàäåbcdeéèëfěíňř

And Using the C language setting just change the problem to:

$ LANG=C
$ a='AÁÀÄBßCRSTUVWXYZ[]^_`aáàäåbcdeéèëfxyz-ěžíňř'
$ echo "${a//[^A-z]}     ${a//[^[:alnum:]]}"
ABCRSTUVWXYZ[]^_`abcdefxyz     ABCRSTUVWXYZabcdefxyz

Subshell

To use the LANG=C option, it is usual to need a sub-shell to avoid changing the value of LANG in the running shell:

$ (LANG=C; echo "${a//[^a-zA-Z]}")
ABCRSTUVWXYZabcdefxyz

List

An explicit list of characters will avoid both the issue of collating and the need to change the LANG variable (brace expansions use only the default C locale):

$ r1=$(printf '%s' {a..z} {A..Z})
$ echo "$r1"
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
$ a='AÁÀÄBßCRSTUVWXYZ[]^_`aáàäåbcdeéèëfxyz-ěžíňř'
$ echo "${a//[^"$r1"]}"
ABCRSTUVWXYZabcdefxyz

For A-Z, a-z and 0-9, use:

r1=$(printf '%s' {A..Z} {a..z} {0..9})

Then build the two kind of regex values that are needed:

r2="[$r1]"
r3="[$r1-]"

And the full regex will be:

reg="^$r2($r3{0,61}$r2)?$"

use it as (it is better that the regex is inside a variable) :

if [[ ! $SUBDOMAIN =~ $reg ]]; then

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.