Command "if"

if construction used very often in shell script, while not many users understand it's syntax correct. Manuals says:

if list; then list; [ elif list; then list; ] ... [ else list; ] fi
Where list is list of commands.

"if" is a conditional construction, a "then" list will be executed if "if" list was successfull.

Let's do some tests to understand how it works.

$ cd /tmp		# A good place for junk files
$ echo nana > a		# Create file "a" contains "nana" word in it.
$ rm -f b		# Ensure that file "b" does not exist.
$ grep -q nana a
$ echo $?		# Check exit status of previous command
0
$ grep -q mama a
$ echo $?
1
$ grep -q mama b
grep: b: No such file or directory
$ echo $?
2
Here "grep -q" command (-q - quiet, no output) return success status (0), when it found pattern and other values in other cases.

So, our first "if" costruction will be :

if grep -q nana a ; then
	echo "pattern found"
fi

Common practice to use number of pipelined commands in testing constructions. Remember that only last command's exit status will checked. Would first command failed, this will be successfully ignored and this is probably not what you want. Example:

$ if hostname | grep -q localhost ; then
	echo "hostname not configured yet"
fi
$

My hostname is other than default "localhost", then this test did not print message exactly as it was expected. The second run contains typo in first command:

$ if hstname | grep -q localhost ; then
	echo "hostname not configured yet"
fi
bash: hstname: command not found
$

The second run contains typo in first command, therefore it failed, nothing comes to grep command, then it did not find "localhost" pattern. In this case, nothing will be printed out even hostname does not configured yet.

OFF TOPIC: Try to use less commands in your scripts to be more efficient. Every command forks new process, allocates memory and manage all around it. When this happen within recursive loops, less commands can save a lot of time. Here is an example how to eliminate running unnessesary "hostname" command in Linux environment:

$ if grep -q localhost /proc/sys/kernel/hostname ; then
	echo "hostname not configured yet"
fi
$

"test" command

"test" command obviously tests supplied condition and returns success value for true condition. Check "man test" for avaliable tests, they are very usefull. Example:

$ test 2 -le 5
$ echo $?
0
$ test 5 -le 2
$ echo $?
1

Historically "test" command aliased to [ symbol and was symbolic link to it, like:

$ ls -l /bin/[
-rwxr-xr-x. 1 root root 40888 May 19 18:18 /bin/[ -> test
I saw they are separate command now in latest linuxes without real reason. The only difference in use [ command is to close condition with ] bracet. Same examples:
$ [ 2 -le 5 ]
$ echo $?
0
$ [ 5 -le 2 ]
$ echo $?
1
Important NOTE: Now you know that they are not real bracets, but commands. You have to put spaces around braces, otherwice:
$ [2 -le 5]
bash: [2: command not found
$ [ 2 -le 5]
bash: [: missing `]'

"if" command again

Combining these two command give us powerfull construction, you used to see:

if [ 2 -le 5 ] ; then
	echo "Correct"
fi

Note:Please not forget that bracet is command when writing scripts. Check "man test" for avaliable costructions. Comparing numbers and strings is different.

HOSTNAME=localhost
if [ $HOSTNAME = "localhost" ] ; then
	echo "hostname not configured yet"
fi

Lets see what happen if variable will be zero (this happen very often when grepping and awking some inputs):

$ HOSTNAME=""
$ if [ $HOSTNAME = "localhost" ] ; then
	echo "hostname not configured yet"
fi
bash: [: =: unary operator expected
$

Still you remeber that bracet is really "test" command ? Zero length string is suggested as missing argument, that breaks ability to test. The script can skeep then an important part of code after this test fail. The solution is to prepend strings with common for both comparison sides value, like:

if [ 'x'$HOSTNAME = 'x'"localhost" ] ; then
        echo "hostname not configured yet"
fi

Same solution can be used when integer number expected:

$ a=2 ; if [ '0'$a -lt '0'5 ] ; then echo "correct" ; fi
correct
$ a= ; if [ '0'$a -lt '0'5 ] ; then echo "correct" ; fi
correct

Let's return to strings and check what will happen if our HOSTNAME variable gets multiline content in it:

$ HOSTNAME=$(echo -e "localhost\nAnother localhost line, grepped by mistake")
$ if [ 'x'$HOSTNAME = 'x'"localhost" ] ; then
        echo "hostname not configured yet"
fi
bash: [: too many arguments

This is another very often mistake when writing scripts. Usually this happen when playing with file names wihtout thought they could have spaces and other special symbols in names. The solution is easy. EVERY time (except special cases) writes varuables surrounded by double quotes. The winning syntax will be:

if [ 'x'"$HOSTNAME" = 'x'"localhost" ] ; then
        echo "hostname not configured yet"
fi

Don't you think we found the bullet-proof solution ? Then check this:

HOSTNAME=localhost
if [ 'x'"$HOSTANME" = 'x'"localhost" ] ; then
        echo "hostname not configured yet"
fi

In this case "if" construction checking undefined typo variable HOSTANME, when really defined HOSTNAME even not checked. Script will not warn you about, and you will sure it work correct. The good practice is check every few lines of code.

Another usefull built-in conditions check constructions

The command2 will run if command1 runs OK:

command1 opt opt opt && command2

Command3 will run if command2 fail:

command2 || command3

Usefull combinations:

[ -d /mnt/cdrom ] && mkdir -p /mnt/cdrom	# Create /mnt/cdrom directory if not exist
cd /mnt/cdrom || exit 1				# Abort entire script if cannot "cd /mnt/cdrom"
command1 || echo "command1 failed"		# Print debug info

Updated on Tue Aug 9 15:14:03 IDT 2016 by Oleg Volkov More documentations here