Conditions
Like any other programming languages, BASH supports conditions for logical operations. Unlike POSIX, bash supports both POSIX and BASH extended format.
This guide here is using BASH extended conditions. That also means it breaks POSIX compliance. If you want to maintain portability, you should use POSIX conditions.
if else if
If else if condition, like its name implies, is a value condition operator. It performs value checking sequentially based on the conditions list and executes the code block if the condition is right. A good way to write these conditions is:
if [[ condition ]]; then
....
elif [[ condition ]]; then
....
else
....
fi
Example:
#!/bin/sh
OS="$(uname -s)"
if [[ "$OS" == "FreeBSD" ]]; then
echo "This is FreeBSD"
elif [[ "$OS" == "CYGWIN_NT-5.1" ]]; then
echo "This is Cygwin"
elif [[ "$OS" == "SunOS" ]]; then
echo "This is Solaris"
elif [[ "$OS" == "Darwin" ]]; then
echo "This is MacOS"
elif [[ "$OS" == "AIX" ]]; then
echo "This is AIX"
elif [[ "$OS" == "Minix" ]]; then
echo "This is Minix"
elif [[ "$OS" == "Linux" ]]; then
echo "This is Linux"
else
echo "Unknown OS"
fi
Best Practice
- Keep the
then
keyword the same line as theif
orelif
.- Rationale: cleaner one line execution statement and reduce test tool complications.
- Watch for your indentation
- Rationale: if you need more than 3 indents (including function indentation), it signals cyclomatic complexity and you should consider re-writing your code. Heed the warning.
Case
Case is also a conditional operator. The difference between if else if
and case
is that case
relies only on a single value or a single condition to determine the next course of action. A good style of writing is:
case "$value"; do
value1|valueOne)
...
;;
value2|valueTwo)
...
;;
*)
...
;;
done
The last case is the default when no other specific case is fulfilled. Here's an example converting from if else if.
#!/bin/sh
OS="$(uname -s)"
case "$OS" in
"FreeBSD")
echo "This is FreeBSD"
;;
"CYGWIN_NT-5.1")
echo "This is CYGWIN"
;;
"SunOS")
echo "This is Solaris"
;;
"Darwin")
echo "This is MacOS"
;;
"AIX")
echo "This is AIX"
;;
"Minix")
echo "This is Minix"
;;
"Linux")
echo "This is Linux"
;;
*)
echo "Unknown OS"
;;
done
Best Practice
- Keep the
;;
break case keyword as one line. It itself is an instruction.- Rationale: cleaner one line execution statement and reduce test tool complications.
- Watch for your indentation
- Rationale: if you need more than 3 indents (including function indentation), it signals cyclomatic complexity and you should consider re-writing your code. Heed the warning.
New Test ([[)
BASH allows the use of new test, which is a double bracket ([[
). This test is BASH specific feature, operates in a much familiar syntaxes. The idea is that everything is compared via strings. We will review each expression closely.
Filesystem Flags
Since everything in Unix is a file, there is definitely a set of filesystem flags. You can use these flags to check for file, directory, block device, character device, missing file, status, permission, group, etc.
[[ -b "/path/to/file" ]]
- check/path/to/file
is a block device. False if no.[[ -c "/path/to/file" ]]
- check/path/to/file
is a character file. False is no.[[ -d "/path/to/directory" ]]
- check/path/to/directory
is a directory. False is no.[[ -e "/path/to/object" ]]
- check/path/to/object exists
. False is no.[[ -f "/path/to/file" ]]
- check/path/to/file
is a file. False is no.[[ -g "/path/to/file" ]]
- check/path/to/file
has Group-ID set. False is no or missing file.[[ -h "/path/to/file" ]]
,[[ -L "/path/to/file" ]]
- check/path/to/file
is a symbolic link. Failed is no or missing file. If the final file is a symbolic link. It will not follow.[[ -p "/path/to/file" ]]
- check/path/to/file
is a FIFO pipe. False is no or missing file.[[ -S "/path/to/file" ]]
- check/path/to/file
is a socket. False is no or missing file.
File Flags
[[ -s "/path/to/file" ]]
- check/path/to/file
contain byte data. False is 0 byte or missing file.[[ -r "/path/to/file" ]]
- check/path/to/file
has read permission. False means no read permission or missing file.[[ -w "/path/to/file" ]]
- check/path/to/file
has write permission. False means no write permission or missing file.[[ -x "/path/to/file" ]]
- check/path/to/file
has execute permission. False means no execute permission or missing file.[[ -u "/path/to/file" ]]
- check/path/to/file
has user-ID description. False means no user-ID description is set or missing file.[[ -t "/path/to/file" ]]
- check/path/to/file
has file descriptor. False means no or missing file.
String Operations
[[ "one" == "one" ]]
- check strings are the same. False otherwise.[[ "one" != "two" ]]
- check strings are not the same. False otherwise.
Arithmetic Operations
[[ 1 -eq 1 ]]
- check both integers are equal. False otherwise.[[ 1 -ne 2 ]]
- check both integers are not equal. False otherwise.[[ 1 -gt 0 ]]
- check the former integer is greater than the latter. False otherwise.[[ 1 -ge 0 ]]
- check the former integer is greater than or equal to the latter. False otherwise.[[ 0 -lt 1 ]]
- check the former integer is less than the latter. False otherwise.[[ 0 -le 1 ]]
- check the former integer is less than or equal to the latter. False otherwise.
Regular Expression
[[ "string" =~ regex ]]
- check string for the regular expression matches. False is no.[[ "string" == *pattern* ]]
- check string matches the pattern. False is no.
NOTE:
both regex
and *pattern*
must not be quoted as a single string. Example:
re='some RE'
if [[ $foo =~ $re ]]
[[ $foo = "*.glob" ]] # Wrong! *.glob is treated as a literal string.
[[ $foo = *.glob ]] # Correct. *.glob is treated as a glob-style pattern.
Multiple Expressions
This is when a condition requires multiple expressions (all listed above), we usually use AND or OR cases. Example: if expression1 is true AND expression2 is true, then do this. Here are some practices:
AND logic ( && )
[[ expression1 && expression2 ]]
- make logical checking for bothexpression1
andexpression2
using AND logic. if either is false, the overall result is false.
OR logic ( || )
[[ expression1 || expression2 ]]
- make logical checking for bothexpression1
andexpression2
using OR logic. if either is true, the overall result is true.
Compare First ( (...) )
[[ expression3 ] && (expression1 || expression2) ]]
- make logical checking inside( ... )
to run first before any other comparisons. In this case,expression1
andexpression2
OR logic gets evaluated first and produced the first result. Then, using that result,expression3
gets evaluated against with using AND logic.
Examples
#!/bin/sh
if [[ ("one" != "two") && ( (1 -gt 0) || (2 -lt 0) ) ]]; then
...
fi
Breaking Conditions into Multiple Lines
In some situations, you might encounter a need to break the conditions into multiple lines. There are various way to do it with forward slash. However, as a good practice, you should break it after the expression.
Rationale:
- Leave a clear signal for line continuation reason.
Here is an example:
if [[ ("$very_long_variable_name" != "super long string that doesn't fit") && \
("$very_long_variable_name2" != "super long string that doesn't fit") || \
("$very_long_variable_name" != "super long string that doesn't fit") ]]; then
...
fi
That's all about the BASH conditions. Feel free to proceed to the new section.