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
fi
if
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 if
if
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 if
We 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 exists
Bash 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"
fi
test 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 directory
String 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 equal
Arithmetic 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 6
Other 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-10
Playing 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 if
Same 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"
fi
We 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: 5
We can execute if
clauses in sub shell, the output will be same.
if ( cat /file-not-found; echo "Hello" )
then
echo "Inside if"
fi
if
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.n
We can also use &&
and ||
inside [[
and ]]
.
NUM=5
if [[ "$NUM" -gt 1 && "$NUM" -lt 10 ]]
then
echo "$NUM is between 1-10"
fi
Check help [[
for more details.
Tools Version
- bash 4.4.19(1)-release