Deep Dive Bash test
Introduction
Had hard time learning if/else for the first time in bash after coming from C/C++. I was quite confused why spacing
is so important with [ and ]. Later realized [ is just a synonym for test command.
Understanding if
If we look into the signature of if bash builtin:
if COMMANDS_0
then
COMMANDS_1
fiif executes based on the exit status of COMMAND_0. Now following code should and will run without any error if
echo exits successfully.
if echo "Near if"
then echo "Inside if"
fi
# Output:
# Near if
# Inside ifif also works with exit status of funciton. As we can control/return exit status of function, we can write a
function like,
True () {
echo "Inside 'True' function"
return 0
}
# As last command's exit status is returned automatically
# We can even remove the return statement
True () {
echo "Inside 'True' function"
}Now we can write if like:
if True
then
echo "Inside if"
fi
# Output:
# Inside 'True' function
# Inside ifWe can further update the function and write function like file_exists:
file_exists() {
FILE_NAME="$1"
# Redirecting both stdin and stdout to /dev/null
ls "$FILE_NAME" &> /dev/null
}
if file_exists "/etc/passwd"
then
echo "File exists"
fi
# Output:
# File existsBash builtin test is like file_exits function but with lots of options and flexible. We can check existence of
file using -f test option.
if test -f "/etc/passwd"
then
echo "File exists"
fitest Operations
test builtin has lots of options for checking conditions. test also has a synonym [, which behaves exactly same as
test only expects last augments to be ].
File Operations:
Here only some important file operations are mentioned. For complete list see help test.
| Option | Description |
|---|---|
| -a FILE | True if FILE exists |
| -e FILE | True if FILE exists |
| -f FILE | True if FILE is a regular file |
| -d FILE | True if FILE is a directory |
| -h FILE | True if FILE is a symbolic link |
| -L FILE | True if FILE is a symbolic link |
| -r FILE | True if FILE is readable by user |
| -w FILE | True if FILE is writeable by user |
| -x FILE | True if FILE is executable by user |
| -s FILE | True if FILE is not empty |
| -O FILE | True if FILE is owned by user |
| -G FILE | True if FILE is owned by user’s group |
| FILE1 -nt FILE2 | True if FILE1 is newer than FILE2 |
| FILE1 -ef FILE2 | True if FILE1 is hard link of FILE2 |
All file operations except -h and -L works on the target file if FILE is a symbolic link.
if test -d "/etc"
then
echo "File exists and is a directory"
fi
# Can also be written with [
if [ -d "/etc" ]
then
echo "File exists and is a directory"
fi
# Output:
# File exists and is a directoryString Operations:
| Option | Description |
|---|---|
| -z STRING | True if STRING is empty |
| -n STRING | True if STRING is not empty |
| STRING1 = STRING2 | True if STRING1 and STRING2 are equal |
| STRING1 != STRING2 | True if STRING1 and STRING2 not are equal |
if [ -z "" ]
then
echo "Empty String"
fi
# Output:
# Empty String
if [ "John" != "Jane" ]
then
echo "Strings are not equal"
fi
# Output:
# Strings are not equalArithmetic Operations:
| Option | Description |
|---|---|
| NUM1 -eq NUM2 | True if NUM1 and NUM2 are equal |
| NUM1 -ne NUM2 | True if NUM1 and NUM2 are not equal |
| NUM1 -lt NUM2 | True if NUM1 is less than NUM2 |
| NUM1 -le NUM2 | True if NUM1 is less than or equal to NUM2 |
| NUM1 -gt NUM2 | True if NUM1 is greater than NUM2 |
| NUM1 -ge NUM2 | True if NUM1 is greater than or equal to NUM2 |
NUM1=5
NUM2=6
if [ "$NUM1" -lt "$NUM2" ]
then
echo "$NUM1 is less than $NUM2"
fi
# Output:
# 5 is less than 6Other Operations:
| Option | Description |
|---|---|
| -v VAR | True if VAR is defined |
| ! EXPR | True if EXPR is false |
| EXPR1 -a EXPR2 | True if both EXPR1 and EXPR2 is true |
| EXPR1 -o EXPR2 | True if either EXPR1 or EXPR2 is true |
NAME="John"
if [ -v NAME ]
then
echo "\$NAME = $NAME"
fi
# Output:
# $NAME = John
if [ ! -f "/root/not-exists" ]
then
echo "File doesn't exists"
fi
# Output:
# File doesn't exists
NUM=5
if [ "$NUM" -gt 1 -a "$NUM" -lt 10 ]
then
echo "$NUM is between 1-10"
fi
# We can also write using &&, as two separate command
NUM=5
if [ "$NUM" -gt 1 ] && [ "$NUM" -lt 10 ]
then
echo "$NUM is between 1-10"
fi
# Output:
# 5 is between 1-10Playing with if
When we add multiple commands with if separated with ;,if depends on exit status of last command.
if cat /file-not-found; echo "Hello"
then
echo "Inside if"
fi
# Output:
# cat: /file-not-found: No such file or directory
# Hello
# Inside ifSame code can be written with grouping command {}. Inside { and } all command must end with ;.
if { cat /file-not-found; echo "Hello"; }
then
echo "Inside if"
fiWe can also run code inside a sub shell using ( and ). This will create a separate environment and run code inside
that environment.
NUM=5
{ NUM=6; }
echo $NUM # Output: 6
# Created sub-shell
NUM=5
( NUM=6 )
echo $NUM # Output: 5We can execute if clauses in sub shell, the output will be same.
if ( cat /file-not-found; echo "Hello" )
then
echo "Inside if"
fiif clauses can also execute based on arithmetic expression using (( and )). If the result of arithmetic expression
is non-zero then then block is executed.
if (( 5 - 10 ))
then
echo "Non-Zero numbers are considered True"
fi
# Output:
# Non-Zero numbers are considered True
if (( 5 - 5 ))
then
echo "Don't Print"
else
echo "Enters else block"
fi
# Output:
# Enters else block[ VS [[
Bash has another builtin [[ which is superset of [. Its supports all features of [ plus some more. The most
useful feature is matching regular expression with =~.
if [[ "John" =~ Jo.n ]]
then
echo "Matched Jo.n"
fi
# Output:
# Matched Jo.nWe can also use && and || inside [[ and ]].
NUM=5
if [[ "$NUM" -gt 1 && "$NUM" -lt 10 ]]
then
echo "$NUM is between 1-10"
fiCheck help [[ for more details.
Tools Version
- bash 4.4.19(1)-release