This section reviews some basic syntactic features of the Bourne shell, in a somewhat arbitrary order. Lines in shell scripts beginning with number signs are comments: # Start or stop the lp scheduler In fact, comments can begin anywhere on a line: grep ':00*:' /etc/passwd # Check for UID=0 accounts The first line of a shell script usually looks like this: #!/bin/sh This identifies the shell that should run the script, in this case, the Bourne shell. The path location can vary. NOTE
The best practice is to begin every shell script with a line identifying the shell to be used to run it. If this line is not present, /bin/sh is assumed. The Bourne shell offers some syntactic flexibility over other shells. For example, quotes remain in effect across physical lines, as in this example we looked at in Chapter 7: echo "*** Non-root UID=0 or GID=0 accounts:" grep ':00*:' /etc/passwd | \ awk -F: 'BEGIN {n=0} $1!="root" {print $0 ; n=1} END {if (n==0) print "None found."}' Note that the arguments to the awk command extend across three lines, which is much more readable than forcing them onto a single line. A.1.1 I/O RedirectionAnother construct you'll see quite often is this redirection of standard output to a file and of standard error to standard output (and thus to the same file): /usr/lib/lpshut > /dev/null 2>&1 In this case the file is /dev/null, but the concept applies whether output goes to a real disk file, to /dev/console, or gets thrown out. Note that standard output and error can also be redirected to separate destinations: /sbin/rc.local 1>> boot.log 2> /dev/console In general, the form n> file redirects file descriptor n to the specified file; file may also be replaced by a second file description, as in the form n1>&n2. Some Bourne shells and bash support more complex I/O-redirection syntax. For example, the following command redirects all future standard input and all output to the system console (which is the target of the CONSOLE environment variable): exec 0<> $CONSOLE 1>&0 2>&0 bash also offers additional I/O-redirection features. One of the most useful is illustrated in this example: /etc/shutdown.local >| /var/adm/shutdown.log 2>&1 This command runs the specified script, placing all of its output into the indicated file even if the file already exists and the noclobber shell variable, which inhibits accidental overwriting of existing files, is set. A.1.2 The dot CommandThe so-called dot command consisting of a single period is used to run commands from a file in the same shell as the script itself. The file specified as a dot command's argument thus functions as an include file. For example, the following command executes the contents of /etc/rc.config as if they were part of the calling script: . /etc/rc.config Placing some commands in a separate file can have many purposes: isolating their function, allowing them to be used in multiple scripts, and so on. bash provides source as a synonym for the dot command. The return command may be used to return to the calling script at any point within a script executed with the dot command. A.1.3 Return Codes and the exit CommandOn Unix systems, commands return a value of zero when they terminate normally and a nonzero value when they don't. The exit command may be used in scripts to return an explicit value; it takes the return value as its argument. Here is a typical use of exit: echo "configure network FAILED" exit 1 This command, from a TCP/IP startup file, terminates the script and returns a non-zero value (indicating an error). A.1.4 Compound CommandsThe forms && and || are used to create conditional compound commands. When the shell encounters one of these operators, it checks the exit value of the command on the left of the operator before deciding whether to execute the command on the right. For &&, the second command is executed only if the first one completed successfully; for ||, the second command executes when the first one fails. Here is an example with &&: grep chavez /etc/passwd && grep chavez /etc/group If the string "chavez" is found in the password file, the same string is searched for in the group file; if it isn't found, the second command doesn't execute. The two constructs can be used together: /usr/local/cksecret && echo "Everything ok." || mail root < slog If the script cksecret returns 0, a message is sent to standard output; otherwise, the contents of the slog file are mailed to root. The && has to come before the || for this to work correctly. A.1.5 Command SubstitutionBack quotes may be used to place the output of one command into a separate command. For example, this command defines the variable otty as the output of the stty command: otty=`stty -g` bash and some Bourne shells also support the following more readable syntax: otty=$(stty -g) A.1.6 Argument Symbols and Other $ AbbreviationsBourne shell scripts can be passed arguments like any Unix command. The first nine arguments can be referred to by the abbreviations $1 through $9. The shift command is one way to access later arguments. Here is an example of how it works: $ cat show_shift #!/bin/sh echo $1 $2 $3 shift echo $1 $2 $3 $ show_shift a1 a2 a3 a4 a1 a2 a3 a2 a3 a4 After the shift command, all parameters are shifted one parameter position to the left (or down, depending on how you want to look at it), and their parameter numbers are reduced by one. bash provides a simplified syntax for accessing arguments beyond the ninth: ${n}. Thus, echo ${12} would display the value of the twelfth argument. $0 refers to the command or script name, as in this example: restart) $0 stop && $0 start ;; These lines are from a boot script. They are part of a case statement in which the various options correspond to possible arguments that may be passed to the script. In this case, when the script argument is "restart", it calls itself with the argument "stop" and then calls itself again with the argument "start", provided that the first command was successful. The form $# is a shorthand for the number of arguments. Thus, for the show_shift command in the previous example, $# was 4 before the shift command was executed and 3 afterwards. There are two shorthand forms for all the arguments passed to a script: $@ and $*. $@ keeps the individual arguments as separate entities; $* merges them into a single item. Quoting the two of them illustrates this clearly: "$*" = "$1 $2 $3 $4 ... $n" "$@" = "$1" "$2" "$3" "$4" ... "$n" You'll usually see the $@ form in system scripts. There are a few other dollar-sign abbreviations that appear from time to time. Although they're not related to script arguments, I'll list them here:
We'll see examples of some of these later in this appendix. A.1.7 Variable SubstitutionShell scripts can also define variables, using the same syntax as environment variables: name=value No spaces allowed around the = sign. Variables are dereferenced by putting a dollar sign in front of their name: $name. The variable name may be surrounded with braces to protect it from surrounding text. For example: $ cat braces #!/bin/sh item=aaaa item1=bbbb echo $item1 ${item}1 $ braces bbbb aaaa1 The first command displays the value of the variable item1, while the second command displays the value of the variable item, followed by a 1. There are more complex ways of conditionally substituting variable values. They are summarized in Table A-1.
Here are some examples: $ name=rachel Assign value to variable name. $ echo ${name-tatiana} name is set, so use its value. rachel $ echo ${name2-tatiana} name2 is unset, so use "tatiana". tatiana $ echo ${name=tatiana} name is set, so use it. rachel $ echo ${n2=tatiana}; echo $n2 n2 is unset, so use "tatiana" . . . tatiana tatiana . . . and give n2 that value too: $ echo ${name+tatiana} name is set, so use "tatiana". tatiana $ echo name3=${name3+tatiana} name3 is unset, so return nothing. name3= $ name4=${name3?"no name given"} name3 is unset, so display message . . . name3: no name given $ echo name4=$name4 . . . note name4 is not set. name4= $ dir=${name-`pwd`}; echo $dir name is set, so use it (pwd not run). rachel $ dir=${name3-`pwd`}; echo $dir name3 is unset, so set dir to `pwd`. /home/chavez As the final two examples indicate, commands can be included in the string, and they will be executed only if that portion of the construct is actually used. A.1.7.1 bash variable substitution extensionsThe bash shell and some Bourne shell implementations provide additional variable substitution possibilities:
A.1.8 Variable Double DereferencingIt's very common to come across code like this: netdev="NETDEV_" iconfig="IFCONFIG_" # Set up environment variables for each network device . /etc/rc.config num=0 while [ $num -le $NUM_NETDEVS ]; do curr_dev=`eval echo $netdev$num` # NETDEV_n eval device=\$$curr_dev # value of NETDEV_n if [ "$device" != '' ]; then curr_opts=`eval echo $iconfig$num` # IFCONFIG_n eval options=\$$curr_opts # value of IFCONFIG_n /sbin/ifconfig $device $options up fi num=`expr $num + 1` done This script fragment initializes all the network interfaces on a system. The device names are stored in a series of environment variables named NETDEV_0, NETDEV_1, and so on, and the corresponding ifconfig options are stored in IFCONFIG_n. The while loop configures each interface in turn. The variable num holds the number of the current interface, and the variables netdev and iconfig hold the beginning part of the environment variable names. The value of the proper environment variable is extracted into the variables device and options (which are used in the ifconfig command) via a two-step process: for the NETDEV case, the name of the environment variable is constructed first and saved in the variable curr_dev. Then curr_dev is itself dereferenced, and its value which is the value stored in NETDEV_n is assigned to the variable device. If you've ever wondered how to get to the value of the value of variable, this is one way. Here is a similar example from a Linux system: . /etc/rc.config locale_vars="\ LANG \ LC_ALL \ ... \ LC_MONETARY" for var in $locale_vars; do Loop over locale-related environment variables. if eval test -z "\$$var" Is the variable's value undefined or null? then eval $var="\$RC_$var" If so, set its value to the same RC_ variable. export $var fi done Consider the first trip through the loop. The loop variable var is set to LANG. If the LANG environment variable is not set, then LANG's value is set to that of RC_LANG variable (defined in /etc/rc.config) via the second eval command, and the environment variable is exported. |