Sometimes we need to run a particular command or sequence of commands only if a certain condition is true.
For example, if program B processes the output of program A, we probably won't want to run B at all unless A finished successfully.
Conditional execution in Unix shell scripts often utilizes the exit status of the most recent command.
All Unix programs return an exit status. It is not possible to write a Unix program that does not return an exit status. Even if the programmer neglects to explicitly return a value, the program will return a default value.
By convention, programs return an exit status of 0 if they determine that they completed their task successfully and a variety of non-zero error codes if they failed. There are some standard error codes defined in the C header file sysexits.h. You can learn about them by running man sysexits.
We can check the exit status of the most recent command by examining the shell variable $? in Bourne shell family shells or $status in C shell family shells.
bash> ls myprog.c bash> echo $? 0 bash> ls -z ls: illegal option -- z usage: ls [-ABCFGHILPRSTUWZabcdfghiklmnopqrstuwx1] [-D format] [file ...] bash> echo $? 1 bash>
tcsh> ls myprog.c tcsh> echo $status 0 tcsh> ls -z ls: illegal option -- z usage: ls [-ABCFGHILPRSTUWZabcdfghiklmnopqrstuwx1] [-D format] [file ...] tcsh> echo $status 1 tcsh>
Run several commands correctly and incorrectly and check the $? or $status variable after each one.
All Unix shells have an if-then-else construct implemented as internal commands. The Bourne shell family of shells all use the same basic syntax. The C shell family of shells also use a common syntax, which is somewhat different from the Bourne shell family.
The general syntax of a Bourne shell family if statement is shown below. Note that there can be an unlimited number of elifs, but we will use only one for this example.
#!/bin/sh if command1 then command command ... elif command2 then command command ... else command command ... fi
The 'if' and the 'then' are actually two separate commands, so they must either be on separate lines as shown above, or separated by an operator such as ';', which can be used instead of a newline to separate Unix commands.
cd; ls if command; then
The if command executes command1 and checks the exit status when it completes.
If the exit status of command1 is 0 (indicating success), then all the commands before the elif are executed, and everything after the elif is skipped.
If the exit status is non-zero, then nothing above the elif is executed. The elif command then executes command2 and checks its exit status.
If the exit status of command2 is 0, then the commands between the elif and the else are executed and everything after the else is skipped.
If the exit status of command2 is non-zero, then everything above the else is skipped and everything between the else and the fi is executed.
In most programming languages, we use some sort of Boolean expression (usually a comparison, also known as a relation), not a command, as the condition for an if statement.
This is generally true in Bourne shell scripts as well, but the capability is provided in an interesting way. We'll illustrate by showing an example and then explaining how is works.
Suppose we have a shell variable and we want to check whether it contains the string "blue". We could use the following if statement to test:
#!/bin/sh printf "Enter the name of a color: " read color if [ "$color" = "blue" ]; then printf "You entered blue.\n" elif [ "$color" = "red" ]; then printf "You entered red.\n" else printf "You did not enter blue or red.\n" fi
The interesting thing about this code is that the square brackets are not Bourne shell syntax. As stated above, the Bourne shell if statement simply executes a command and checks the exit status. This is always the case.
The '[' in the condition above is actually an external command! In fact, is simply another name for the test command. The files /bin/test and /bin/[ are actually the same program file:
FreeBSD tocino bacon ~ 401: ls -l /bin/test /bin/[ -r-xr-xr-x 2 root wheel 8516 Apr 9 2012 /bin/[* -r-xr-xr-x 2 root wheel 8516 Apr 9 2012 /bin/test*
We could have also written the following:
if test "$color" = "blue"; then
Hence, "$color", =, "blue", and ] are all separate arguments to the [ command, and must be separated by white space. If the command is invoked as '[', then the last argument must be ']'. If invoked as 'test', then the ']' is not allowed.
The test command can be used to perform comparisons (relational operations) on variables and constants, as well as a wide variety of tests on files. For comparisons, test takes three arguments: the first and third are string values and the second is a relational operator.
# Compare a variable to a string constant test "$name" = 'Bob'
# Compare the output of a program directly to a string constant test `myprog` = 42
For file tests, test takes two arguments: The first is a flag indicating which test to perform and the second is the path name of the file or directory.
# See if output file exists and is readable to the user # running test test -r output.txt
The exit status of test is 0 (success) if the test is deemed to be true and a non-zero value if it is false.
shell-prompt: test 1 = 1 shell-prompt: echo $? 0 shell-prompt: test 1 = 2 shell-prompt: echo $? 1
The relational operators supported by test are shown in Table 2.5, “Test Command Relational Operators”.
Table 2.5. Test Command Relational Operators
Operator | Relation |
---|---|
= | Lexical equality (string comparison) |
-eq | Integer equality |
!= | Lexical inequality (string comparison) |
-ne | Integer inequality |
< | Lexical less-than (10 < 9) |
-lt | Integer less-than (9 -lt 10) |
-le | Integer less-than or equal |
> | Lexical greater-than |
-gt | Integer greater-than |
-ge | Integer greater-than or equal |
Note that some operators, such as < and >, have special meaning to the shell, so they must be escaped or quoted.
test 10 > 9 # Redirects output to a file called '9'. # The only argument sent to the test command is '10'. # The test command issues a usage message since it requires # more arguments. test 10 \> 9 # Compares 10 to 9. test 10 '>' 9 # Compares 10 to 9.
Common file tests are shown in Table 2.6, “Test command file operations”. To learn about additional file tests, run "man test".
Table 2.6. Test command file operations
Flag | Test |
---|---|
-e | Exists |
-r | Is readable |
-w | Is writable |
-x | Is executable |
-d | Is a directory |
-f | Is a regular file |
-L | Is a symbolic link |
-s | Exists and is not empty |
-z | Exists and is empty |
Variable references in a [ or test command should usually be enclosed in soft quotes. If the value of the variable contains white space, such as "navy blue", and the variable is not enclosed in quotes, then "navy" and "blue" will be considered two separate arguments to the [ command, and the [ command will fail. When [ sees "navy" as the first argument, it expects to see a relational operator as the second argument, but instead finds "blue", which is invalid.
Furthermore, if there is a chance that a variable used in a comparison is empty, then we must attach a common string to the arguments on both sides of the operator. It can be almost any character, but '0' is popular and easy to read.
name="" if [ "$name" = "Bob" ]; then # Error, expands to: if [ = Bob; then if [ 0"$name" = 0"Bob" ]; then # OK, expands to: if [ 0 = 0Bob ]; then
Relational operators are provided by the test command, not by the shell. Hence, to find out the details, we would run "man test" or "man [", not "man sh" or "man bash".
See Section 2.14.1, “Command Exit Status” for information about using the test command.
Run the following commands in sequence and run 'echo $?' after every test or [ command under bash and 'echo $status' after every test or [ command under tcsh.
bash test 1 = 1 test 1=2 test 1 = 2 [ 1 = 1 [ 1 = 1 ] [ 1 = '1' ] [1=1] [ 2 < 10 ] [ 2 = 3 ] [ 2 \< 10 ] [ 2 '<' 10 ] [ 2 -lt 10 ] [ $name = 'Bill' ] [ 0$name = 0'Bill' ] name='Bob' [ $name = 'Bill' ] [ $name = Bill ] [ $name = Bob ] exit tcsh [ $name = 'Bill' ] [ 0$name = 0'Bill' ] set name='Bob' [ $name = 'Bill' ] which [ exit
Unlike the Bourne shell family of shells, the C shell family implements its own operators, so there is generally no need for the test or [ command (although you can use it in C shell scripts if you really want to).
The C shell if statement requires () around the condition, and the condition is always a Boolean expression, just like in C and similar languages. As in C, and unlike Bourne shell, a value of zero is considered false and non-zero is true.
#!/bin/csh -ef printf "Enter the name of a color: " set color = "$<" if ( "$color" == "blue" ) then printf "You entered blue.\n" else if ( "$color" == "red" ) then printf "You entered red.\n" else printf "You did not enter blue or red.\n" endif
The C shell relational operators are shown in Table 2.7, “C Shell Relational Operators”.
Table 2.7. C Shell Relational Operators
Operator | Relation |
---|---|
< | Integer less-than |
> | Integer greater-than |
<= | Integer less-then or equal |
>= | Integer greater-than or equal |
== | String equality |
!= | String inequality |
=~ | String matches glob pattern |
!~ | String does not match glob pattern |
Conditions in a C shell if statement do not have to be relations, however. We can check the exit status of a command in a C shell if statement using the {} operator:
#!/bin/csh if ( { command } ) then command ... endif
The {} essentially inverts the exit status of the command. If command returns 0, the the value of { command } is 1, which means "true" to the if statement. If command returns a non-zero status, then the value of { command } is zero.
C shell if statements also need soft quotes around strings that contain white space. However, unlike the test command, it can handle empty strings, so we don't need to add an arbitrary prefix like '0' if the string may be empty.
if [ 0"$name" = 0"Bob" ]; then
if ( "$name" == "Bob" ) then
Type in the following commands in sequence:
tcsh if ( $first_name == Bob ) then printf 'Hi, Bob!\n' endif set first_name=Bob if ( $first_name == Bob ) then printf 'Hi, Bob!\n' endif exit
The shell's conditional operators allow us to alter the exit status of a command or utilize the exit status of each in a sequence of commands. They include the Boolean operators AND (&&), OR (||), and NOT (!).
shell-prompt: command1 && command2 shell-prompt: command1 || command2 shell-prompt: ! command
Table 2.8. Shell Conditional Operators
Operator | Meaning | Exit status |
---|---|---|
! command | NOT | 0 if command failed, 1 if it succeeded |
command1 && command2 | AND | 0 if both commands succeeded |
command1 || command2 | OR | 0 if either command succeeded |
Note that in the case of the && operator, command2 will not be executed if command 1 fails (exits with non-zero status), since it could not change the exit status. Once any command in an && sequence fails, the exit status of the whole sequence will be 1, so no more commands will be executed.
Likewise in the case of a || operator, once any command succeeds (exits with zero status), the remaining commands will not be executed.
This fact is often used to conditionally execute a command only if another command is successful:
pre-processing && main-processing && post-processing
When using the test or [ commands, multiple tests can be performed using either the shell's conditional operators or the test command's Boolean operators:
if [ 0"$first_name" = 0"Bob" ] && [ 0"$last_name" = 0"Newhart" ]; then if test 0"$first_name" = 0"Bob" && test 0"$last_name" = 0"Newhart"; then
if [ 0"$first_name" = 0"Bob" -a 0"$last_name" = 0"Newhart" ]; then if test 0"$first_name" = 0"Bob" -a 0"$last_name" = 0"Newhart"; then
The latter is probably more efficient, since it only executes a single [ command, but efficiency in shell scripts is basically a lost cause, so it's best to aim for readability instead. If you want speed, use a compiled language.
Conditional operators can also be used in a C shell if statement. Parenthesis are recommended around each relation for readability.
if ( ("$first_name" == "Bob") && ("$last_name" == "Newhart") ) then
Run the following commands in sequence and run 'echo $?' after every command under bash and 'echo $status' after every command under tcsh.
bash ls -z ls -z && echo Done ls -a && echo Done ls -z || echo Done ls -a || echo Done first_name=Bob last_name=Newhart if [ 0"$first_name" = 0"Bob" ] && [ 0"$last_name" = 0"Newhart" ] then printf 'Hi, Bob!\n' fi if [ 0"$first_name" = 0"Bob" -a 0"$last_name" = 0"Newhart" ] then printf 'Hi, Bob!\n' fi exit tcsh ls -z ls -z && echo Done ls -a && echo Done ls -z || echo Done ls -a || echo Done if ( $first_name == Bob && $last_name == Newhart ) then printf 'Hi, Bob!\n' endif set first_name=Bob set last_name=Nelson if ( $first_name == Bob && $last_name == Newhart ) then printf 'Hi, Bob!\n' endif set last_name=Newhart if ( $first_name == Bob && $last_name == Newhart ) then printf 'Hi, Bob!\n' endif exit
If you need to compare a single variable to many different values, you could use a long string of elifs or else ifs:
#!/bin/sh printf "Enter a color name: " read color if [ "$color" = "red" ] || \ [ "$color" = "orange" ]; then printf "Long wavelength\n" elif [ "$color" = "yello" ] || \ [ "$color" = "green" ] || \ [ "$color" = "blue" ]; then printf "Medium wavelength\n" elif [ "$color" = "indigo" ] || \ [ "$color" = "violet" ]; then printf "Short wavelength\n" else printf "Invalid color name: $color\n" fi
Like most languages, however, Unix shells offer a cleaner solution.
Bourne shell has the case statement:
#!/bin/sh printf "Enter a color name: " read color case $color in red|orange) printf "Long wavelength\n" ;; yellow|green|blue) printf "Medium wavelength\n" ;; indigo|violet) printf "Short wavelength\n" ;; *) printf "Invalid color name: $color\n" ;; esac
C shell has a switch statement that looks almost exactly like the switch statement in C, C++, and Java:
#!/bin/csh -ef printf "Enter a color name: " set color = "$<" switch($color) case red: case orange: printf "Long wavelength\n" breaksw case yellow: case green: case blue: printf "Medium wavelength\n" breaksw case indigo: case violet: printf "Short wavelength\n" breaksw default: printf "Invalid color name: $color\n" endsw
Write a Bourne shell script that inputs a first name and outputs a different message depending on whether the name is 'Bob'.
shell-prompt: ./script What is your name? Bob Hey, Bob! shell-prompt: ./script What is your name? Bill Hey, you're not Bob!
Write a Bourne and/or C shell script that inputs a person's age and indicates whether they get free peanuts. Peanuts are provided to senior citizens.
shell-prompt: ./script How old are you? 42 Sorry, no peanuts for you. shell-prompt: ./script How old are you? 72 Have some free peanuts, wise sir!
Write a Bourne and/or C shell script that inputs a person's age and indicates whether they get free peanuts. Peanuts are provided to senior citizens and children between the ages of 3 and 12.
shell-prompt: ./script How old are you? 42 Sorry, no peanuts for you. shell-prompt: ./script How old are you? 72 Have some free peanuts, wise sir!
shell-prompt: ./script FreeBSD is supported.
shell-prompt: ./script AIX is not supported.