bash conditional statement OR regex - regex

I have a test file that logs the number of failures on the last line. I determine whether the test passed by checking for this line. Due to a bug a passed test will always have one failure. A passed line looks like the following:
FAILED : - Failures = 1
My code to date which works is:
if [[ $(tail -n 1 temp.txt) != "FAILED : - Failures = 1" ]]; then
echo "FAILED"
fi
However with a different software branch the word 'FAILED' has been replaced with 'WARNING' e.g.
WARNING : - Failures = 1
I'm trying to write a regex to OR the words 'WARNING' and 'FAILED' but so far it's not working.

You can use extglob:
if [[ $(tail -n 1 temp.txt) != #(WARNING|FAILED)" : - Failures = 1" ]]
then
echo "FAILED"
fi
#(pattern1|pattern2|..) is a bash glob that matches either of the given patterns.
Depending on your input, you can use further pattern matching to make your matching more resilient. For example, strings containing the word "WARNING" or "FAILED" followed by the word "Failures" can be matched with
[[ $(tail -n 1 temp.txt) != *#(WARNING|FAILED)*"Failures"* ]]
Fun fact: extglobs are regular expressions, they just don't use the canonical syntax that other UNIX tools do.

Related

FreeBSD Bash - Unable to code a condition with regex

I am trying to code a script for pfSense who is based on FreeBSD. The only part left who is giving me trouble is a condition with a regex. The simplified code is this :
RESPONSE='{"port":98989}'
REG='{"port":([0-9]*)}'
if [[ $RESPONSE =~ $REG ]]; then
PORT=${BASH_REMATCH[1]}
fi
With the trace mode enabled, the error returned is the following :
+ RESPONSE='{"port":98989}'
+ REG='{"port":([0-9]*)}'
+ '[[' '{"port":98989}' '=~' '{"port":([0-9]*)}' ]]
./pia-port-v2: [[: not found
I don't understand why the [[ is between single quote in the trace and it is probably why the "not found" error occurs.
Update
It is probably because pfSense's FreeBSD does not support bash and these instructions are bash only. I found that after writing this question and trying to find an answer.
Anybody have an alternative for bourne shell? The goal is to return the port number if the expression match.
I am new to script coding in unix like OS.
In the meantime, I look at grep, but it seems to apply the regex to file input only.
You should be able to use the expr utility to do this, but note that it use Posix basic regexps, which means that you need to backslash your parentheses to make them into captures:
response='{"port":98989}'
reg='{"port":\([0-9]*\)}'
port=$(expr "$response" : "$reg")
expr returns failure if the regex doesn't match, so you could use a shell conditional to test:
port=$(expr "$response" : "$reg") || { echo Failed; }
or
if ! port=$(expr "$response" : "$reg"); then
# Do something on failure
fi
With /bin/sh:
#!/bin/sh
response='{"port":98989}'
case $response in
'{"port":'[0-9]*'}')
port=${response#*:} # {"port":98989} --> 98989}
port=${port%'}'} # 98989} --> 98989
esac
printf 'response %s yields port %s\n' "$response" "$port"
Note that a case statement does not use regular expression but shell filename globbing patterns. Therefore, the pattern will only match a single digit and trigger for bogus strings like {"port":0xxx}.
If the response string is a JSON document:
$ response='{"port":98989}'
$ printf '%s\n' "$response" | jq .port
98989
There is trouble with ' and " when using [[ regexps (sometimes; not always) so I would try this instead (which works fine for me):
#!/bin/bash
REG=\{\"port\"\:\([0-9]\*\)\} # This line is altered
RESPONSE='{"port":98989}'
if [[ $RESPONSE =~ $REG ]]; then
echo funkar
fi

SVN pre-commit hook logic

I'm adding a logic in my svn pre-commit hook to check if there is QA(in upper case starting with space) in commit message then commit should fail. But its not working. Kindly assist me how to write it properly.
REPOS="$1"
TXN="$2"
# Make sure that the log message contains some text.
SVNLOOK=/usr/bin/svnlook
LOGMSG=$($SVNLOOK log -t "$TXN" "$REPOS")
# check if any comment has supplied by the commiter
if [ -z "$LOGMSG" ]; then
echo "Your commit was blocked because it have no comments." 1>&2
exit 1
fi
#check minimum size of text
if [ ${#LOGMSG} -lt 15 ]; then
echo "Your Commit was blocked because the comments does not meet minimum length requirements (15 letters)." 1>&2
exit 1
fi
# get TaskID by regex
TaskID=$(expr "$LOGMSG" : '\([#][0-9]\{1,9\}[:][" "]\)[A-Za-z0-9]*')
# Check if task id was found.
if [ -z "$TaskID" ]; then
echo "" 1>&2
echo "No Task id found in log message \"$LOGMSG\"" 1>&2
echo "" 1>&2
echo "The TaskID must be the first item on the first line of the log message." 1>&2
echo "" 1>&2
echo "Proper TaskID format--> #123- 'Your commit message' " 1>&2
exit 1
fi
#Check that QA should not be present in log message.
QA=$(expr "$LOGMSG" : '\(*[" "][QA][" "]\)')
if [ "$QA" == "QA" ]; then
echo "" 1>&2
echo "Your log message \"$LOGMSG\" must not contain QA in upper case." 1>&2
echo "" 1>&2
exit 1
fi
The regex is incorrect:
\( starts a capturing group in expr, but you don't need a capturing group for your task
When * follows a \( in a pattern, it tries to match a literal *
[QA] matches a single character, which can be Q or A
The pattern of expr must match from the start of the string
As it is, the regex doesn't correspond to your requirement.
Even if the above points are fixed, a pattern QA, "QA" with spaces around it, will not match commit messages like this:
"Fix the build of QA"
"Broken in QA, temporarily"
... and so on...
That is, instead of "QA" with spaces around, you probably want to match QA with word boundaries around.
This is easy to do using grep -w QA.
As you clarified in a comment, you really want a space before the "Q".
In that case the -w flag of grep is not suitable,
because that requires a word boundary at both sides of patterns.
There is another way to match word boundaries,
using \< for word start and \> for word end.
So to have a space in front of "Q",
and a word boundary after "A", you can write QA\>, like this:
if grep -q ' QA\>' <<< "$LOGMSG"; then
echo
echo "Your log message \"$LOGMSG\" must not contain QA in upper case."
echo
exit 1
fi 1>&2
Notice some other improvements:
Instead of redirecting to stderr every single echo, you can redirect the entire if statement
Instead of echo "" you can write simply echo
Instead of storing the result of a command in a temporary variable, you can write conditionals on the exit code of commands
This could be an error with your regex expression checking for " QA ".
I find using this site pretty useful for testing out regex expressions - RegExr.
I put your (*[" "][QA][" "]) expression into the site and when I looked at the details of it (a tab link towards the bottom of the page), it would break down exactly what you regular expression would match with. From this, it was saying that it was looking for the following:
0 or more (
Either a " or a space
Either Q or A (not both)
Either a " or a space
Ending with a )
I put the following expression into it - ( (QA) ) and it was able to find the match in a sample svn message (TEST-117 QA testing message).

Regular expressions don't work as expected in bash if-else block's condition

My pattern defined to match in if-else block is :
pat="17[0-1][0-9][0-9][0-9].AUG"
nln=""
In my script, I'm taking user input which needs to be matched against the pattern, which if doesn't match, appropriate error messages are to be shown. Pretty simple, but giving me a hard time though. My code block from the script is this:
echo "How many days' AUDIT Logs need to be searched?"
read days
echo "Enter file name(s)[For multiple files, one file per line]: "
for(( c = 0 ; c < $days ; c++))
do
read elements
if [[ $elements =~ $pat ]];
then
array[$c]="$elements"
elif [[ $elements =~ $nln ]];
then
echo "No file entered.Run script again. Exiting"
exit;
else
echo "Invalid filename entered: $elements.Run script again. Exiting"
exit;
fi
done
The format I want from the user for filenames to be entered is this:
170402.AUG
So basically yymmdd.AUG (where y-year,m-month,d-day), with trailing or leading spaces is fine. Anything other than that should throw "Invalid filename entered: $elements.Run script again. Exiting" message. Also I want to check if if it is a blank line with a "Enter" hit, it should give an error saying "No file entered.Run script again. Exiting"
However my code, even if I enter something like "xxx" as filename, which should be throwing "Invalid filename entered: $elements.Run script again. Exiting", is actually checking true against a blank line, and throwing "No file entered.Run script again. Exiting"
Need some help with handling the regular expressions' check with user input, as otherwise rest of my script works just fine.
I think as discussed in the comments you are confusing with the glob match and a regEx match, what you have defined as pat is a glob match which needs to be equated with the == operator as,
pat="17[0-1][0-9][0-9][0-9].AUG"
string="170402.AUG"
[[ $string == $pat ]] && printf "Match success\n"
The equivalent ~ match would be to something as
pat="17[[:digit:]]{4}\.AUG"
[[ $string =~ $pat ]] && printf "Match success\n"
As you can see the . in the regex syntax has been escaped to deprive of its special meaning ( to match any character) but just to use as a literal dot. The POSIX character class [[:digit:]] with a character count {4} allows you to match 4 digits followed by .AUG
And for the string empty check do as suggested by the comments from Cyrus, or by Benjamin.W
[[ $elements == "" ]]
(or)
[[ -z $elements ]]
I would not bug the user with how many days (who want count 15 days or like)? Also, why only one file per line? You should help the users, not bug them like microsoft...
For the start:
show_help() { cat <<'EOF'
bla bla....
EOF
}
show_files() { echo "${#files[#]} valid files entered: ${files[#]}"; }
while read -r -p 'files? (h-help)> ' line
do
case "$line" in
q) echo "quitting..." ; exit 0 ;;
h) show_help ; continue;;
'') (( ${#files} )) && show_files; continue ;;
l) show_files ; continue ;;
p) (( ${#files} )) && break || { echo "No files enterd.. quitting" ; exit 1; } ;; # go to processing
esac
# select (grep) the valid patterns from the entered line
# and append them into the array
# using the -P (if your grep know it) you can construct very complex regexes
files+=( $(grep -oP '17\d{4}.\w{3}' <<< "$line") )
done
echo "processing files ${files[#]}"
Using such logic you can build really powerful and user-friendly app. Also, you can use -e for the read enable the readline functions (cursor keys and like)...
But :) Consider just create a simple script, which accepts arguments. Without any dialogs and such. example:
myscript -h
same as above, or some longer help text
myscript 170402.AUG 170403.AUG 170404.AUG 170405.AUG
will do whatever it should do with the files. Main benefit, you could use globbing in the filenames, like
myscript 1704*
and so on...
And if you really want the dialog, it could show it when someone runs the script without any argument, e.g.:
myscript
will run in interactive mode...

Regular expression Bash issue

I have to match a string composed of only lowercase characters repeated 2 times , for example ballball or printprint. For example the word ball is not accepted because is not repeated 2 time.
For this reason I have this code:
read input
expr='^(([a-z]*){2})$'
if [[ $input =~ $expr ]]; then
echo "OK string"
exit 0
fi
exit 10
but it doesn't work , for example if I insert ball the script prints "OK string".
What do I wrong?
Not all Bash versions support backreferences in regexes natively. If yours doesn't, you can use an external tool such as grep:
read input
re='^\([a-z]\+\)\1$'
if grep -q "$re" <<< "$input"; then
echo "OK string"
exit 0
fi
exit 1
grep -q is silent and has a successful exit status if there was a match. Notice how (, + and ) have to be escaped for grep. (grep -E would understand () without escaping.)
Also, I've replaced your * with + so we don't match the empty string.
Alternatively: your requirement means that a matching string has two identical halves, so we can check for just that, without any regexes:
read input
half=$(( ${#input} / 2 ))
if (( half > 0 )) && [[ ${input:0:$half} = ${input:$half} ]]; then
echo "OK string"
fi
This uses Substring Expansion; the first check is to make sure that the empty string doesn't match.
Your requirement is to match strings made of two repeated words. This is easy to do by just checking if the first half of your string is equal to the remaining part. No need to use regexps...
$ var="byebye" && len=$((${#var}/2))
$ test ${var:0:$len} = ${var:$len} && { echo ok ; } || echo no
ok
$ var="abcdef" && len=$((${#var}/2))
$ test ${var:0:$len} = ${var:$len} && { echo ok ; } || echo no
no
The regex [a-z]* will match any alphanumeric or empty string.
([a-z]*){2} will match any two of those.
Ergo, ^(([a-z]*){2})$ will match any string containing zero or more alphanumeric characters.
Using the suggestion from #hwnd (replacing {2} with \1) will enforce a match on two identical strings.
N.B: You will need a fairly recent version of bash. Tested in bash 4.3.11.

Shell: Checking if argument exists and matches expression

I'm new to shell scripting and trying to write the ability to check if an argument exists and if it matches an expression. I'm not sure how to write expressions, so this is what I have so far:
#!/bin/bash
if [[ -n "$1"] && [${1#*.} -eq "tar.gz"]]; then
echo "Passed";
else
echo "Missing valid argument"
fi
To run the script, I would type this command:
# script.sh YYYY-MM.tar.gz
I believe what I have is
if the YYYY-MM.tar.gz is not after script.sh it will echo "Missing valid argument" and
if the file does not end in .tar.gz it echo's the same error.
However, I want to also check if the full file name is in YYYY-MM.tar.gz format.
if [[ -n "$1" ]] && [[ "${1#*.}" == "tar.gz" ]]; then
-eq: (equal) for arithmetic tests
==: to compare strings
See: help test
You can also use:
case "$1" in
*.tar.gz) ;; #passed
*) echo "wrong/missing argument $1"; exit 1;;
esac
echo "ok arg: $1"
As long as the file is in the correct YYYY-MM.tar.gz format, it obviously is non-empty and ends in .tar.gz as well. Check with a regular expression:
if ! [[ $1 =~ [0-9]{4}-[0-9]{1,2}.tar.gz ]]; then
echo "Argument 1 not in correct YYYY-MM.tar.gz format"
exit 1
fi
Obviously, the regular expression above is too general, allowing names like 0193-67.tar.gz. You can adjust it to be as specific as you need it to be for your application, though. I might recommend
[1-9][0-9]{3}-([1-9]|10|11|12).tar.gz
to allow only 4-digit years starting with 1000 (support for the first millennium ACE seems unnecessary) and only months 1-12 (no leading zero).