The Bourne/bash shell supports a variety of conditionals, loops and other flow control operations. You’ll use these often.
if
The if statement is a simple conditional. Its syntax is:
if condition ; then commands [elif condition ; then commands]... [else commands] fi
This is an if-block, optionally followed by one or more elif-blocks (elif is short for “else if”), optionally followed by an else-block, and terminated by fi.
The if statement does what you’d expect: if the condition is true, it executes the if-block. Otherwise, it executes the else-block, if there is one. The elif construct lets you avoid nesting multiple if statements. For instance:
#!/bin/sh if [ $USER = root ]; then echo "Welcome to FooSoft 3.0" foosoft.sh else echo "You must be root to run this script" exit 666 fi
Notice the square brackets in the condition statement. Actually, only the left bracket is important, because it is aliased to the test command.
The condition can actually be any command. If it returns a zero exit status, the condition is true; otherwise, it is false. Thus, you can write things like:
#!/bin/sh user=arnie mytest=`grep $user /etc/passwd|wc -l` # Notice the backticks, not single quotes, above if [ $mytest -gt 0 ]; then echo "$user has an account" else echo "$user doesn't have an account" fi
Create a “password” file named passwords, with two columns: one holding a user name, and the second holding a user password. There should be at least three name/password pairs.
Use the script above to model a test: that the user at least exists in the password file. Name your script passcheck.sh.
Test and run it.
for
The for loop iterates over all of the elements in a list. Its syntax is
for var in list do commands done
The element list is zero or more words (elements). The for construct will assign the variable var to each word in turn, then execute commands. For example, the elements can be listed directly:
#!/bin/bash for x in red green blue do echo $x done
Which will return:
red green blue
Notice that spaces define individual strings. To deal with a string that contains spaces, enclose it in weak quotes (“).
#!/bin/bash for i in foo bar baz "do be do"; do echo "$i" done
This will print:
foo bar baz do be do
Note that if some of the elements will return with embedded spaces, you need to protect them with quotes.
#!/bin/bash color1="red chile" color2="green chile" color3="christmas" for x in "$color1" "$color2" "$color3" do echo "$x" done
Here’s the point: variables should be protected with quotes unless you are sure that their value does not contain any spaces.
The elements in a for loop need not be listed separately. They can instead be supplied by a command.
#!/bin/bash for x in $(ls) do echo "$x" done
A for loop may contain two special commands: break and continue. break exits the for loop immediately, jumping to the next statement after done. continue skips the rest of the body of the loop, and jumps back to the top, to for.
Consider:
#remove spaces in file names for i in *.wma; do mv "$i" `echo $i | tr ’ ’ ’_’`; done
See http://www.linux-mag.com/id/8797/ for an excellent example of for loop usage.
while
The while statement should also be familiar to you from any number of other programming languages. Its syntax in sh is
while condition do commands done
The while loop executes commands as long as the condition is true. Again, the condition can be any command, and is true if the command exits with a zero exit status (in other words, there wasn’t an error). Consider a simple mathematical example:
#!/bin/bash # set the variable x=0 # test: less than or equal to 20 while [ $x -le 20 ] # begin the actual loop do # notice that the do command could have been # on the previous line using the in-line return # character " ; " echo $x # increment x by 1 x=$(($x+1)) #loop done
A while loop may contain two special commands: break and continue.
break exits the while loop immediately, jumping to the next statement after done.
continue skips the rest of the body of the loop, and jumps back to the top, to condition
Using Command Output to Supply the Elements of a Loop
The elements in a for loop need not be listed separately. They can instead be supplied by a command. Try this loop:
#!/bin/bash for x in `ls` do echo $x done
- What did you get?
- Now substitute `who` for the `ls` command.
- Create a file listing the contents of your home directory:
ls ~ > files - How can you make this file supply the list for the for loop?
Globbing
An interesting thing happens when you include a filename wildcard character in a command. Remember that the shell “gets to” the wildcards first, and expands them. By default, wildcards (to the shell) are always references to filenames, unless some other comparison is specified. This is known as “globbing.” Globbing is used mainly in case and for statements.
The shell expands a string containing a * to all filenames that match. The character * by itself expands to a space -delimited list of all files in the working directory (excluding those that start with a dot “.” ).
When a glob begins with * or ?, it does not match files that begin with a dot. To match these, you need to specify the dot explicitly (e.g., .* or /tmp/.*). Under DOS, the pattern *.* matches every file. In sh, it matches every file that contains a dot.
So:
echo *
lists all the non-hidden files and directories in the current directory.
echo *.jpg
lists all the jpeg files.
echo ${HOME}/public_html/*.jpg
lists all jpeg files in your public_html directory.
As it happens, this turns out to be very useful for performing operations on the files in a directory, especially used in conjunction with a for loop. For example:
#!/bin/bash for x in ${HOME}/public_html/*.htm do grep -L '<bold>' "$x" done
case
The case command’s syntax is:
case value in pattern1) commands ;; #commands are executed until a double-semicolon is hit pattern2) commands ;; esac
value is a string; this is generally either a variable or a back quoted command.
Notice that a case block ends with esac.
pattern is a glob pattern (see globbing, above). For instance,
case pattern in gle*) command ;; glen*) command ;; fred) command ;; barney) command ;; esac
The patterns are evaluated in the order in which they occur, and only the first pattern that matches will be executed. To include a “none of the above” clause, use * as your last pattern.
case "$color" in blue) echo \$color is blue ;; green) echo \$color is green ;; red|orange) echo \$color is red or orange ;; *) echo "Not a match" ;; esac
The “|” is used to separate multiple patterns; it functions as a logical “or.”
echo -n "Enter the name of an animal: "
read ANIMAL
echo -n "The $ANIMAL has "
case $ANIMAL in
horse | dog | cat) echo -n "four";;
man | kangaroo ) echo -n "two";;
*) echo -n "an unknown number of";;
esac
echo "legs."