CONDITIONS & INTERACTIVE SCRIPTS
4.1. IF clause
General
At times you need to specify different courses of action to be taken in a shell script, depending on the success or failure of a command. The if construction allows you to specify such conditions.
The most compact syntax of the if command is:
if TEST-COMMANDS; then CONSEQUENT-COMMANDS; fi
or
if [[ some condition ]]; then do_something fi
Double brackets are used to enclose the conditional expression
The TEST-COMMAND list is executed, and if its return status is zero, the CONSEQUENT-COMMANDS list is executed. The return status is the exit status of the last command executed, or zero if no condition tested true.
The TEST-COMMAND often involves numerical or string comparison tests, but it can also be any command that returns a status of zero when it succeeds and some other status when it fails. Unary expressions are often used to examine the status of a file. If the FILE argument to one of the primaries is of the form /dev/fd/N, then file descriptor “N” is checked. stdin, stdout and stderr and their respective file descriptors may also be used for tests.
Expressions used with if
The table below contains an overview of the so-called “primaries” that make up the TEST-COMMAND command or list of commands. These primaries are put between square brackets to indicate the test of a conditional expression.
Primary expressions
Primary Meaning
[ -a FILE ] True if FILE exists.
[ -b FILE ] True if FILE exists and is a block-special file.
[ -c FILE ] True if FILE exists and is a character-special file.
[ -d FILE ] True if FILE exists and is a directory.
[ -e FILE ] True if FILE exists.
[ -f FILE ] True if FILE exists and is a regular file.
[ -g FILE ] True if FILE exists and its SGID bit is set.
[ -h FILE ] True if FILE exists and is a symbolic link.
[ -k FILE ] True if FILE exists and its sticky bit is set.
[ -p FILE ] True if FILE exists and is a named pipe (FIFO).
[ -r FILE ] True if FILE exists and is readable.
[ -s FILE ] True if FILE exists and has a size greater than zero.
[ -t FD ] True if file descriptor FD is open and refers to a terminal.
[ -u FILE ] True if FILE exists and its SUID (set user ID) bit is set.
[ -w FILE ] True if FILE exists and is writable.
[ -x FILE ] True if FILE exists and is executable.
[ -O FILE ] True if FILE exists and is owned by the effective user ID.
[ -G FILE ] True if FILE exists and is owned by the effective group ID.
[ -L FILE ] True if FILE exists and is a symbolic link.
[ -N FILE ] True if FILE exists and has been modified since it was last read.
[ -S FILE ] True if FILE exists and is a socket.
[ FILE1 -nt FILE2 ] True if FILE1 has been changed more recently than FILE2, or if FILE1 exists and FILE2 does not.
[ FILE1 -ot FILE2 ] True if FILE1 is older than FILE2, or if FILE2 exists and FILE1 does not.
[ FILE1 -ef FILE2 ] True if FILE1 and FILE2 refer to the same device and inode numbers.
[ -o OPTIONNAME ] True if shell option “OPTIONNAME” is enabled.
[ -z STRING ] True if the length of “STRING” is zero.
[ -n STRING ] or [ STRING ] True if the length of “STRING” is non-zero.
[ STRING1 == STRING2 ] True if the strings are equal. “=” may be used instead of “==” for strict POSIX compliance.
[ STRING1 != STRING2 ] True if the strings are not equal.
[ STRING1 < STRING2 ] True if “STRING1” sorts before “STRING2” lexicographically in the current locale.
[ STRING1 > STRING2 ] True if “STRING1” sorts after “STRING2” lexicographically in the current locale.
[ ARG1 OP ARG2 ] “OP” is one of -eq, -ne, -lt, -le, -gt or -ge. These arithmetic binary operators return true if “ARG1” is equal to, not equal to, less than, less than or equal to, greater than, or greater than or equal to “ARG2”, respectively. “ARG1” and “ARG2” are integers.
Combining expressions
Operation Effect
[ ! EXPR ] True if EXPR is false.
[ ( EXPR ) ] Returns the value of EXPR. This may be used to override the normal precedence of operators.
[ EXPR1 -a EXPR2 ] True if both EXPR1 and EXPR2 are true.
[ EXPR1 -o EXPR2 ] True if either EXPR1 or EXPR2 is true.
The [ (or test) built-in evaluates conditional expressions using a set of rules based on the number of arguments. More information about this subject can be found in the Bash documentation. Just like the if is closed with fi, the opening square bracket should be closed after the conditions have been listed.
Commands following the then statement
The CONSEQUENT-COMMANDS list that follows the then statement can be any valid UNIX command, any executable program, any executable shell script or any shell statement, with the exception of the closing fi. It is important to remember that the then and fi are considered to be separated statements in the shell. Therefore, when issued on the command line, they are separated by a semi-colon.
In a script, the different parts of the if statement are usually well-separated.
Activity:
o If clause to check file is existing.
vi checkfile.sh
#!/bin/bash
echo “This scripts checks the existence of the messages file.”
echo “Checking…”
if [ -f /var/log/messages ]
then
echo “/var/log/messages exists.”
else
echo “/var/log/messages DO NOT exists.”
fi
echo
echo “…done. “
chmod +x checkfile.sh
./checkfile.sh
o Check if the file is protected against accidental overwriting using redirection.
if [ -o noclobber ]
then
echo “Your files are protected against accidental overwriting using redirection.”
fi
o Testing exit status with if.
if [ $? -eq 0 ]
then
echo “Earlier command executed successfully”
else
echo “Earlier command DID NOT execute successfully”
fi
o Testing with a command and then $?
grep oracle /etc/passwd
if [ $? -eq 0 ]
then
echo “Earlier command executed successfully”
else
echo “Earlier command DID NOT execute successfully”
fi
grep oraC /etc/passwd
if [ $? -eq 0 ]
then
echo “Earlier command executed successfully”
else
echo “Earlier command DID NOT execute successfully”
fi
o Testing this user existence within if clause.
if ! grep oracle /etc/passwd
then
echo “User doesn’t exists”
else
echo “User exists”
fi
o Working with numeric.
num=`cat revenues| wc -l `
if [ “$num” -gt “4” ]
then
echo
echo “Condition is true”
else
echo “Condition is false”
fi
o Working with string comparisons.
if [ “$(whoami)” != ‘root’ ]
then
echo “You have no permission to run $0 as non-root user.”
fi
o Double-pipes for a logical OR statement
if [[ $a -lt 25 || $a –gt 50 ]]
then
echo ” The value $a either: less than 25 or greater than 50 “
else
echo ” The value of $a is between 25 and 50 “
fi
4.2. Advanced IF usage
Checking command line arguments
Instead of setting a variable and then executing a script, it is frequently more elegant to put the values for the variables on the command line.
We use the positional parameters $1, $2, …, $N for this purpose. $# refers to the number of command line arguments. $0 refers to the name of the script.
Activity:
o Example with two arguments we passed.
vi weight.sh
#!/bin/bash
# This script prints a message about your weight if you give it your
# weight in kilos and height in centimeters.
weight=”$1″
height=”$2″
idealweight=$[$height – 100]
if [ $weight -le $idealweight ] ;
then
echo “You should eat a bit more fat.”
else
echo “You should eat a bit more fruit.”
fi
o Execute this script with debug mode to verify the process flow.
bash -x weight.sh 60 175
o Let us edit this script to also include verifying number of arguments passed.
vi weight.sh
#!/bin/bash
# This script prints a message about your weight if you give it your
# weight in kilos and height in centimeters.
if [ ! $# == 2 ]; then
echo “Usage: $0 weight_in_kilos length_in_centimeters”
exit
fi
weight=”$1″
height=”$2″
idealweight=$[$height – 100]
if [ $weight -le $idealweight ] ; then
echo “You should eat a bit more fat.”
else
echo “You should eat a bit more fruit.”
fi
if/then/elif/else constructs
General
This is the full form of the if statement:
if TEST-COMMANDS; then
CONSEQUENT-COMMANDS;
elif MORE-TEST-COMMANDS; then
MORE-CONSEQUENT-COMMANDS;
else ALTERNATE-CONSEQUENT-COMMANDS;
fi
The TEST-COMMANDS list is executed, and if its return status is zero, the CONSEQUENT-COMMANDS list is executed. If TEST-COMMANDS returns a non-zero status, each elif list is executed in turn, and if its exit status is zero, the corresponding MORE-CONSEQUENT-COMMANDS is executed and the command completes. If else is followed by an ALTERNATE-CONSEQUENT-COMMANDS list, and the final command in the final if or elif clause has a non-zero exit status, then ALTERNATE-CONSEQUENT-COMMANDS is executed. The return status is the exit status of the last command executed, or zero if no condition tested true.
Activity:
o Example with complex variable and simple if.
vi disktest.sh
#!/bin/bash
# This script does a very simple test for checking disk space.
space=`df -h | awk ‘{print $5}’ | grep % | grep -v Use | sort -n | tail -1 | cut -d “%” -f1 -`
alertvalue=”85″
if [ “$space” -ge “$alertvalue” ]; then
echo “ALERT!!!! One of my disks is nearly full!”
else
echo “Disk space is normal”
fi
o Example with nested if clauses.
vi testleap.sh
#!/bin/bash
# This script will test if we’re in a leap year or not. year=`date +%Y`
if [ $[$year % 400] -eq “0” ]; then
echo “This is a leap year. February has 29 days.”
elif [ $[$year % 4] -eq 0 ]; then
if [ $[$year % 100] -ne 0 ]; then
echo “This is a leap year, February has 29 days.”
else
echo “This is not a leap year. February has 28 days.”
fi
else
echo “This is not a leap year. February has 28 days.”
fi
o Using exit numbers in the if clauses.
#!/bin/bash
#This script lets you present different menus to Phinx. He will only be happy
# when given a fish. We’ve also added a dolphin and (presumably) a camel.
menu=”$1″
animal=”$2″
if [ “$menu” == “fish” ]; then
if [ “$animal” == “penguin” ]; then
echo “Hmmmmmm fish… Phinx happy!”
elif [ “$animal” == “dolphin” ]; then
echo “Its okay for Phinx”
else
echo ” Yuck Phinx dont like “
fi
else
if [ “$animal” == “penguin” ]; then
echo ” Phinx don’t like that. Phinx wants fish!”
exit 1
elif [ “$animal” == “dolphin” ]; then
echo “XXXXXXXXXXXXXXXXXX!”
exit 2
else echo “Will you read this sign?!”
exit 3
fi
fi
4.3. CASE statements
Simplified conditions
Nested if statements might be nice, but as soon as you are confronted with a couple of different possible actions to take, they tend to confuse. For the more complex conditionals, use the case syntax:
case EXPRESSION in CASE1) COMMAND-LIST;; CASE2) COMMAND-LIST;; … CASEN) COMMAND-LIST;; esac
Each case is an expression matching a pattern. The commands in the COMMAND-LIST for the first match are executed.
The “|” symbol is used for separating multiple patterns, and the “)” operator terminates a pattern list. Each case plus its according commands are called a clause. Each clause must be terminated with “;;”. Each case statement is ended with the esac statement.
Activity:
o In the example, we demonstrate use of cases for sending a more selective warning message with the disktest.sh script:
#!/bin/bash
# This script does a very simple test for checking disk space.
space=`df -h | awk ‘{print $5}’ | grep % | grep -v Use | sort -n | tail -1 | cut -d “%” -f1 -`
case $space in
[1-6]*)
Message=”Disk space is well below threshold.”
;;
[7-8]*)
Message=”Please do some housekeeping. There’s a partition that is $space % full.”
;;
9[1-8])
Message=”Order for a new disk… One partition is $space % full.”
;;
99)
Message=”ALERTTTTTT! There’s a partition at $space %!”
;;
*)
Message=”Please initiate a bridge, as this will be a P1 incident…”
;;
esac
echo $Message
o Case on string value comparisions.
case “$1” in
start)
start
;;
stop)
stop
;;
status)
status filename
;;
restart)
stop
start
;;
Condition restart)
if test “x`pidof filename`” != x; then
stop
start
fi
;;
*)
echo $”Usage: $0 {start|stop|restart|conditionrestart|status}”
exit 1
esac
4.4. Display user messages
Interactive or not?
Some scripts run without any interaction from the user at all. Advantages of non-interactive scripts include:
o The script runs in a predictable way every time.
o The script can run in the background.
Many scripts, however, require input from the user, or give output to the user as the script is running. The advantages of interactive scripts are, among others:
o More flexible scripts can be built.
o Users can customize the script as it runs or make it behave in different ways.
o The script can report its progress as it runs.
When writing interactive scripts, never hold back on comments. A script that prints appropriate messages is much more user-friendly and can be more easily debugged. A script might do a perfect job, but you will get a whole lot of support calls if it does not inform the user about what it is doing. So include messages that tell the user to wait for output because a calculation is being done. If possible, try to give an indication of how long the user will have to wait. If the waiting should regularly take a long time when executing a certain task, you might want to consider integrating some processing indication in the output of your script.
When prompting the user for input, it is also better to give too much than too little information about the kind of data to be entered. This applies to the checking of arguments and the accompanying usage message as well.
Bash has the echo and printf commands to provide comments for users, and although you should be familiar with at least the use of echo by now, we will discuss some more examples in the next sections.
Using the echo built-in command
The echo built-in command outputs its arguments, separated by spaces and terminated with a newline character. The return status is always zero. echo takes a couple of options:
-e: interprets backslash-escaped characters.
-n: suppresses the trailing newline.
Activity:
o Let us write two scripts in a better format than earlier with user messages using echo.
#!/bin/bash
# This script lets you present different menus to Phinx. He will only be happy
# when given a fish. To make it more fun, we added a couple more animals.
if [ “$menu” == “fish” ]; then
if [ “$animal” == “penguin” ]; then
echo -e ” Hmmmmmm fish… Phinx happy!\\n”
elif [ “$animal” == “dolphin” ]; then
echo -e ” Its okay for Phinx \\n” else
echo -e ” Yuck Phinx dont like \\n” fi
else
if [ “$animal” == “penguin” ]; then
echo -e ” Phinx don’t like that. Phinx wants fish!\\n”
exit 1
elif [ “$animal” == “dolphin” ]; then
echo -e ” XXXXXXXXXXXXXXXXXX! “
exit 2
else
echo -e “Will you read this sign?! Don’t feed the “$animal”s!\\n”
exit 3
fi
fi
o Example which includes case conditional statements.
#!/bin/bash
# This script acts upon the exit status given by penguin.sh
if [ “$#” != “2” ]; then
echo -e “Usage of the feed script:\\t$0 food-on-menu animal-name\\n”
exit 1
else
export menu=”$1″
export animal=”$2″
echo -e “Feeding $menu to $animal…\\n” feed=”/home/oracle/scripting/penguin.sh”
$feed
result=”$?”
echo -e “Done feeding.\\n”
case “$result” in
1)
echo -e “Guard: \\”You’d better give’m a fish, else he gets violent…\\”\\n”
;;
2)
echo -e “Guard: \\”No wonder they will be extinct…\\”\\n”
;;
3)
echo -e “Guard: \\”Buy packaged foods ***\\”\\n”
echo -e “Guard: \\”Don’t provide unhealthy foods\\”\\n”
;;
*)
echo -e “Guard: \\”Don’t forget the guide!\\”\\n”
;;
esac
fi
echo “Leaving…”
echo -e “\\a\\a\\aThanks for visiting, hope to see you again soon!\\n”
o Run feed.sh to re-run penguin.sh script.
./feed.sh apple camel
Escape sequences used by the echo command
Sequence Meaning
\\a Alert (bell).
\\b Backspace.
\\c Suppress trailing newline.
\\e Escape.
\\f Form feed.
\\n Newline.
\\r Carriage return.
\\t Horizontal tab.
\\v Vertical tab.
\\\\ Backslash.
\\0NNN The eight-bit character whose value is the octal value NNN (zero to three octal digits).
\\NNN The eight-bit character whose value is the octal value NNN (one to three octal digits).
\\xHH The eight-bit character whose value is the hexadecimal value (one or two hexadecimal digits).
4.5. User input
Using the read built-in command
The read built-in command is the counterpart of the echo and printf commands. The syntax of the read command is as follows:
read [options] NAME1 NAME2 … NAMEN
One line is read from the standard input, or from the file descriptor supplied as an argument to the -u option. The first word of the line is assigned to the first name, NAME1, the second word to the second name, and so on, with leftover words and their intervening separators assigned to the last name, NAMEN. If there are fewer words read from the input stream than there are names, the remaining names are assigned empty values.
The characters in the value of the IFS variable are used to split the input line into words or tokens; see the section called “Word splitting”. The backslash character may be used to remove any special meaning for the next character read and for line continuation.
If no names are supplied, the line read is assigned to the variable REPLY.
The return code of the read command is zero, unless an end-of-file character is encountered, if read times out or if an invalid file descriptor is supplied as the argument to the -u option.
Options to the read built-in
Option Meaning
-a ANAME The words are assigned to sequential indexes of the array variable ANAME, starting at 0. All elements are removed from ANAME before the assignment. Other NAME arguments are ignored.
-d DELIM The first character of DELIM is used to terminate the input line, rather than newline.
-e readline is used to obtain the line.
-n NCHARS read returns after reading NCHARS characters rather than waiting for a complete line of input.
-p PROMPT Display PROMPT, without a trailing newline, before attempting to read any input. The prompt is displayed only if input is coming from a terminal.
-r If this option is given, backslash does not act as an escape character. The backslash is considered to be part of the line. In particular, a backslash-newline pair may not be used as a line continuation.
-s Silent mode. If input is coming from a terminal, characters are not echoed.
-t TIMEOUT Cause read to time out and return failure if a complete line of input is not read within TIMEOUT seconds. This option has no effect if read is not reading input from the terminal or from a pipe.
-u FD Read input from file descriptor FD.
Activity:
o Writing a better leaptest.sh script.
#!/bin/bash
# This script will test if you have given a leap year or not.
year=”$1″
if (( (“$year” % 400) == “0” )) || (( (“$year” % 4 == “0”) && (“$year” % 100 != “0”) )); then
echo “$year is a leap year.”
else
echo “This is not a leap year.”
fi
o Writing a script to prompt user input.
vi relatives.sh
#!/bin/bash
# This is a program that keeps your address book up to date.
relatives=”/home/oracle/scripting/relatives”
echo “Hello, “$USER”. This script will register you in Dian’s relatives database.”
echo -n “Enter your name and press [ENTER]: “
read name
echo -n “Enter your gender [m or f] : “
read -n 1 gender
echo
grep -i “$name” “$relatives”
if [ $? == 0 ]; then
echo “You are already registered, quitting.”
exit 1
elif [ “$gender” == “m” ]; then
echo “$name $gender” >> “$relatives”
echo “You are added to Dian’s relatives list.”
exit 1
else
echo -n “How old are you?”
read age
if [ $age -lt 25 ]; then
echo -n “Where do you reside? “
read place
echo “$name $gender $age $place” >> “$relatives”
echo “You are added to Dian’s relatives list. Thank you so much!”
else
echo “$name $gender $age” >> “$relatives”
echo “You are added to Dian’s relatives list.”
exit 1
fi
fi
To continue with Tutorial 5, please click here
To start reading from Chapter 1, please click here