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

Bookmarks