The expr program can be used to manipulate variables, normally interpreted as strings, as integers. Consider the following "adder" script:
sum=`expr $1 + $2` printf "%s + %s = %s\n" $1 $2 $sum
A Few Other Special Variables
We'll talk a bit more about these as we get into more complex examples. For now, I'd just like to mention them:
- $? - the exit status of the last program to exit
- $$ - The shell's pid
The convention among UNIX programmers is that programs should return a 0 upon success. Typically a non-0 value indicates that the program couldn't do what was requested. Some (but not all) programmers return a negative number upon an error, such as file not found, and a positive number upon some other terminal condition, such as the user choosing to abort the request.
As a result, the shell notion of true and false is a bit backward from what most of us might expect. 0 is considered to be true and non-0 is considered to be false.
We can use the test to evaluate an expression. The following example will print 0 if gkesden is the user and 1 otherwise. It illustrates not only the test but also the use of the status variable. status is automatically set to the exit value of the most recently exited program. The notation $var, such as $test, evaluates the variable.
test "$LOGNAME" = gkesden echo $?
Shell scripting languages are typeless. By default everything is interpreted as a string. So, when using variables, we need to specify how we want them to be interpreted. So, the operators we use vary with how we want the data interpreted.
Operators for strings, ints, and files string x = y, comparison: equal x != y, comparison: not equal x, not null/not 0 length -n x, is null ints x -eq y, equal x -ge y, greater or equal x -le y, lesser or equal x -gt y, strictly greater x -lt y, strictly lesser x -ne y, not equal file -f x, is a regular file -d x, is a directory -r x, is readable by this script -w x, is writeable by this script -x x, is executible by this script logical x -a y, logical and, like && in C (0 is true, though) x -o y, logical or, like && in C (0 is true, though)
[ Making the Common Case Convenient ]
We've looked at expressions evaluated as below:
test -f somefile.txt
Although this form is the canonical technique for evaluating an expression, the shorthand, as shown below, is universally supported -- and much more reasonable to read:
[ -f somefile.txt ]
You can think of the  operator as a form of the test command. But, one very important note -- there must be a space to the inside of each of the brackets. This is easy to forget or mistype. But, it is quite critical.
Like most programming languages, shell script supports the if statement, with or without an else. The general form is below:
if command then command command ... command else command command ... command fi
if command then command command ... command fi
The command used as the predicate can be any program or expression. The results are evaluated with a 0 return being true and a non-0 return being false.
If ever there is the need for an empty if-block, the null command, a :, can be used in place fo a command to keep the syntax legal.
The following is a nice, quick example of an if-else:
if [ "$LOGNAME" = "gkesden" ] then printf "%s is logged in" $LOGNAME else printf "Intruder! Intruder!" fi
The elif construct
Shell scripting also has another construct that is very helpful in reducing deep nesting. It is unfamilar to those of us who come from languages like C and Perl. It is the elif, the "else if". This probably made its way into shell scripting because it drastically reduces the nesting that would otherwise result from the many special cases that real-world situatins present -- without functions to hide complexity (shell does have functions, but not parameters -- and they are more frequently used by csh shell scripters than traniditonalists).
if command command command ... command then command command ... command elif command then command command ... command elif command then command command ... command fi
The switch statement
Much like C, C++, or Java, shell has a case/swithc statement. The form is as follows:
case var in pat) command command ... command ;; # Two ;;'s serve as the break pat) command command ... command ;; # Two ;;'s serve as the break pat) command command ... command ;; # Two ;;'s serve as the break esac
Here's a quick example:
#!/bin/sh echo $1 case "$1" in "+") ans=`expr $2 + $3` printf "%d %s %d = %d\n" $2 $1 $3 $ans ;; "-") ans=`expr $2 - $3` printf "%d %s %d = %d\n" $2 $1 $3 $ans ;; "\*") ans=`expr "$2 * $3"` printf "%d %s %d = %d\n" $2 $1 $3 $ans ;; "/") ans=`expr $2 / $3` printf "%d %s %d = %d\n" $2 $1 $3 $ans ;; # Notice this: the default case is a simple * *) printf "Don't know how to do that.\n" ;;
The for Loop
The for loop provides a tool for processing a list of input. The input to the for loop is a list of values. Each trip through the loop it extracts one value into a varible and then enters the body of the loop. the loop stops when the extract fails because there are no more values in the list.
Let's consider the following example which prints each of the command line arguments, one at a time. We'll extract them from "$@" into $arg:
for var in "$@" do printf "%s\n" $var done
Much like C or Java, shell has a break command, also. As you might guess, it can be used to break out of a loop. Consider this example which stops printing command line arguments, when it gets to one whose value is "quit":
for var in "$@" do if [ "$var" = "quit" ] then break fi printf "%s\n" $var done
Similarly, shell has a continue that works just like it does in C or Java. This one can be used to censor me!
for var in "$@" do if [ "$var" = "shit" ] then continue elif [ "$var" = "fuck" ] then continue elif [ "$var" = "damn" ] then continue fi if [ "$var" = "quit" ] then break fi printf "%s\n" $var done
The while and until Loops
Shell has a while loop similar to that seen in C or Java. It continues until the predicate is false. And, like the other loops within shell, break and continue can be used. Here's an example of a simple while loop:
# This lists the files in a directory in alphabetical order # It continues until the read fails because it has reached the end of input ls | sort | while read file do echo $file done
There is a similar loop, the until loop that continues until the condition is successful -- in other words, while the command failes. This will pound the user for input until it gets it:
printf "ANSWER ME! " until read $answer do printf "ANSWER ME! " done