A.1 Basic Syntax


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

figs/armadillo_tip.gif

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 Redirection

Another 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 Command

The 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 Command

On 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 Commands

The 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 Substitution

Back 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 $ Abbreviations

Bourne 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:

$?

Exit status of previous command

$$

PID of this shell's process

$!

PID of the most recently started background job.

We'll see examples of some of these later in this appendix.

A.1.7 Variable Substitution

Shell 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.

Table A-1. Conditional variable substitution
 

Return Value (Action Taken)

Form

If var is set[3]

If var is unset

${var-string}

$var

string

${var+string}

string

null

${var=string}

$var

string (set var=string)

${var?string}

$var

(display var:string only)

[3] "Set" means "defined," regardless of value (i.e., even if null).

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 extensions

The bash shell and some Bourne shell implementations provide additional variable substitution possibilities:

  • Placing a colon before the operator character in the various items in Table A-1 tests whether the variable is set to a non-null value. Thus, echo ${var:-apple} displays the value of variable var if it is set to something other than an empty string (null value); it displays "apple" otherwise.

  • The form ${var:offset:length} may be used to extract substrings from a variable. offset indicates which character to start with (numbering begins at 0); if offset is negative, character counting begins from the end of the string (e.g., -1 starts extracting at the penultimate character). length indicates how many characters to extract, and it is optional; if it is omitted, all remaining characters are extracted. It must be greater than zero.

    Here are some examples:

    $ names="applepearplum" $ echo ${names:5} pearplum $ echo ${names:5:4} pear $ echo ${names:(-4):4}      Negative numbers must be parenthesized to plum                           avoid confusion with the :- operator.
  • The form ${#var} may be used to determine the length of the specified variable's value. For example, ${#names} is 13.

  • The form ${var#pattern} may be used to remove substrings from a variable, returning the remaining string. The following commands illustrate its use:

    $ names="applepearplum" $ echo ${names#apple} pearplum $ echo ${names#a*p}       The pattern can include wildcards.   plepearplum $ echo ${names#pear}      Patterns only match the beginning of the string. applepearplum

    Note that the pattern must match the beginning of the string.

    There are several variations on this form: ${var##pattern}, ${var%pattern} and ${var%%pattern}. The number sign says to match the beginning of the string, and the percent sign says to match the end of the string. The single character forms remove the shortest matching substring, and the double character forms remove the longest matching substring, as in these examples:

    $ echo ${names##a*p}    Remove longest match.   lum $ echo ${names%%e*m} appl

    Here is a real-world example:

    rex="[0-9][0-9]" for i in $prerc/K${rex}*; do   service=${i#*/K$rex}           # extract service name   ... done

    This loop runs over the K-file boot scripts in whatever directory prerc resolves to. For each script, the variable service is set to the name of the facility being started by removing the initial path and Knn portion from the variable i.

  • The preceding syntax can be extended to perform general search-and-replace operations within strings, using constructs of the form: ${var/pattern/repstr}. This form replaces the longest string matching the pattern with repstr. If the initial slash is replaced by two slashes, all matching substrings are replaced. If repstr is null, the matching substrings are simply deleted.

    By default, matching occurs anywhere within the string. Precede the pattern with a number sign or percent sign to force matches to be at the beginning/end of the string.

    Here are some examples:

    $ names="applepearplum" $ echo ${names/p/X} aXplepearplum $ echo ${names//p/X} aXXleXearXlum $ echo ${names/%plum/kumquat} applepearkumquat

    You can do such pattern matching and replacement on the script argument list by using @ as the variable name.

A.1.8 Variable Double Dereferencing

It'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.



Essential System Administration
Essential System Administration, Third Edition
ISBN: 0596003439
EAN: 2147483647
Year: 2002
Pages: 162

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net