2

I have been working on this for a while, visiting dozens of sites and trying all kinds of combinations; however, I cannot get the script to run as intended. Even when they work in https://regex101.com/, I still cannot get them to work in bash.

I am trying to write a bash script which will validate that an input ("$password") is at least eight characters long and contains at least one number and at least one of these special characters: #?!@$ %^&*-

GNU bash, version 5.1.16(1)-release-(x86_64-pc-linux-gnu)

Any help would be greatly appreciated!

read -p "Please enter a password to test: " password
echo "You entered '$password'"
# I have tried all of the following (plus a few others) and cannot get it to work
#regex='^[a-zA-Z0-9#@$?]{8,}$'
#regex='^[a-zA-Z0-9@#$%&*+-=]{8,}$'
#regex='^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[@#$%&*+-=]).{8,}$'
#regex='^(?=.*?[a-zA-Z0-9])(?=.*?[#?!@$ %^&*-]).{8,}$'
#regex='^(?=.*[A-Za-z])(?=.*[0-9])(?=.*[#?!@$ %^&*-]).{8,}$'
if [[ $password =~ $regex ]]; then
        echo "This works"
else
        echo "Nope"
fi
3
  • 1
    Very similar: How to quantify POSIX extended regex? May 25 at 5:27
  • Thank you, @StéphaneChazelas. That is one post which I did not see prior to asking. That, and all of the answers/comments below, really helped me to understand better why nothing I tried worked. Thank you, All. May 25 at 12:58
  • re what Kusalananda mentioned at the bottom of their answer about regex101.com not knowing POSIX regexes, the (?...) constructs in particular are one common thing from Perl regexes that aren't present in POSIX regexes.
    – ilkkachu
    May 25 at 15:57

2 Answers 2

5

The extended regular expression syntax supported by bash lack the ability to construct single expressions that perform boolean AND tests between several subexpressions. Therefore, it would be easier for you to perform one test per condition.

You seem to have three conditions that your string needs to fulfil:

  1. At least eight characters.
  2. At least one digit (which is what I assume you mean by "number").
  3. At least one character from the set #?!@$ %^&*-.

This implies three tests:

if [ "${#password}" -ge 8 ] &&
   [[ $password == *[[:digit:]]* ]] &&
   [[ $password == *[#?!@$\ %^\&*-]* ]]
then
    echo 'good'
else
    echo 'not good'
fi

Some special characters have to be escaped in the last test. We can make it look prettier by using variables:

has_digit='*[[:digit:]]*'
has_special='*[#?!@$ %^&*-]*'  # or possibly '*[[:punct:]]*'

if [ "${#password}" -ge 8 ] &&
   [[ $password == $has_digit ]] &&
   [[ $password == $has_special ]]
then
    echo 'good'
else
    echo 'not good'
fi

Note that I'm not using regular expressions here but ordinary shell patterns. The set matched by [[:punct:]] is the slightly larger set of "punctuation characters" (which notably does not contain the space character, but you could use [[:punct:] ] or [[:punct:][:blank:]] or [[:punct:][:space:]]):

!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~

If you really need to use only regular expressions, then do something like

has_8='.{8}'
has_digit='[[:digit:]]'
has_special='[#?!@$ %^&*-]'  # or possibly '[[:punct:]]'

if [[ $password =~ $has_8 ]] &&
   [[ $password =~ $has_digit ]] &&
   [[ $password =~ $has_special ]]
then
    echo 'good'
else
    echo 'not good'
fi

Note the changed patterns.


A general warning about the regex101.com site is that it does not claim to support POSIX regular expressions specifically, which most standard Unix text processing tools use, only various extended variants of these.

4
  • Aren't there some double quotes missing around the variables?
    – Philippos
    May 25 at 6:45
  • @Philippos The $password expansion does not need quotes in [[ ... ]]. The variables holding patterns should not be quoted as they would be taken as literal strings, so you would get string matches instead of pattern matches.
    – Kusalananda
    May 25 at 6:47
  • Interesting. Thank you.
    – Philippos
    May 25 at 6:54
  • 1
    @Philippos Extra reading about this: When is double-quoting necessary?
    – Kusalananda
    May 25 at 6:56
-1

As an option:

if [ ! "${password/????????*}" -a\ 
        "$password" != "${password/[0-9]}" -a\ 
        "$password" != "${password/[#?!@$ %^&*-]}" ]
then
        echo "This works"
else
        echo "Nope"
fi

But as I remember, validation provides for the presence of letters in both lower and upper case, which means you need to change:

if [ ! "${password/????????*}" -a\ 
        "$password" != "${password/[a-z]}" -a\ 
        "$password" != "${password/[A-Z]}" -a\ 
        "$password" != "${password/[0-9]}" -a\ 
        "$password" != "${password/[#?!@$ %^&*-]}" ]
5
  • 2
    There's actually nothing in the requirements saying the string needs to contain letters from the set [A-Za-z] (yes, apart from in the faulty regular expressions).
    – Kusalananda
    May 25 at 6:55
  • 1
    -a's [ operator should not be used. It's deprecated (by POSIX at least) and makes for unreliable expressions. Try with password='(' in bash for instance. May 25 at 8:52
  • 1
    Character ranges such as a-z, A-Z, 0-9 should be avoided for input validation, especially in bash as in many locales what they match is rather random. May 25 at 8:53
  • 1
    I suppose doing a replacement on the string and comparing against the original works, but isn't that a bit convoluted? Why not just do the pattern match? (With [[ == ]], or [[ =~ ]], or case) Explicitly going from "${#password}" -gt 7 to ! "${password/????????*}" seems even more weird.
    – ilkkachu
    May 25 at 9:47
  • @ilkkachu. The very first option "${password/???????}" did not work correctly, so I urgently replaced it. But since a similar solution has already been proposed, after several tests I returned to the corrected original version. As an alternative. Thanks.
    – nezabudka
    May 25 at 10:07

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.