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

2 Answers2

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.

Kusalananda
  • 333,661
-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/[#?!@$ %^&*-]}" ]
nezabudka
  • 2,428
  • 6
  • 15
  • 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 '22 at 06: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. – Stéphane Chazelas May 25 '22 at 08: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. – Stéphane Chazelas May 25 '22 at 08: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 '22 at 09: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 '22 at 10:07