Bash Conditionals and program flow
Logic in the shell is based on a test statement that yields with return value 0, (success, or true) or return value 1 (failed, or false).
Caution
That is the opposite of most programming languages Logic
- Conditional Constructs GNU Docs:
https://www.gnu.org/software/bash/manual/bash.html#Conditional-Constructs
https://www.gnu.org/software/bash/manual/bash.html#Bash-Conditional-Expressions
if-then Conditionals
If an else block is not needed, the shorter conditional forms are possible.
a="apples"
unset b
[[ "$a" ]] && echo "Variable 'a' is set"
[[ "$b" ]] || echo "Variable 'b' is NOT set"
[[ ! "$b" ]] && echo "Variable 'b' is NOT set"
[[ $a = apples ]] && echo "Variable 'a' equals apples"
[[ $a != apricot ]] && echo "Variable 'a' does NOT equal apricot"
[[ $a = apricot ]] || echo "Variable 'a' does NOT equal apricot"
regex="app.*"
[[ $a =~ $regex ]] && echo "Variable 'a' value starts wih app"
regex="appr.*"
[[ $a =~ $regex ]] || echo "Variable 'a' Does NOT start with 'appr'"
# single brackets can be used, but not preferred, and doesn't support regex
[ "$a" ] && echo "Variable 'a' is set"
The test expression can be used as well, but does NOT support regex matching
a="apples"
unset b
test "$a" && echo "Variable 'a' is set"
test "$b" || echo "Variable 'b' is NOT set"
test ! "$b" && echo "Variable 'b' is NOT set"
test $a = apples && echo "Variable 'a' equals apples"
test $a != apricot && echo "Variable 'a' does NOT equal apricot"
test $a = apricot || echo "Variable 'a' does NOT equal apricot"
if-then-else Conditionals
Note
make sure to check out man test it can test for files, symbolic files, etc.
if test "$PATH"; then echo "success"; else echo "failed"; fi
if [ "$PATH" ]; then echo "success"; else echo "failed"; fi
if [[ "$PATH" ]]; then echo "success"; else echo "failed"; fi
Warning
It is always preferred to enclose env variables in quotes, because they might expand to a string with multiple tokens
Testing The success of the last command
Sometimes it is requires to make a decision based on the success status of the last run command.
To test and take action upon last command failure
ls /zzz 2>/dev/null
if [[ $? != 0 ]]; then echo "last command has failed"; fi
ls /zzz 2>/dev/null
[[ $? != 0 ]] && echo "last command has failed"
ls /zzz 2>/dev/null
[[ $? = 0 ]] || echo "last command has failed"
To test and take action when last command is successful
ls /etc 1>/dev/null
if [[ $? = 0 ]]; then echo "last command successful"; fi
ls /etc 1>/dev/null
[[ $? = 0 ]] && echo "last command successful"
ls /etc 1>/dev/null
[[ $? != 0 ]] || echo "last command successful"
Boolean Operators
a=apples z=zebra
unset b c
# AND Operator
[[ $a && $z ]] && echo true
[[ $a && $b ]] || echo false
# OR Operator
[[ $a || $b ]] && echo true
[[ $b || $c ]] || echo false
# Not Operator
[[ ! $a ]] || echo false
[[ ! $b ]] && echo true
# Multiple Operators
[[ $a && ! $b ]] && echo true
Testing For Files and directories
man test for all file and node testing options
The following flags apply in the [[ ]] expression
-eFile exists-fFile Exists and is a normal File-sFile Exists file and size is greater than zero-xtest for file (or directory) execute bit is set-Ltest for symbolic links Files-dtest for directories
[[ -d "/etc" ]] && echo "Folder exists"
[[ -d "/etc/hosts" ]] || echo "this file is not a directory"
[[ -e "/etc/hosts" ]] && echo "This File exists"
[[ -e "/dev/null" ]] && echo "This File exists"
[[ -f "/etc/hosts" ]] && echo "File exists and is a regular file"
[[ -e "/dev/null" ]] || echo "Not a regular file"
[[ -L "/etc/hosts" ]] && echo "File exists and is a symbolic link"
[[ -x "/etc" ]] && echo "Folder is searchable"
[[ -x "/root" ]] || echo "Folder is not Searchable"
Numerical Testing
The following works with Integers only, which is reasonable when testing for files counts, line counts and similar shell and file system operations
# Greater than
[[ 5 -gt 4 ]]; echo $? # True
test 5 -gt 4 ; echo $? True, the test form
$pi=3
test $pi -eq 3 ; echo $? True
# Greater or equal
[[ 4 -ge 4 ]]; echo $? # True
# -lt less than
# -le less then or equal
# equal to
[[ 10 -eq 10 ]]; echo $? # True
# count the number of files in the root dir
root_files=$(ls / | wc -w)
[[ $root_files -gt 10 ]]; echo $? # True
# Count the number of lines in .bashrc
rc_lines=$(cat /home/mk/.bashrc | wc -l)
[[ $rc_lines -lt 1000 ]]; echo $?
Testing For Variable Existence
https://linuxize.com/post/bash-check-if-file-exists/ https://www.gnu.org/software/bash/manual/bash.html#Bash-Conditional-Expressions
Expression in script |
name=’fish’ |
name=’’ |
unset name |
|---|---|---|---|
test “$name” |
TRUE |
f |
f |
test -n “$name” |
TRUE |
f |
f |
test ! -z “$name” |
TRUE |
f |
f |
test ! “${name-x}” |
f |
TRUE |
f |
test ! “${name+x}” |
f |
f |
TRUE |
For Program flow, setting, unsetting and leaving out env variables is a valid strategy
test "$HOME"; echo $? # True
test "$xxx"; echo $? # False, variable doesn't exist yet
xxx=abcd
test "$xxx"; echo $? # True, variable now exists
unset xxx
test "$xxx"; echo $? # False, variable was unset
xxx=""
test "$xxx"; echo $? # False, variable is blank
# Create a function that tests for a variable
test_for_python () {
if [[ "$PYTHONPATH" ]]; then
echo "pythonpath is set"
else
echo "python path is not set"
fi
}
test_for_python
unset PYTHONPATH
test_for_python
# you can use not ! to reverse the logic with the ! operator
if [[ ! "$PYTHONPATH" ]]; then
echo "python path is not set"
else
echo "pythonpath is set"
fi
Testing String values and Regex
- POSIX Regex summary:
https://en.wikibooks.org/wiki/Regular_Expressions/POSIX_Basic_Regular_Expressions
=operator for exact matching!=operating for non-matching=~operator for regex matching
Warning
Keep the regex pattern in a variable to avoid issues
Warning
the test form doesn’t accept the regex matching, only the [[ ]] form does
PING=PONG
if [[ $PING = PONG ]]; then echo TRUE; else echo FALSE; fi # True
test $PING != PING; echo $? # False
keyword="a longer string"
if [[ $keyword = "a longer string" ]]; then echo TRUE; else echo FALSE; fi # True
# test outputs of two commands
test "$(dmesg)" != "$(cat /var/log/dmesg)"; echo $? # True
test "$(dmesg)" = "$(dmesg)"; echo $? # True
pattern='a longer.*'
if [[ $keyword =~ $pattern ]]; then echo TRUE; else echo FALSE; fi
if [[ $PING = PONG && -f $HOSTBASE ]]; then echo TRUE; else echo FALSE; fi
PING=PING
pattern='PO.*'
if [[ $PING =~ $pattern ]]; then echo TRUE; else echo FALSE; fi