shell script in bash using regex in while loop - regex

Hi i am try to validate user inputs to be not empty and is a number or with decimal
re='^[0-9]+$'
while [ "$num" == "" ] && [[ "$num" ~= $re ]]
do
echo "Please enter the price : "
read num
done
I was able to run smooth with just the 1st condition. When i add 2nd condition my program couldn't run.
----EDIT----------
Ok i try changing and the program run. But when i enter a number it still prompting for input.
re='^[0-9]+$'
while [ "$num" == "" ] && [ "$num" != $re ]
do
echo "Please enter the price : "
read num
done

regualar expression can be used with the operator =~ not ~= like you used it.
An additional binary operator, =~, is available, with the same
prece dence as == and !=. When it is used, the string to the right of
the operator is considered an extended regular expression and matched
accordingly (as in regex(3)). The return value is 0 if the string
matches the pattern, and 1 otherwise. If the regular expression is
syntactically incorrect, the conditional expression's return value is
2. If the shell option nocasematch is enabled, the match is performed
without regard to the case of alphabetic characters. Any part of the
pattern may be quoted to force the quoted portion to be matched as a
string. Bracket expressions in regular expressions must be treated
carefully, since normal quoting characters lose their meanings between
brackets. If the pattern is stored in a shell variable, quoting the
variable expansion forces the entire pattern to be matched as a string.
Substrings matched by parenthesized subexpressions within the regular
expression are saved in the array variable BASH_REMATCH. The element
of BASH_REMATCH with index 0 is the portion of the string matching the
entire regular expression. The element of BASH_REMATCH with index n is
the portion of the string matching the nth parenthesized subexpression.
consider theese examples (0 true/match, 1 false/no match)
re=^[0-9]+; [[ "1" =~ ${re} ]]; echo $? # 0
re=^[0-9]+; [[ "a" =~ ${re} ]]; echo $? # 1
re=^[0-9]+; [[ "a1" =~ ${re} ]]; echo $? # 1
re=^[0-9]+; [[ "1a" =~ ${re} ]]; echo $? # 0 because it starts with a number
use this one to check for a number
re=^[0-9]+$; [[ "1a" =~ ${re} ]]; echo $? # 1 because checked up to the end
re=^[0-9]+$; [[ "11" =~ ${re} ]]; echo $? # 0 because all nums
UPDATE: If you just want to check if the user inputs a number combine the lesson learned above with your needs. i think your conditions do not fit. perhaps this snippet solves your issue completely.
#!/bin/bash
re=^[0-9]+$
while ! [[ "${num}" =~ ${re} ]]; do
echo "enter num:"
read num
done
This snippet just requests input if ${num} is NOT (!) a number. During the first run ${num} is not set so it will not fit at least one number, ${num} then evaluates to an empty string. Afterwards it just contains the input entered.

Your error is simple; the variable can't be both empty and a number at the same time. Maybe you mean || "or" instead of && "and".
You can do this with glob patterns as well.
while true; do
read -r -p "Enter a price: " num
case $num in
"" | *[!.0-9]* | *.*.*) echo invalid ;;
*) break;;
esac

