13.7 Understanding Shell ScriptsThis section explains how more advanced shell scripts work. The information is also adequate to equip you to write many of your own useful shell scripts. The section begins by showing how to process a script's arguments. Then it shows how to perform conditional and iterative operations. 13.7.1 Processing ArgumentsYou can easily write scripts that process arguments, because a set of special shell variables holds the values of arguments specified when your script is invoked. Table 13-6 describes the most popular such shell variables.
For example, here's a simple one-line script that prints the value of its second argument: echo My second argument has the value $2. Suppose you store this script in the file second, change its access mode to permit execution, and invoke it as follows: $ ./second a b c The script will print the output: My second argument has the value b. Notice that the shell provides variables for accessing only nine arguments. Nevertheless, you can access more than nine arguments. The key to doing so is the shift command, which discards the value of the first argument and shifts the remaining values down one position. Thus, after executing the shift command, the shell variable $9 contains the value of the 10th argument. To access the 11th and subsequent arguments, you simply execute the shift command the appropriate number of times. 13.7.2 Exit CodesThe shell variable $? holds the numeric exit status of the most recently completed command. By convention, an exit status of zero denotes successful completion; other values denote error conditions of various sorts.You can set the error code in a script by issuing the exit command, which terminates the script and posts the specified exit status. The format of the command is: $ exit status where status is a nonnegative integer that specifies the exit status. 13.7.3 Conditional LogicA shell script can employ conditional logic, which lets the script take different action based on the values of arguments, shell variables, or other conditions. The test command lets you specify a condition, which can be either true or false. Conditional commands (including the if, case, while, and until commands) use the test command to evaluate conditions. 13.7.3.1 The test commandTable 13-7 describes some argument forms commonly used with the test command. The test command evaluates its arguments and sets the exit status to zero, which indicates that the specified condition was true, or a nonzero value, which indicates that the specified condition was false.
To see the test command in action, consider the following script: test -d $1 echo $? This script tests whether its first argument specifies a directory and displays the resulting exit status, a zero or a nonzero value that reflects the result of the test. If the script was stored in the file tester, which permitted execute access, executing the script might yield results similar to the following: $ ./tester / 0 $ ./tester /missing 1 These results indicate that the root directory (/) exists and that the /missing directory does not. 13.7.3.2 The if commandThe test command is not of much use by itself, but combined with commands such as the if command, it is useful indeed. The if command has the following form: if command then commands else commands fi The command that usually follows if is a test command. However, this need not be so. The if command merely executes the specified command and tests its exit status. If the exit status is zero, the first set of commands is executed; otherwise, the second set of commands is executed. An abbreviated form of the if command does nothing if the specified condition is false: if command then commands fi When you type an if command, it occupies several lines; nevertheless, it's considered a single command. To underscore this, the shell provides a special prompt, called the secondary prompt, after you enter each line. You won't see the secondary prompt when entering a script using a text editor, or any other shell prompt for that matter. As an example, suppose you want to delete a file, file1, if it's older than another file, file2. The following command would accomplish the desired result: if test file1 -ot file2 then rm file1 fi You could incorporate this command in a script that accepts arguments specifying the filenames: if test $1 -ot $2 then rm $1 echo Deleted the old file. fi If you name the script riddance and invoke it as follows: $ riddance thursday wednesday the script will delete the thursday file if that file is older than the wednesday file. 13.7.3.3 The case commandThe case command provides a more sophisticated form of conditional processing: case value in pattern1 ) commands ;; pattern2 ) commands ;; ... esac The case command attempts to match the specified value against a series of patterns. The commands associated with the first matching pattern, if any, are executed. Patterns are built using characters and metacharacters, such as those used to specify command arguments. As an example, here's a case command that interprets the value of the first argument of its script: case $1 in -r) echo Force deletion without confirmation ;; -i) echo Confirm before deleting ;; *) echo Unknown argument ;; esac The command echoes a different line of text, depending on the value of the script's first argument. As done here, it's good practice to include a final pattern that matches any value. 13.7.3.4 The while commandThe while command lets you execute a series of commands iteratively (that is, repeatedly) so long as a condition tests true: while command do commands done Here's a script that uses a while command to print its arguments on successive lines: echo $1 while shift 2> /dev/null do echo $1 done Notice how the 2> operator is used to direct error messages to the device /dev/null, which prevents them from being seen. You can omit this operator if you prefer. The commands that comprise the do part of a while (or any other loop command) can include if, case, and even other while commands. However, scripts rapidly become difficult to understand when this occurs often. You should include conditional commands within other conditional commands only with due consideration for the clarity of the result. Don't forget to include comments in your scripts (with each commented line beginning with a #) to clarify difficult constructs. 13.7.3.5 The until commandThe until command lets you execute a series of commands iteratively (that is, repeatedly) so long as a condition tests false: until command do commands done Here's a script that uses an until command to print its arguments on successive lines, until it encounters an argument that has the value red: until test $1 = red do echo $1 shift done For example, if the script were named stopandgo and stored in the current working directory, the command: $ ./stopandgo green yellow red blue would print the lines: green yellow 13.7.3.6 The for commandThe for command iterates over the elements of a specified list: for variable in list do commands done Within the commands, you can reference the current element of the list by means of the shell variable $variable, where variable is the name specified following the for. The list typically takes the form of a series of arguments, which can incorporate metacharacters. For example, the following for command: for i in 2 4 6 8 do echo $i done prints the numbers 2, 4, 6, and 8 on successive lines. A special form of the for command iterates over the arguments of a script: for variable do commands done For example, the following script prints its arguments on successive lines: for i do echo $i done 13.7.3.7 The break and continue commandsThe break and continue commands are simple commands that take no arguments. When the shell encounters a break command, it immediately exits the body of the enclosing loop (while, until, or for) command. When the shell encounters a continue command, it immediately discontinues the current iteration of the loop. If the loop condition permits, other iterations may occur; otherwise the loop is exited. 13.7.4 Periscope: A Useful Networking ScriptSuppose you have a free email account such as that provided by Yahoo! You're traveling and find yourself in a remote location with web access. However, you're unable to access files on your home machine or check email that has arrived there. This is a common circumstance, especially if your business requires that you travel. If your home computer runs Windows, you're pretty much out of luck. You'll find it extraordinarily difficult to access your home computer from afar. However, if your home computer runs Linux, gaining access is practically a piece of cake. In order to show the power of shell scripts, this subsection explains a more complex shell script, periscope. At an appointed time each day, periscope causes your computer (which you must leave powered on) to establish a PPP connection to your ISP, which is maintained for about one hour. This provides you enough time to connect to an ISP from your hotel room or other remote location and then connect via the Internet with your home Linux system, avoiding long-distance charges. Once connected, you have about an hour to view or download mail and perform other work. Then, periscope breaks its PPP connection, which it will reestablish at the appointed time the next day. The following code shows the periscope script file, which is considerably larger than any script you've so far encountered in this chapter. Therefore, we'll disassemble the script, explaining it line by line. As you'll see, each line is fairly simple in itself, and the lines work together in a straightforward fashion. 1 route del default 2 wvdial & 3 sleep 1m 4 ifconfig | mail username@mail.com 5 sleep 1h 6 killall wvdial 7 sleep 2s 8 killall -9 wvdial 9 killall pppd 10 sleep 2s 11 killall -9 pppd 12 echo "/root/periscope" | at 10:00 Here's the line-by-line analysis of the periscope script:
To try the script for yourself, you must have installed the wvdial program, as explained in Chapter 10. Place the script in the file /root/periscope. Of course, you'll probably want to customize the script to specify an appointment time and duration of your own choosing. To start periscope, log in as root and issue the command: # (echo "/root/periscope" | at 10:00)& The parentheses cause the & operator to apply to the entire command, not just the at. When 10:00 a.m. (or any other time you specify) comes around, your Linux system should obediently dial your ISP and maintain the connection for the specified interval of time. 13.7.5 Using PeriscopeAt the appointed time, fire up your computer and access your email account. You should find a mail message that contains the ifconfig output giving your computer's current IP address. Now you can use telnet or an ssh client—your choice corresponds to the server you're running on your Linux system—to contact your computer and work for the remainder of the specified connection time. At the end of the connection time, your Linux system will sever its PPP connection and begin counting down until it's again time to connect. |