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. .. code-block:: bash 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 .. code-block:: bash 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. .. code-block:: bash 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 .. code-block:: bash 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 .. code-block:: bash 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 ^^^^^^^^^^^^^^^^^ .. code-block:: bash 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 .. code-block:: bash [[ -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 .. code-block:: bash # 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 .. code-block:: bash 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 .. code-block:: bash # 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 .. code-block:: bash 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