First off, there is the classic logic trap demonstrated in the OP's question:
while [ "$num" == "" ] && [ "$num" != $re ]
The issue here is the && which pretty much means the moment the left expression is false, the entire expression is false. i.e. the moment somebody types a non empty response, it breaks the loop and the regular expression test is never used. To fix the logic problem, one should consider changing && to ||, i.e.
while [ "$num" == "" ] || [ "$num" != $re ]
The second issue, is we are testing for negative matches to regular expression, pattern. So, this is done in two parts, one we need to use [[ "$num" =~ $re ]] for regular expression testing. Then, we need to look for negative matches, i.e. append a ! which yields:
while [ "$num" == "" ] || ! [[ "$num" =~ $re ]
Having got this far, many people observed that there is actually no need to test for the empty string. That edge condition is already covered by the regular expression itself, so, we optimize out the redundant test. The answer now reduces to:
while ! [[ "$num" =~ $re ]
In addition to the above observation, here are my notes about regular expression ( some of the observation has been collated from other answers ):
regular expressions can be tested with the [[ "$str" =~ regex ]] syntax
regular expressions match with $? == 0 ( 0 == no error )
regular expressions do not match with $? == 1 ( 1 == error )
regular expressions do not seem to work when quoted. recommend using [0-9] not "[0-9]"
To implement a number validation, the following pattern seems to work:
str=""
while ! [[ "${str?}" =~ ^[0-9]+$ ]]
do
read -p "enter a number: " str
done
You can mix regular expression filters with regular arithmetic filters for some really nice validation results:
str=""
while ! [[ "${str?}" =~ ^[0-9]+$ ]] \
|| (( str < 1 || str > 15 ))
do
read -p "enter a number between 1 and 15: " str
done
N.B. I used the ${str?} syntax ( instead of $str ) for variable expansion as it demonstrates good practice for catching typos.

Related

Bash Regex comparison not working

keyFileName=$1;
for fileExt in "${validTypes[#]}"
do
echo $fileExt;
if [[ $keyFileName == *.$fileExt ]]; then
keyStatus="true";
fi
done;
I am trying to check the file extension of a file passed in against an array of multiple file extensions. However it doesn't seem to be working properly. Any help?
validTypes=(".txt" ".mp3")
keyFileName="$1"
for fileExt in "${validTypes[#]}"
do
echo $fileExt;
if [[ $keyFileName =~ ^.*$fileExt$ ]]; then
keyStatus="true";
echo "Yes"
fi
done;
Effectively, you could change your if statement to either:
if [[ $keyFileName == ?*$fileExt ]] # Glob pattern case, ? denotes single char
or:
if [[ $keyFileName =~ .*$fileExt ]] # Regex case, . denotes single char
Looping over the array to do a regex match on each element seems rather inefficient. You're using regex; it's easy to combine the expressions and avoid looping at all.
Mangling the array into a valid regex is not entirely trivial, though. Here's my attempt:
validTypes=('\.txt' '\.mp3')
fileExtRe=$(printf '|%s' "${validTypes[#]}"
# Trim off the first alternation, add parens and anchor
fileExtRe="(${fileExtRe#?})$"
if [[ $keyFileName =~ $fileExtRe ]]; then
:
Notice how the elements in validTypes are regular expressions now, with the dot escaped to only match a literal dot.

Regex validation for grouping in Bash Scripting

I have tried creating a regex validation for Bash and have been doing this. It's working only for the first digit, the second one no. Can you help me out here?
while [[ $usrInput =~ [^[1-9]|[0-2]{1}$] ]]
do
echo "This is not a valid option. Please type an integer between 1 and 12"
read usrInput
done
You can't nest ranges. You want something like
while ! [[ $usrInput =~ ^[0-9]|11|12$ ]]; do
although in general it would be simpler to compare a digit string numerically:
min=1
max=12
until [[ $usrInput =~ ^[0-9]+$ ]] &&
(( usrInput >= min && usrInput <= max )); do
I believe you (or bash) are not grouping the expressions correctly. Either way, this'll work:
while read usrInput
do
if [[ "$usrInput" =~ ^([1-9]|1[0-2])$ ]]
then
break
else
echo "Number not between 1 and 12"
fi
done

shell script - assign regex to a variable based on result of an if statement

I have an if statement (which tests if a variable (test) has a certain value) which then assigns a value to a new variable (result) based on the result of the if statement. One of the values has got to be a regex, which will match the next item to be a letter (a-z, A-Z).
if [ "$test" -eq "0" ] // if test = 0
then
result="^[a-zA-Z\-]"
fi
What would the regex be and how would I assign it (not sure if I'm assigning it right)?
Thanks :)
EDIT: it would be used in a sentence like structure, e.g The next character needs to be a letter: and result would make sure the next character is a letter.
Bash doesn't have any way to store a regular expression other than as a string, so yes, it's just regular string assignment, like so:
if next_input_must_start_with_a_letter; then
regex='^[[:alpha:]]'
else
regex='' # always match unconditionally
fi
read -r input
if ! [[ $input =~ $regex ]]; then
echo "Input did not match regex!" >&2
fi

Regular expression in Bash filter

i have this string
<div style='text-align:center;padding-top:6px;'>Displaying Result 1 - 10 of 10 Matching Services</div>
I need the number "10" after "of"
My Regex is now
if [[ "$WARNING" =~ "of.([0-9]*)" ]]
then
echo "OK: $WARNING"
else
echo "NOK: $WARNING"
fi
can anyone help me please?
You don't need to quote the rhs of =~.
You can use the BASH_REMATCH variable to get the desired value.
Try:
if [[ "$WARNING" =~ of.([0-9]*) ]]
then
echo "OK: $WARNING"
else
echo "NOK: $WARNING"
fi
echo "${BASH_REMATCH[1]}"
From the manual:
BASH_REMATCH
An array variable whose members are assigned by the =~ binary operator to the [[ conditional command (see Conditional Constructs).
The element with index 0 is the portion of the string matching the
entire regular expression. The element with index n is the portion of
the string matching the nth parenthesized subexpression. This variable
is read-only.
You don't need regular expressions. Just use bash's built-in parameter expansions:
$ x="<div style='text-align:center;padding-top:6px;'>Displaying Result 1 - 10 of 10 Matching Services</div>"
$ x="${x##*of }"
$ echo "${x%% *}"
10
this is another just for fun awk example, you can modify it to supply the WARNING
[[bash_prompt$]]$ cat log
<div style='text-align:center;padding-top:6px;'>Displaying Result 1 - 10 of 10 Matching Services</div>
[[bash_prompt$]]$ awk '/of [0-9]*/{l=gensub(/^.*of ([0-9]*).*$/,"\\1",1); if(l > 10) print "greater"; else print "smaller"}' log
smaller

How should I get bash 3.2 to find a pattern between wildcards

Trying to compare input to a file containing alert words,
read MYINPUT
alertWords=( `cat "AlertWordList" `)
for X in "${alertWords[#]}"
do
# the wildcards in my expression do not work
if [[ $MYINPUT =~ *$X* ]]
then
echo "#1 matched"
else
echo "#1 nope"
fi
done
The =~ operator deals with regular expressions, and so to do a wildcard match like you wanted, the syntax would look like:
if [[ $MYINPUT =~ .*$X.* ]]
However, since this is regex, that's not needed, as it's implied that it could be anywhere in the string (unless it's anchored using ^ and/or $, so this should suffice:
if [[ $MYINPUT =~ $X ]]
Be mindful that if your "words" happen to contain regex metacharacters, then this might do strange things.
I'd avoid =~ here because as FatalError points out, it will interpret $X as a regular expression and this can lead to surprising bugs (especially since it's an extended regular expression, so it has more special characters than standard grep syntax).
Instead, you can just use == because bash treats the RHS of == as a globbing pattern:
read MYINPUT
alertWords=($(<"AlertWordList"))
for X in "${alertWords[#]}"
do
# the wildcards in my expression do work :-)
if [[ $MYINPUT == *"$X"* ]]
then
echo "#1 matched"
else
echo "#1 nope"
fi
done
I've also removed a use of cat in your alertWords assignment, as it keeps the file reading inside the shell instead of spawning another process to do it.
If you want to use patterns, not regexes for matching, you can use case:
read MYINPUT
alertWords=( `cat "AlertWordList" `)
for X in "${alertWords[#]}"
do
# the wildcards in my expression do not work
case "$MYINPUT" in
*$X* ) echo "#1 matched" ;;
* ) echo "#1 nope" ;;
esac
done