![]() |
| |||||||
|
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
The 'if' statement was discussed in Part 8 (Shell Scripting 1). In addition to the simple 'if-fi' and 'if-else-fi' variants, one is able to specify any number of alternatives. For example: $ cat ifelif #!/bin/sh Here there are four alternatives; thought there is no (sensible) upper limit. The first alternative is selected when the first 'if' condition is TRUE. If the first condition is FALSE, the second 'elif' condition (short for else if) is tried, and so on. If no conditions are TRUE the 'else' part is executed. Running this script we get: $ ./ifelifType a letter: c case c $ ./ifelif Type a letter: a case a $ ./ifelif Type a letter: z default case $ ./ifelif Type a letter: b case b ...which I guess is self-explanatory So far, the conditions I have given to 'if' and 'elif' have been simple equality tests on strings. One can test strings, numeric values, and files. Here are some other conditions you can use. I am assuming the existence of shell variables num1 and num2. It helps if these contain strings that can be interpreted as integer numbers such as "123", not "abc". if [ $1 != $2 ]; thenTRUE if the parameter strings are not the same if [ $num1 -eq $num2 ]; then TRUE if the variables are numerically equal if [ $num1 -le $num2 ]; then TRUE if num1 is less than or equal to num2 if [ -e filename ]; then TRUE if the specified file exists (the filename may include a pathname too) if [ -d filename ]; then TRUE if the files exists and is a directory There are lots more conditions. They can be viewed with: $ man testRead this fully. It shows all manner of tests, particularly on files, and is invaluable when writing scripts. See the sidebar for an explanation of 'test condition' versus '[ condition ]'. |
Tell Me More...
|
|
[ and 'test' Have a look in /bin and you find an odd entry that is simply '['. You will also see a command called 'test' Now try this: $ test 1 = 2$ echo $? 1 $ test 1 = 1 $ echo $? 0 The test command evaluates the expression given to it, and returns TRUE (0) or FALSE (1). To obtain the return value of the last command executed use the shell special variable $?. This variable can also be checked in shell scripts after calling an external command. Now look at this simple shell script: $ cat bkt #!/bin/sh The script executes the command 'test "$1" = ""' (after the shell has expanded $1) and the 'if' statement is tests the result of this (TRUE/FALSE). This is exactly equivalent to the more usual: if [ "$1" = "" ]; thenexcept that the command '[' is executed instead of test. '[' additionally looks for a matching ']' and discards it. 'test' and '[' are otherwise equivalent. |
The example I gave above using if-elif-elif-else-fi would have been better done using a 'case-esac' control construct. The two constructs are very similar, and the case equivalent looks like this:
% cat case #!/bin/sh
read -p "Type a letter: " l
case "$l" in "a") echo "case a" ;; "b") echo "case b" ;; "c") echo "case c" ;; *) echo "None of A, B, C" echo "This is the default case." ;; esac
Executing the script gives:
$ ./case'Case' works like this:
case "$l" incompares the value of variable 'l' (in this example) against each of the cases listed below it - "a", "b", and "c" (in this example).
The alternatives are tried in order, and for the first that matches the statements between the closing bracket and ';;' are executed. You may place any statement, and any number of statements, between ')' and ';;'. When ';;' is reached, execution of the case statement completes and continues from the statement following 'esac'. ('esac' is case backwards, in the same vein as the if-fi pair.)
The special alternative '*)' is a catch-all and behaves just like the 'else' part of an 'if' statement.
Note that:
"a") echo "case a" ;;
can be written as:
"a") echo "case a" ;;The former is preferred if the case contains more than one statement like:
*) echo "None of A, B, C" echo "This is the default case." ;;
When to use 'if' and when to use 'case'
The example I have presented is better coded as a 'case' statement: it is simpler to follow that way. However, 'case' is limited in that each alternative must compare the variable given in:
case $var infor equality to the variable or constant given in each alternative:
"value") statement statement ;;
An 'if' statement is more powerful as each condition ('if' and 'elif') is independent. The following example looks quite complex, but in actuality is quite easy to follow through. It makes use of more 'test' conditions, and '$#' as representing the number of parameters passed to the script. In particular, I have used a nested condition - an 'if' statement within another 'if' statement. (In fact any condition can be nested within any another condition.)
% cat ifelif2 #!/bin/sh
# Check if the wrong number of parameters has been given, and if so issue # a usage message. We require 1 or 2 parameters, and choose to check # for an error by testing for greater than two or equal to 0. if [ "$#" -gt "2" ]; then echo "Usage: $0 filename [filetype]" echo "Too many parameters" elif [ "$#" = "0" ]; then echo "Usage: $0 filename [filetype]" echo "Too few parameters"
# At this point, we have the correct number of parameters (1 or 2). # If two parameters were given we test if the file specified by the # first parameter is of the type specified by the second parameter. elif [ "$#" = "2" ]; then if [ -$2 $1 ]; then echo "Yes, $1 is of type $2" else echo "No, $1 isn't of type $2" fi
# If just one parameter has been given, we simply report whether the file specified # by the parameter is of type file, directory, or something else. elif [ -f $1 ]; then echo "$1 is a file" elif [ -d $1 ]; then echo "$1 is a directory" else echo "$1 is something other than a file or directory" fi
Executing this:
$ ./ifelif2The last two executions show that the script could do with some improvements.
|
A loop is a control construct that goes round and round, either forever, or more usually until a condition is met. The Bourne shell provides three kinds of loop: for - loop n times to process a list of n valueswhile - loop continually while a condition is true until - loop continually until a condition is true 'while' and 'until' are similar, but reverse the termination condition. Here is a very simple 'for' loop: $ cat for1 #!/bin/sh Executing script 'for1' gives: $ ./for1The value of var is now one This is iteration number 1 The value of var is now two This is iteration number 2 The value of var is now three This is iteration number 3 The value of var is now a This is iteration number 4 The value of var is now b This is iteration number 5 The value of var is now c This is iteration number 6 The loop was executed six times, once for each value listed in: for var in one two three a b cEach iteration, the variable specified in the 'for' loop (in this case 'var') takes on the next value in the list (in this case 'one two three a b c'). When the list is exhausted the 'for' loop completes. A 'for' loop is more useful when the list is generated dynamically, for example when it is the output of a program or expanded by the shell. This next example prints the type of each file in the current directory. $ cat for2 #!/bin/sh for var in * do if [ -d "$var" ]; then echo "$var - directory" elif [ -f "$var" ]; then echo "$var - file" else echo "$var - dunno" fi done The list is "*", which of course the shell expands to all files in the current directory. Alternatively, for var in $*will generate a list containing all the parameters passed to the script, and: for var in $(command)will generate a list containing the output generated by 'command'. Note that the list is space-separated. Here is a simple while loop: $ cat while1 #!/bin/sh The loop comprises the statements between 'do' and 'done'. They are executed while the condition given in: while [ "$fn" != "" ]remains TRUE. A 'while' condition is formed exactly as for an 'if' or 'elif' condition. In this example, the loop executes while the input filename is not empty. Note that two 'read' statements are required, one before the loop and one within the loop. Running the script gives: $ ./while1Give a filename: xzy File xzy does not exist Give a filename: while1 while1: Bourne shell script text Give a filename: Bye. The last request for a filename is responded to with a press of the return key, resulting in an empty string being assigned to fn, and thus the 'while' condition being FLASE. At this point the loop completes. Note the use of command 'file', which attempts (with a little /etc/magic) to identify the type of the file passed to it. An 'until' loop simply uses the keyword 'until' instead of 'while', and as one might guess from the linguistic sense of the construct, the condition for exiting the loop is reversed. Here is the above example written as an 'until' loop: $ cat until1 #!/bin/sh Executing it: $ ./until1Give a filename: hello File hello does not exist Give a filename: until1 until1: Bourne shell script text Give a filename: while1 while1: Bourne shell script text Give a filename: Bye. |
Tell Me More...
|
|
Adding Up Note the statement: count=$(expr $count + 1)in the script 'for1' The command 'expr' evaluates the expression given to it, in this case $count (which will be expanded by the shell before being passed to 'eval') plus 1. The output from the command is assigned back to 'count' (overwriting its previous value) using the $(command) technique to execute 'command' and place its output into a variable. See Shell Scripting 1. $ man evalwill reveal the power of 'eval'. Some 'for' list tricks Here are some variants of the 'for' statement from the script 'for2': 1) filenames with spaces Suppose we have a file called: "file with space"The 'for' loop: for var in *will correctly produce: file with space - filewhereas the supposedly equivalent: for var in $(ls)will return: file - dunnowith - dunno space - dunno This is because the shell expands "*" itself and understands which spaces are part of the filename, and which separate elements of the 'for' list. Using '$(ls)', the necessary information is lost because the shell sees only the output from the 'ls' command. 2) parameters with spaces The special parameter '$@' is similar to '$*', but is useful when input parameters might contain spaces. This is most easily illustrated by an example. The script: $ cat for3 #!/bin/sh will behave as below. Note that the third parameter: "three four"contains a space and is quoted to indicate that it is supposed to be a single parameter. Note also the heavy use of escaping in the 'echo' commands. $ ./for3 one two "three four"Using $* one two three four Using "$*" one two three four Using $@ one two three four Using "$@" one two three four "$@" is often the correct expression to use. |
Next Part
In the next episode, I will continue the shell scripting tutorial, covering more complex conditions, nesting, more on loops, shell functions, and other such goodies.
Meanwhile, check out the Daily Unix Tips.
Week 54 gives comparisons between sh and tcsh.
Week 56 gives some more scripting tips.
Until then, enjoy :-)
Discuss this article in the Learning Center forum
|
|
Part 9 - Shell Scripting 2 (Page 2 of 2) |
|
| Copyright © 2000-2010 Inside Mac Media, Inc. All rights reserved. | ||
| Apple assumes no responsibility with regard to the selection, performance, or use of the products or services. All understandings, agreements, or warranties, if any, take place directly between the vendors and prospective users. | ||
| Apple, the Apple logo, Mac, PowerMac G4, PowerMac G5, Xserve, Xserve RAID, PowerBook, iBook, Airport, AirPort Extreme, iMac, eMac, iLife, iMovie, iCal, iPhoto, iTunes, QuickTime, FireWire, iPod, iSight, AppleWorks, Macintosh, Jaguar, Panther, Mac OS, Mac OS X and Mac OS X Server are trademarks of Apple Computer, Inc. |