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

  • -e File exists

  • -f File Exists and is a normal File

  • -s File Exists file and size is greater than zero

  • -x test for file (or directory) execute bit is set

  • -L test for symbolic links Files

  • -d test 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