Chapter 12. Programming with thebash Shell

CONTENTS

Chapter 12. Programming with the bash Shell

  •  12.1 Introduction
  •  12.2 Reading User Input
  •  12.3 Arithmetic
  •  12.4 Positional Parameters and Command Line Arguments
  •  12.5 Conditional Constructs and Flow Control
  •  12.6 Looping Commands
  •  12.7 Functions
  •  12.8 Trapping Signals
  •  12.9 Debugging
  •  12.10 Processing Command Line Options withgetopts
  •  12.11 The eval Command and Parsing the Command Line
  •  12.12 bash Options
  •  12.13 Shell Built-In Commands
  •  BASH SHELL LAB EXERCISES

graphics/ch12.gif

12.1 Introduction

When commands are executed from within a file, instead of from the command line, the file is called a shell script and the shell is running noninteractively. When the bash shell starts running noninteractively, it looks for the environment variable, BASH_ENV (ENV) and starts up the file (normally .bashrc) assigned as its value. After the BASH_ENV file has been read, the shell will start executing commands in the script.[1]

12.1.1 The Steps in Creating a Shell Script

A shell script is normally written in an editor and consists of commands interspersed with comments. Comments are preceded by a pound sign and consist of text used to document what is going on.

The First Line. The first line at the top left corner of the script will indicate the program that will be executing the lines in the script. This line, commonly called the shbang line, is written as

#!/bin/bash

The #! is called a magic number and is used by the kernel to identify the program that should be interpreting the lines in the script. This line must be the top line of your script. The bash program can also accept arguments to modify its behavior. See Table 12.8 for a list of bash options.

Comments. Comments are lines preceded by a pound sign (#) and can be on a line by themselves or on a line following a script command. They are used to document your script. It is sometimes difficult to understand what the script is supposed to do if it is not commented. Although comments are important, they are often too sparse or not used at all. Try to get used to commenting what you are doing not only for someone else, but also for yourself. Two days from now you may not recall exactly what you were trying to do.

Executable Statements and bash Shell Constructs. A bash shell program consists of a combination of UNIX commands, bash shell commands, programming constructs, and comments.

Making the Script Executable. When you create a file, it is not given the execute permission. You need this permission to run your script. Use the chmod command to turn on the execute permission.

Example 12.1
1   $ chmod +x myscript 2   $ ls -lF  myscript     -rwxr-xr-x    1  ellie   0 Jul  13:00 myscript*

EXPLANATION

  1. The chmod command is used to turn on the execute permission for the user, group, and others.

  2. The output of the ls command indicates that all users have execute permission on the myscript file. The asterisk at the end of the filename also indicates that this is an executable program.

A Scripting Session. In the following example, the user will create a script in the editor. After the user saves the file, the execute permissions are turned on, and the script is executed. If there are errors in the program, the shell will respond immediately.

Example 12.2
(The Script) 1   #!/bin/bash 2   # This is the first Bash shell program of the day.     # Scriptname: greetings     # Written by:  Barbara Bashful 3   echo "Hello $LOGNAME, it's nice talking to you." 4   echo "Your present working directory is 'pwd'."     echo "You are working on a machine called 'uname -n'."     echo "Here is a list of your files." 5   ls    # List files in the present working directory 6   echo  "Bye for now $LOGNAME. The time is 'date +%T'!" (The Command Line)     $ greetings     # Don't forget to turn turn on x permission!     bash: ./greetings: Permission denied.     $ chmod +x greetings     $ greetings  or  ./greetings 3   Hello barbara, it's nice talking to you. 4   Your present working directory is /home/lion/barbara/prog     You are working on a machine called lion.     Here is a list of your files. 5   Afile        cplus    letter     prac     Answerbook   cprog    library    prac1     bourne       joke     notes      perl5 6   Bye for now barbara. The time is 18:05:07!

EXPLANATION

  1. The first line of the script, #!/bin/bash, lets the kernel know what interpreter will execute the lines in this program, in this case the bash (Bourne Again shell) interpreter.

  2. The comments are nonexecutable lines preceded by a pound sign. They can be on a line by themselves or appended to a line after a command.

  3. After variable substitution is performed by the shell, the echo command displays the line on the screen.

  4. After command substitution is performed by the shell, the echo command displays the line on the screen.

  5. The ls command is executed. The comment will be ignored by the shell.

  6. The echo command displays the string enclosed within double quotes. Variables and command substitution (backquotes) are expanded when placed within double quotes. In this case, the quotes were really not necessary.

12.2 Reading User Input

12.2.1 Variables (Review)

In the last chapter we talked about declaring and unsetting variables. Variables are set local to the current shell or as environment variables. Unless your shell script will invoke another script, variables are normally set as local variables within a script. (See "Variables".)

To extract the value from a variable, precede the variable with a dollar sign. You can enclose the variable within double quotes and the dollar sign will be interpreted by the shell for variable expansion. Variable expansion is not performed if the variable is enclosed in single quotes.

Example 12.3
1   name="John Doe" or declare name="John Doe"   # local variable 2   export NAME="John Doe"    # global variable 3   echo "$name" "$NAME"      # extract the value 

12.2.2 The read Command

The read command is a built-in command used to read input from the terminal or from a file (see Table 12.1). The read command takes a line of input until a newline is reached. The newline at the end of a line will be translated into a null byte when read. If no names are supplied, the line read is assigned to the special built-in variable, REPLY. You can also use the read command to cause a program to stop until the user hits Enter. To see how the read command is most effectively used for reading lines of input from a file, see "Looping Commands". The r option to read causes the backslash/newline pair to be ignored; the backslash is treated as part of the line. The read command has four options to control its behavior: a, e, p, and r.[2]

Table 12.1. The read Command
Format Meaning
read answer Reads a line from standard input and assigns it to the variable answer.
read first last Reads a line from standard input to the first whitespace or newline, putting the first word typed into the variable first and the rest of the line into the variable last.
read Reads a line from standard input and assigns it to the built-in variable, REPLY.
read a arrayname Reads a list of words into an array called arrayname.[a]
read e Used in interactive shells with command line editing in effect; e.g., if editor is vi, vi commands can be used on the input line.[a]
read p prompt Prints a prompt, waits for input, and stores input in REPLY variable.[a]
read r line Allows the input to contain a backslash.[a]

[a] Not implemented on versions of bash prior to 2.0.

Example 12.4
(The Script)     #!/bin/bash     # Scriptname: nosy     echo -e "Are you happy? \c" 1   read answer     echo "$answer is the right response."     echo -e "What is your full name? \c" 2   read first middle last     echo "Hello  $first"     echo  n "Where do you work? " 3   read 4   echo I guess $REPLY keeps you busy! -----------------------------------------------------[a] 5   read -p "Enter your job title: " 6   echo "I thought you might be an $REPLY." 7   echo -n "Who are your best friends? " 8   read -a friends 9   echo "Say hi to ${friends[2]}." ------------------------------------------------------- (The Output)     $ nosy     Are you happy? Yes 1   Yes is the right response. 2   What is your full name? Jon Jake Jones     Hello Jon 3   Where do you work? the Chico Nut Factory 4   I guess the Chico Nut Factory keeps you busy! 5   Enter your job title: Accountant 6   I thought you might be an Accountant. 7,8 Who are your best friends?  Melvin Tim Ernesto 9   Say hi to Ernesto. 

[a] The commands listed below this line are not implemented on versions of bash prior to 2.x.

EXPLANATION

  1. The read command accepts a line of user input and assigns the input to the variable answer.

  2. The read command accepts input from the user and assigns the first word of input to the variable first, the second word of input to the variable middle, and all the rest of the words up to the end of the line to the variable last.

  3. A line is read from standard input and stored in the built-in REPLY variable.

  4. The value of the REPLY variable is printed.

  5. With the p option, the read command produces a prompt, Enter your job title: and stores the line of input in the special built-in REPLY variable.

  6. The value of the REPLY variable is displayed in the string.

  7. The user is asked to enter input.

  8. With the a option, the read command takes input as an array of words. The array is called friends. The elements read into the array are Melvin, Tim, and Ernesto.

  9. The third element of the friends array, Ernesto, is printed. Array indices start at 0.

Example 12.5
(The Script)     #!/bin/bash     # Scriptname: printer_check     # Script to clear a hung-up printer 1   if [ $LOGNAME != root ]     then        echo "Must have root privileges to run this program"        exit 1     fi 2   cat << EOF     Warning: All jobs in the printer queue will be removed.     Please turn off the printer now. Press return when you     are ready to continue. Otherwise press Control C.     EOF 3   read JUNK      # Wait until the user turns off the printer     echo 4   /etc/rc.d/init.d/lpd stop       # Stop the printer 5   echo -e "\nPlease turn the printer on now." 6   echo "Press Enter to continue" 7   read JUNK                # Stall until the user turns the printer                              # back on     echo                     # A blank line is printed 8   /etc/rc.d/init.d/lpd start     # Start the printer 

EXPLANATION

  1. Checks to see if user is root. If not, sends an error and exits.

  2. Creates a here document. Warning message is displayed on the screen.

  3. The read command waits for user input. When the user presses Enter, the variable JUNK accepts whatever is typed. The variable is not used for anything. The read in this case is used to wait until the user turns off the printer, comes back, and presses Enter.

  4. The lpd program stops the printer daemon.

  5. Now it's time to turn the printer back on!

  6. The user is asked to press Enter when ready.

  7. Whatever the user types is read into the variable JUNK, and when Enter is pressed, the program will resume execution.

  8. The lpd program starts the print daemons.

12.3 Arithmetic

12.3.1 Integers (declare and let Commands)

The declare Command. Variables can be declared as integers with the declare i command. If you attempt to assign any string value, bash assigns 0 to the variable. Arithmetic can be performed on variables that have been declared as integers. (If the variable has not been declared as an integer, the built-in let command allows arithmetic operations. See "The let Command" on page 732.) If you attempt to assign a floating point number, bash reports a syntax error. Numbers can also be represented in different bases such as binary, octal, and hex.

Example 12.6
1   $ declare  i num 2   $ num=hello     $ echo $num     0 3   $ num=5 + 5     bash: +: command not found 4   $ num=5+5     $ echo $num     10 5   $ num=4*6     $ echo $num     24 6   $ num="4 * 6"     $ echo $num     24 7   $ num=6.5     bash: num: 6.5: sytax error in expression (remainder of      expression is ".5") 

EXPLANATION

  1. The declare command with the i option creates an integer variable num.

  2. Trying to assign the string hello to the integer variable num causes the string to be stored as zero.

  3. The whitespace must be quoted or removed unless the let command is used.

  4. The whitespace is removed and arithmetic is performed.

  5. Multiplication is performed and the result assigned to num.

  6. The whitespace is quoted so that the multiplication can be performed and to keep the shell from expanding the wildcard (*).

  7. Because the variable is set to integer, adding a fractional part causes a bash syntax error.

Listing Integers. The declare command with only the i argument will list all preset integers and their values, as shown in the following display.

$ declare  i declare -ir EUID="15"          # effective user id declare -ir PPID="235"         # parent process id declare -ir UID="15"           # user id 

Representing and Using Different Bases. Numbers can be represented in decimal (base 10, the default), octal (base 8), hexadecimal (base 16), and a range from base 2 to 36.

FORMAT

variable=base#number-in-that-base 
Example 12.7
n=2#101   Base is 2; number 101 is in base 2 
Example 12.8
(The Command Line) 1  $ declare -i x=017    $ echo $x    15 2  $ x=2#101    $ echo $x    5 3  $ x=8#17    $ echo $x    15 4  $ x=16#b    $ echo $x    11 

EXPLANATION

  1. The declare function is used to assign an integer variable x the octal value 017. Octal numbers must start with a leading zero. 15, the decimal value of 017, is printed.

  2. The variable, x, is assigned the value of 101 (binary). 2 represents the base, separated by a #, and the number in that base, 101. The value of x is printed as decimal, 5.

  3. The variable x is assigned the value of 17 (octal). The value of x is printed as decimal, 15.

  4. The variable x is assigned the value of b (hexadecimal). The value of x is decimal 11.

The let Command. The let command is a bash shell built-in command that is used to perform integer arithmetic and numeric expression testing. To see what let operators your version of bash supports, type at the prompt:

help let 

A list of the operators is also found in Table 12.4.

Example 12.9
1   $ i=5  or let i=5 2   $ let i=i+1     $ echo $i     6 3   $ let "i = i + 2"     $ echo $i     8 4   $ let "i+=1"     $ echo $i     9 5   $ i=3 6   $ (( i+=4))     $ echo $i     7 7   $ (( i=i-2 ))     $ echo $i     5 

EXPLANATION

  1. The variable i is assigned the value 5.

  2. The let command will add 1 to the value of i. The $ (dollar sign) is not required for variable substitution when performing arithmetic.

  3. The quotes are needed if the arguments contain whitespace.

  4. The shortcut operator, +=, is used to add 1 to the value of i.

  5. The variable i is assigned the value 5.

  6. The double parentheses can be used to replace let.[a]

    4 is added and assigned to i.

  7. 2 is subtracted from i. We could have also written i =2

[a] Double parentheses (( )) are used to replace let on versions of bash 2.x.

12.3.2 Floating Point Arithmetic

Bash supports only integer arithmetic, but the bc, awk, and nawk utilities are useful if you need to perform more complex calculations.

Example 12.10
(The Command Line) 1   $ n='echo "scale=3; 13 / 2" | bc'     $ echo $n     6.500 2   product='gawk -v x=2.45 -v y=3.123 'BEGIN{printf "%.2f\n",x*y}''     $ echo $product     7.65 

EXPLANATION

  1. The output of the echo command is piped to the bc program. The scale is set to 3, which is the number of significant digits to the right of the decimal point that will be printed. The calculation is to divide 13 by 2. The entire pipeline is enclosed in backquotes. Command substitution will be performed and the output assigned to the variable n.

  2. The gawk program gets its values from the argument list passed in at the command line, x=2.45 y=3.123. After the numbers are multiplied, the printf function formats and prints the result with a precision of two places to the right of the decimal point. The output is assigned to the variable product.

12.4 Positional Parameters and Command Line Arguments

12.4.1 Positional Parameters

Information can be passed into a script via the command line. Each word (separated by whitespace) following the scriptname is called an argument.

Command line arguments can be referenced in scripts with positional parameters; for example, $1 for the first argument, $2 for the second argument, $3 for the third argument, and so on. After $9, curly braces are used to keep the number as one number. For example, positional parameter 10 is referenced as ${10}. The $# variable is used to test for the number of parameters, and $* is used to display all of them. Positional parameters can be set or reset with the set command. When the set command is used, any positional parameters previously set are cleared out. See Table 12.2.

Table 12.2. Positional Parameters
Positional Parameter What It References
$0 References the name of the script.
$# Holds the value of the number of positional parameters.
$* Lists all of the positional parameters.
$@ Means the same as $*, except when enclosed in double quotes.
"$*" Expands to a single argument (e.g., "$1 $2 $3").
"$@" Expands to separate arguments (e.g., "$1" "$2" "$3").
$1 ${10} References individual positional parameters.
Example 12.11
(The Script)     #!/bin/bash     # Scriptname: greetings2     echo "This script is called $0." 1   echo  "$0  $1 and $2"     echo "The number of positional parameters is $#"     ----------------------------------------------------------- (The Command Line)     $ chmod +x greetings2 2   $ greetings2     This script is called  greetings2.     greetings  and     The number of positional paramters is 3   $ greetings2 Tommy     This script is called greetings2.     greetings Tommy and     The number of positional parameters is 1 4   $ greetings2 Tommy Kimberly     This script is called greetings2.     greetings Tommy and Kimberly     The number of positional parameters is 2 

EXPLANATION

  1. In the script greetings2, positional parameter $0 references the scriptname, $1 the first command line agreement, and $2 the second command line agreement.

  2. The greetings2 script is executed without any arguments passed. The output illustrates that the script is called greetings2 ($0 in the script) and that $1 and $2 were never assigned anything; therefore, their values are null and nothing is printed.

  3. This time, one argument is passed, Tommy. Tommy is assigned to positional parameter 1.

  4. Two arguments are entered, Tommy and Kimberly. Tommy is assigned to $1 and Kimberly is assigned to $2.

12.4.2 The set Command and Positional Parameters

The set command with arguments resets the positional parameters.[3] Once reset, the old parameter list is lost. To unset all of the positional parameters, use set . $0 is always the name of the script.

Example 12.12
(The Script)     #!/bin/bash     # Scriptname: args     # Script to test command line arguments 1   echo The name of this script is $0. 2   echo The arguments are $*. 3   echo The first argument is $1. 4   echo The second argument is $2. 5   echo The number of arguments is $#. 6   oldargs=$* 7   set Jake Nicky Scott    # Reset the positional parameters 8   echo All the positional parameters are $*. 9   echo The number of postional parameters is $#. 10  echo "Good bye for now, $1." 11  set $(date)             # Reset the positional parameters 12  echo The date is $2 $3, $6. 13  echo "The value of \$oldargs is $oldargs." 14  set $oldargs 15  echo $1 $2 $3 (The Output)     $ args a b c d 1   The name of this script is args. 2   The arguments are a b c d. 3   The first argument is a. 4   The second argument is b. 5   The number of arguments is 4. 8   All the positional parameters are Jake Nicky Scott. 9   The number of positional parameters is 3. 10  Good-bye for now, Jake. 12  The date is Mar 25, 2001. 13  The value of $oldargs is a b c d. 15  Wed Mar 25 

EXPLANATION

  1. The name of the script is stored in the $0 variable.

  2. $* represents all of the positional parameters.

  3. $1 represents the first positional parameter (command line argument).

  4. $2 represents the second positional parameter.

  5. $# is the total number of positional parameters (command line arguments).

  6. All positional parameters are saved in a variable called oldargs.

  7. The set command allows you to reset the positional parameters, clearing out the old list. Now, $1 is Jake, $2 is Nicky, and $3 is Scott.

  8. $* represents all of the parameters, Jake, Nicky, and Scott.

  9. $# represents the number of parameters, 3.

  10. $1 is Jake.

  11. After command substitution is performed, i.e., date is executed, the positional parameters are reset to the output of the date command.

  12. The new values of $2, $3, and $6 are displayed.

  13. The values saved in oldargs are printed.

  14. The set command creates positional parameters from the values stored in oldargs.

  15. The first three positional parameters are displayed.

Example 12.13
(The Script)     #!/bin/bash     # Scriptname: checker     # Script to demonstrate the use of special variable     # modifiers and arguments 1   name=${1:?"requires an argument" }     echo Hello $name (The Command Line) 2   $ checker     checker: 1: requires an argument 3   $ checker Sue     Hello Sue 

EXPLANATION

  1. The special variable modifier :? will check whether $1 has a value. If not, the script exits and the message is printed.

  2. The program is executed without an argument. $1 is not assigned a value; an error is displayed.

  3. The checker program is given a command line argument, Sue. In the script, $1 is assigned Sue. The program continues.

How $* and $@ Differ. The $* and $@ differ only when enclosed in double quotes. When $* is enclosed within double quotes, the parameter list becomes a single string. When $@ is enclosed within double quotes, each of the parameters is quoted; that is, each word is treated as a separate string.

Example 12.14
1   $ set 'apple pie' pears peaches 2   $ for i in $*     > do     > echo $i     > done     apple     pie     pears     peaches 3   $ set 'apple pie' pears peaches 4   $ for i in "$*"     > do     > echo $i     > done     apple pie pears peaches 5   $ set 'apple pie' pears peaches 6   $ for i in $@     > do     > echo $i     > done     apple     pie     pears     peaches 7   $ set 'apple pie' pears peaches 8   $ for i in "$@"         # At last!!     > do     > echo $i     > done     apple pie     pears     peaches

EXPLANATION

  1. The positional parameters are set.

  2. When $* is expanded, the quotes surrounding apple pie are stripped; apple and pie become two separate words. The for loop assigns each of the words, in turn, to the variable i, and then prints the value of i. Each time through the loop, the word on the left is shifted off, and the next word is assigned to the variable i.

  3. The positional parameters are set.

  4. By enclosing $* in double quotes, the entire parameter list becomes one string, apple pie pears peaches. The entire list is assigned to i as a single word. The loop makes one iteration.

  5. The positional parameters are set.

  6. Unquoted, $@ and $* behave the same way (see entry 2 of this explanation).

  7. The positional parameters are set.

  8. By surrounding $@ with double quotes, each of the positional parameters is treated as a quoted string. The list would be apple pie, pears, and peaches. The desired result is finally achieved.

12.5 Conditional Constructs and Flow Control

12.5.1 Exit Status

Conditional commands allow you to perform some task(s) based on whether a condition succeeds or fails. The if command is the simplest form of decision-making; the if/else commands allow a two-way decision; and the if/elif/else commands allow a multiway decision.

Bash allows you to test two types of conditions: the success or failure of commands or whether an expression is true or false. In either case, the exit status is always used. An exit status of zero indicates success or true, and an exit status that is nonzero indicates failure or false. The ? status variable contains a numeric value representing the exit status. To refresh your memory on how exit status works, look at Example 12.15.

Example 12.15
    (At the Command Line) 1   $ name=Tom 2   $ grep "$name"  /etc/passwd     Tom:8ZKX2F:5102:40:Tom Savage:/home/tom:/bin/sh 3   $ echo $?     0             # Success! 4   $ name=Fred 5   $ grep "$name" /etc/passwd     $ echo $?     1              # Failure 

EXPLANATION

  1. The variable name is assigned the string Tom.

  2. The grep command will search for string Tom in the passwd file.

  3. The ? variable contains the exit status of the last command executed; in this case, the exit status of grep. If grep is successful in finding the string Tom, it will return an exit status of zero. The grep command was successful.

  4. The variable name is assigned Fred.

  5. The grep command searches for Fred in the passwd file and is unable to find him. The ? variable has a value of 1, indicating that grep failed.

12.5.2 The Built-In test Command

To evaluate an expression, the built-in test command is commonly used. This command is also linked to the bracket symbol. Either the test command itself can be used, or the expression can be enclosed in set of single brackets. Shell metacharacter expansion is not performed on expressions evaluated with the simple test command or when square brackets are used. Since word splitting is performed on variables, strings containing whitespace must be quoted. (See Example 12.16.)

On versions of bash 2.x, double brackets [[ ]] (the built-in compound test command) can be used to evaluate expressions. Word splitting is not performed on variables and pattern matching is done, allowing the expansion of metacharacters. A literal string containing whitespace must be quoted and if a string (with or without whitespace) is to be evaluated as an exact string, rather than part of a pattern, it too must be enclosed in quotes. The logical operators && (and) and || (or) replace the a and o operators used with the simple test command. (See Example 12.17.)

Although the test command can evaluate arithmetic expressions, you may prefer to use the let command with its rich set of C-like operators (bash 2.x). The let command can be represented alternatively by enclosing its expression in a set of double parentheses. (See Example 12.18.)

Whether you are using the test command, compound command, or let command, the result of an expression is tested, with zero status indicating success and nonzero status indicating failure. (See Table 11.10.)

The following examples illustrate how the exit status is tested with the built-in test command and the alternate form of test, a set of single brackets [ ]; the compound command, a set of double brackets [[ ]]; and the let command, a set of double parentheses (( )).

Example 12.16
    (The test Command)     (At the Command Line) 1   $ name=Tom 2   $ grep "$name" /etc/passwd 3   $ echo $? 4   $ test $name != Tom 5   $ echo $?     1            # Failure 6   $ [ $name = Tom ]         # Brackets replace the test command 7   $ echo $?     0 8   $ [ $name = [Tt]?? ]     $ echo $?     1 9   $ x=5     $ y=20 10  $ [ $x -gt $y ]     $ echo $?     1 11  $ [ $x -le $y ]     $ echo $?     0 

EXPLANATION

  1. The variable name is assigned the string Tom.

  2. The grep command will search for string Tom in the passwd file.

  3. The ? variable contains the exit status of the last command executed, in this case, the exit status of grep. If grep is successful in finding the string Tom, it will return an exit status of zero. The grep command was successful.

  4. The test command is used to evaluate strings, numbers, and perform file testing. Like all commands, it returns an exit status. If the exit status is zero, the expression is true; if the exit status is one, the expression evaluates to false. There must be spaces surrounding the equal sign. The value of name is tested to see if it is not equal to Tom.

  5. The test fails and returns an exit status of one.

  6. The brackets are an alternate notation for the test command. There must be spaces after the first bracket. The expression is tested to see if name evaluates to the string Tom. Bash allows either a single or double equal sign to be used to test for equality of strings.

  7. The exit status of the test is 0. The test was successful because $name is equal to Tom.

  8. The test command does not allow wildcard expansion. Because the question mark is treated as a literal character, the test fails. Tom and [Tt]?? are not equal. The exit status is 1, indicating that the text in line 8 failed.

  9. x and y are given numeric values.

  10. The test command uses numeric relational operators to test its operands; in this example it tests if $x is greater than ( gt) $y, and returns 0 exit status if true, 1 if false. (See Table 12.3.)

  11. Tests if $x less than or equal to ( le) $y, returning 0 exit status if true,1 if false.

Example 12.17
(The compound test command)(bash 2.x)     $ name=Tom; friend=Joseph 1   $ [[ $name == [Tt]om ]]      # Wildcards allowed     $ echo $?     0 2   $ [[ $name == [Tt]om && $friend == "Jose" ]]     $ echo $?     1 3   $ shopt -s extglob           # Turns on extended pattern matching 4   $ name=Tommy 5   $ [[ $name == [Tt]o+(m)y ]]     $ echo $?     0 

EXPLANATION

  1. If using the compound test command, shell metacharacters can be used in string tests. In this example, the expression is tested for string equality where name can match either Tom or tom or tommy, etc. If the expression is true, the exit status (?) is 0.

  2. The logical operators && (and) and || (or) can be used with the compound test. If && is used, both expressions must be true, and if the first expression evaluates as false, no further checking is done. With the || logical operator, only one of the expressions must be true. If the first expression evaluates true, no further checking is done. Note that "Jose" is quoted. If not quoted, the friend variable would be checked to see if it contained the pattern Jose. Jose would match, and so would Joseph. The expression evaluates to false because the second condition is not true. The exit status is 1.

  3. Extended pattern matching is turned on with the built-in shopt command.

  4. The variable is assigned the value Tommy.

  5. In this test, the expression is tested for string equality using the new pattern-matching metacharacters. It tests if name matches a string starting with T or t, followed by an o, one or more m characters, and a y.

Example 12.18
    (The let Command) (bash 2.x)     (At the Command Line) 1   $ x=2     $ y=3 2   (( x > 2 ))     echo $?     1 3   (( x < 2 ))     echo $?     0 4   (( x == 2 && y == 3 ))     echo $?     0 5   (( x > 2 || y < 3 ))     echo $?     1 

EXPLANATION

  1. x and y are assigned numeric values.

  2. The double parentheses replace the let command to evaluate the numeric expression. If x is greater than y, the exit status is 0. Because the condition is not true, the exit status is 1. The ? variable holds the exit status of the last command executed; i.e., the (( )) command. Note: To evaluate a variable, the dollar sign is not necessary when the variable is enclosed in (( )).

  3. The double parentheses evaluate the expression. If x is less than 2, and exit status of 0 is returned; otherwise, 1 is returned.

  4. The compound expression is evaluated. The expression is tested as follows: if x is equal to 2 and y is equal to 3 (i.e., both expressions are true), then an exit status of 0 is returned; otherwise, 1 is returned.

  5. The compound expression is evaluated. The expression is tested as follows: if x is greater than 2 or y is less than 3 (i.e., one of the expressions is true), then an exit status of 0 is returned; otherwise, 1 is returned.

 

Table 12.3. The test Command Operators
Test Operator Tests True If
String Test  
[ string1 = string2 ] String1 is equal to String2 (space surrounding = required).
[ string1==string2 ] (Can be used instead of the single = sign on bash versions 2.x.)
[ string1 != string2 ] String1 is not equal to String2 (space surrounding != required).
[ string ] String is not null.
[ z string ] Length of string is zero.
[ n string ] Length of string is nonzero.
[ l string ] Length of string (number of characters).
Examples: test n $word or [ n $word ] test tom = sue or [ tom = sue ]
Logical Test  
[ string1 a string1 ] Both string1 and string2 are true.
[ string1 o string2 ] Either string1 or string2 is true.
[ ! string1 ] Not a string1 match.
Logical Test (Compound Test)[a]  
[[ pattern1 && pattern2 ]] Both pattern1 and pattern2 are true.
[[ pattern1 || pattern2 ]] Either pattern1 or pattern2 is true.
[[ ! pattern ]] Not a pattern match.
Integer Test  
[ int1 eq int2 ] Int1 is equal to int2.
[ int1 ne int2 ] Int1 is not equal to int2.
[ int1 gt int2 ] Int1 is greater than int2.
[ int1 ge int2 ] Int1 is greater than or equal to int2.
[ int1 lt int2 ] Int1 is less than int2.
[ int1 le int2 ] Int1 is less than or equal to int2.
Binary Operators for File Testing  
[ file1 nt file2 ] True if file1 is newer than file2 (according to modification date).
[ file1 ot file2 ] True if file1 is older than file2.
[ file1 ef file2 ] True if file1and file2 have the same device or inode numbers.

[a] With the compound test, pattern can contain pattern matching metacharacters; for exact string testing, pattern2 must be enclosed in quotes.

Table 12.4. The let Command Operators
Operator Meaning
+ Unary minus and plus.
! ~ Logical and bitwise not (negation).
* / % Multiply, divide, remainder.
+ Add, subtract.
let Operators Not Implemented Prior to bash 2.x
<< >> Bitwise left shift, right shift.
<= >= < > Comparison operators.
== != Equal to and not equal to.
& Bitwise and.
^ Bitwise exclusive or.
| Bitwise or.
&& Logical and.
|| Logical or.
= *= /= %= += = <<= >>= &= ^= |= Assignment and shortcut assignment.

12.5.3 The if Command

The simplest form of conditional is the if command. The command (a bash built-in or executable) following the if construct is executed and its exit status is returned. The exit status is usually determined by the programmer who wrote the utility. If the exit status is zero, the command succeeded and the statement(s) after the then keyword are executed. In the C shell, the expression following the if command is a Boolean-type expression as in C. But in the Bash, Bourne, and Korn shells, the statement following the if is a command or group of commands. If the exit status of the command being evaluated is zero, the block of statements after the then is executed until fi is reached. The fi terminates the if block. If the exit status is nonzero, meaning that the command failed in some way, the statement(s) after the then keyword are ignored and control goes to the line directly after the fi statement.

It is important that you know the exit status of the commands being tested. For example, the exit status of grep is reliable in letting you know whether grep found the pattern it was searching for in a file. If grep is successful in its search, it returns a zero exit status; if not, it returns one. The sed and gawk programs also search for patterns, but they will report a successful exit status regardless of whether they find the pattern. The criterion for success with sed and gawk is correct syntax, not functionality.

FORMAT

    if command     then         command         command     fi ------------------------------------ (Using test for numbers and strings -- old format)     if test expression     then         command     fi               or     if [ string/numeric expression ] then            command     fi ------------------------------------- (Using test for strings -- new formst)     if [[ string expression ]] then            command     fi (Using let for numbers -- new format)     if (( numeric expression )) -------------------------------------- 
Example 12.19
1   if grep "$name" /etc/passwd > /dev/null 2>&1 2   then          echo Found $name! 3   fi 

EXPLANATION

  1. The grep command searches for its argument, name, in the /etc/passwd database. Standard output and standard error are redirected to /dev/null, the UNIX bit bucket.

  2. If the exit status of the grep command is zero, the program goes to the then statement and executes commands until fi is reached. Indentation of commands between the then and fi keywords is a convention used to make the program more readable, and hence, easier to debug.

  3. The fi terminates the list of commands following the then statement.

Example 12.20
1   echo  "Are you o.k. (y/n) ?"     read answer 2   if [ "$answer" = Y -o "$answer" = y ]     then         echo  "Glad to hear it." 3   fi 4   if [ $answer = Y -o "$answer" = y ]     [: too many arguments     ------------------------------------------- 5   if [[ $answer == [Yy]* || $answer == Maybe ]][a]     then         echo  "Glad to hear it."     fi 6   shopt -s extglob 7   answer="not really" 8   if [[ $answer = [Nn]o?( way|t really) ]]     then         echo "So sorry. "     fi 

[a] Lines 5 through 8 are only implemented on versions of bash 2.x.

EXPLANATION

  1. The user is asked the question and told to respond. The read command waits for a response.

  2. The test command, represented by square brackets, is used to test expressions. It returns an exit status of zero if the expression is true and nonzero if the expression is false. If the variable answer evaluates to Y or y, the commands after the then statement are executed. (The test command does not allow the use of wildcards when testing expressions, and spaces must surround the square brackets, as well as the = operators. See Table 12.3.) $answer is double quoted to hold it together as a single string. Otherwise the test command will fail.

  3. The fi terminates the if on line 2.

  4. The test command fails if more than one word appears before the = operator. For example, if the user entered yes, you betcha, the answer variable would evaluate to three words, causing the test to fail, unless $answer is enclosed in double quotes. The resulting error message is shown here.

  5. The compound command operators [[ ]] allow the expansion of shell metacharacters in a string expression. The variable does not need quotes surrounding it, even if it contains more than one word, as it did with the old test command.

  6. The shopt built-in, if set to extglob, allows expanded parameter expansion. See Table 11.12.

  7. The answer variable is set to the string "not really".

  8. Extended pattern matching is used here. The expression reads, if the value of the answer variable matches a string starting with no or No and if followed by zero or one occurrences of the expression in the parentheses, the expression is true. The expression could be evaluated to no, No, no way, No way, not really, or Not really.

The exit Command and the ? Variable. The exit command is used to terminate the script and return to the command line. You may want the script to exit if some condition occurs. The argument given to the exit command is a number ranging from 0 to 255. If the program exits with zero as an argument, the program exited with success. A nonzero argument indicates some kind of failure. The argument given to the exit command is stored in the shell's ? variable.

Example 12.21
(The Script) $ cat bigfiles     # Name: bigfiles     # Purpose: Use the find command to find any files in the root     # partition that have not been modified within the past n (any     # number within 30 days) days and are larger than 20 blocks     # (512-byte blocks) 1   if (( $# != 2 ))[a]        # [ $# -ne 2 ]     then        echo  "Usage:   $0 mdays size " 1>&2        exit 1 2   fi 3   if (( $1 <  0 || $1 > 30 ))[b]     # [ $1 -lt 0 -o $1 -gt 30 ]     then        echo "mdays is out of range"        exit 2 4   fi 5   if (( $2 <= 20 ))      # [ $2 -le 20 ]     then        echo "size is out of range"        exit 3 6   fi 7   find / -xdev -mtime $1 -size +$2 (The Command Line) $ bigfiles Usage: bigfiles mdays size $ echo $? 1 $ bigfiles 400 80 mdays is out of range $ echo $? 2 $ bigfiles 25 2 size is out of range $ echo $? 3 $ bigfiles 2 25 (Output of find prints here) 

[a] Not implemented on versions prior to bash 2.x. On older versions could also be written if let $(( $# != 2 )).

[b] Same as above. On older versions could also be written if let $(( $1 < 0 || $1 > 30 ).

EXPLANATION

  1. The statement reads: If the number of arguments is not equal to 2, print the error message and send it to standard error, then exit the script with an exit status of 1. Either the built-in test command or the let command can be used to test numeric expressions.

  2. The fi marks the end of the block of statements after then.

  3. The statement reads: If the value of the first positional parameter passed in from the command line is less than 0 or greater than 30, then print the message and exit with a status of 2. See Table 12.4 for numeric operators.

  4. The fi ends the if block.

  5. The statement reads: If the value of the second positional parameter passed in at the command line is less than or equal to 20 (512-byte blocks), then print the message and exit with a status of 3.

  6. The fi ends the if block.

  7. The find command starts its search in the root directory. The xdev option prevents find from searching other partitions. The mtime option takes a number argument, which is the number of days since the file was modified, and the size option takes a number argument, which is the size of the file in 512-byte blocks.

Checking for Null Values. When checking for null values in a variable, use double quotes to hold the null value or the test command will fail.

Example 12.22
(The Script) 1   if [ "$name" = "" ]     # Alternative to  [ ! "$name" ]                             # or [ -z "$name" ]     then        echo The name variable is null     fi (From System showmount program, which displays all remotely mounted systems)     remotes=$(/usr/sbin/showmount) 2   if [ "X${remotes}" != "X" ]     then        /usr/sbin/wall ${remotes}                     ... 3   fi 

EXPLANATION

  1. If the name variable evaluates to null, the test is true. The double quotes are used to represent null.

  2. The showmount command lists all clients remotely mounted from a host machine. The command will list either one or more clients, or nothing. The variable remotes will either have a value assigned or will be null. The letter X precedes the variable remotes when being tested. If remotes evaluates to null, no clients are remotely logged on and X will be equal to X, causing the program to start execution again on line 3. If the variable has a value, for example, the hostname pluto, the expression would read if Xpluto != X, and the wall command would be executed. (All users on remote machines will be sent a message.) The purpose of using X in the expression is to guarantee that even if the value of remotes is null, there will always be a placeholder on either side of the != operator in the expression.

  3. The fi terminates the if.

Nested if Commands. When if statements are nested, the fi statement always goes with the nearest if statement. Indenting the nested ifs makes it easier to see which if statement goes with which fi statement.

12.5.4 The if/else Command

The if/else commands allow a two-way decision-making process. If the command after the if fails, the commands after the else are executed.

FORMAT

if  command then     command(s) else     command(s) fi 
Example 12.23
(The Script)     #!/bin/bash     # Scriptname: grepit 1   if grep "$name" /etc/passwd >& /dev/null; then         echo Found $name! 3   else 4       echo "Can't find $name."         exit 1 5   fi 

EXPLANATION

  1. The grep command searches for its argument, name, in the NIS passwd database. Because the user does not need to see the output, standard output and standard error are redirected to /dev/null, the UNIX bit bucket.

  2. If the exit status of the ypmatch command is zero, program control goes to the then statement and executes commands until else is reached.

  3. The commands under the else statement are executed if the ypmatch command fails to find $name in the passwd database; that is, the exit status of ypmatch must be nonzero for the commands in the else block to be executed.

  4. If the value in $name is not found in the passwd database, this echo statement is executed and the program exits with a value of 1, indicating failure.

  5. The fi terminates the if.

Example 12.24
(The Script) #!/bin/bash # Scriptname: idcheck # purpose:check user id to see if user is root. # Only root has a uid of 0. # Format for id output:uid=9496(ellie) gid=40 groups=40 # root's uid=0 1   id='id | gawk  F'[=(]'  '{print $2}''      # get user id     echo your user id is: $id 2   if  (( id == 0 ))[a]   # [ $id  eq 0 ] (See cd file: idcheck2)     then 3       echo "you are superuser." 4   else         echo "you are not superuser." 5   fi     (The Command Line) 6   $ idcheck     your user id is: 9496     you are not superuser. 7   $ su     Password: 8   # idcheck     your user id is: 0     you are superuser 

[a] Not implemented on versions of bash prior to 2.x.

EXPLANATION

1. The id command is piped to the gawk command. Nawk uses an equal sign and open parenthesis as field separators, extracts the user ID from the output, and assigns the output to the variable id.

2,3,4. If the value of id is equal to zero, then line 3 is executed. If id is not equal to zero, the else statements are executed.

5. The fi marks the end of the if command.

6. The idcheck script is executed by the current user, whose UID is 9496.

7. The su command switches the user to root.

8. The # prompt indicates that the superuser (root) is the new user. The UID for root is 0.

12.5.5 The if/elif/else Command

The if/elif/else commands allow a multiway decision-making process. If the command following the if fails, the command following the elif is tested. If that command succeeds, the commands under its then statement are executed. If the command after the elif fails, the next elif command is checked. If none of the commands succeeds, the else commands are executed. The else block is called the default.

FORMAT

if  command then     command(s) elif command then     commands(s) elif command then     command(s) else     command(s) fi 
Example 12.25
(The Script)     #!/bin/bash     # Scriptname: tellme     # Using the old-style test command 1   echo -n "How old are you? "     read age 2   if [ $age -lt 0 -o $age -gt 120 ]     then        echo  "Welcome to our planet! "        exit 1     fi 3   if  [ $age -ge 0 -a $age -le 12 ]     then        echo "A child is a garden of verses"     elif [ $age -ge 12 -a $age -le 19 ]     then        echo "Rebel without a cause"     elif [ $age -ge 20 -a  $age -le 29 ]     then        echo "You got the world by the tail!!"     elif [ $age -ge  30 -a  $age -le 39 ]     then        echo "Thirty something..." 4   else        echo "Sorry I asked" 5   fi (The Output) $ tellme How old are you? 200 Welcome to our planet! $ tellme How old are you? 13 Rebel without a cause $ tellme How old are you? 55 Sorry I asked ----------------------------------------------------------     #!/bin/bash     # Using the new (( )) compound let command     # Scriptname: tellme2 1   echo -n "How old are you? "     read age 2   if (( age < 0 || age > 120 ))     then        echo "Welcome to our planet! "        exit 1     fi 3   if ((age >= 0 && age <= 12))     then        echo "A child is a garden of verses"     elif ((age >= 13 && age <= 19 ))     then        echo "Rebel without a cause"     elif (( age >= 19 &&  age <=  29 ))     then        echo "You got the world by the tail!!"     elif  (( age >=  30 &&  age <= 39 ))     then        echo "Thirty something..." 4   else        echo "Sorry I asked" 5   fi 

EXPLANATION

  1. The user is asked for input. The input is assigned to the variable age.

  2. A numeric test is performed by the test command. If age is less than 0 or greater than 120, the echo command is executed and the program terminates with an exit status of one. The interactive shell prompt will appear.

  3. A numeric test is performed by the test command. If age is greater than or equal to 0 and less than or equal to 12, the test command returns exit status zero, true, and the statement after the then is executed. Otherwise, program control goes to the elif. If that test is false, the next elif is tested.

  4. The else construct is the default. If none of the above statements are true, the else commands will be executed.

  5. The fi terminates the initial if statement.

12.5.6 File Testing

Often when you are writing scripts, your script will require that there are certain files available and that those files have specific permissions, are of a certain type, or have other attributes. You will find file testing a necessary part of writing dependable scripts.

Table 12.5. File Test Operators
Test Operator Tests True If
b filename Block special file.
c filename Character special file.
d filename Directory existence.
e filename File existence.
f filename Regular file existence and not a directory.
G filename True if file exists and is owned by the effective group ID.
g filename Set-group-ID is set.
k filename Sticky bit is set.
L filename File is a symbolic link.
p filename File is a named pipe.
O filename File exists and is owned by the effective user ID.
r filename File is readable.
S filename File is a socket.
s filename File is nonzero size.
t fd True if fd (file descriptor) is opened on a terminal.
u filename Set-user-ID bit is set.
w filename File is writeable.
x filename File is executable.
Example 12.26
(The Script)     #!/bin/bash     # Using the old style test command     # filename: perm_check     file=./testing 1   if [ -d $file ]     then        echo "$file is a directory" 2   elif [ -f $file ]     then 3      if [ -r $file -a -w $file -a -x $file ]        then     # nested if command             echo "You have read,write,and execute \             permission on $file." 4      fi 5   else        echo "$file is neither a file nor a directory. " 6   fi ----------------------------------------------------------     #!/bin/bash     # Using the new compound operator for test (( ))[a]     # filename: perm_check2     file=./testing 1   if [[ -d $file ]]     then        echo "$file is a directory" 2   elif [[ -f $file ]]     then 3      if [[ -r $file && -w $file && -x $file ]]        then     # nested if command            echo "You have read,write,and execute \            permission on $file." 4      fi 5   else        echo "$file is neither a file nor a directory. " 6   fi 

[a] New-style test with compound operators not implemented before bash 2.x.

EXPLANATION

  1. If the file testing is a directory, print testing is a directory.

  2. If the file testing is not a directory, else if the file is a plain file, then

  3. If the file testing is readable and writeable, and executable, then

  4. The fi terminates the innermost if command.

  5. The else commands are executed if lines 1 and 2 are not true.

  6. This fi goes with the first if.

12.5.7 The null Command

The null command, represented by a colon, is a built-in, do-nothing command that returns an exit status of zero. It is used as a placeholder after an if command when you have nothing to say, but need a command or the program will produce an error message because it requires something after the then statement. Often the null command is used as an argument to the loop command to make the loop a forever loop.

Example 12.27
(The Script)     #!/bin/bash     # filename: name_grep 1   name=Tom 2   if grep "$name" databasefile >& /dev/null     then 3      : 4   else        echo "$1 not found in databasefile"        exit 1     fi 

EXPLANATION

  1. The variable name is assigned the string Tom.

  2. The if command tests the exit status of the grep command. If Tom is found in the database file, the null command, a colon, is executed and does nothing. Both output and errors are redirected to /dev/null.

  3. The colon is the null command. It does nothing other than returning a 0 exit status.

  4. What we really want to do is print an error message and exit if Tom is not found. The commands after the else will be executed if the grep command fails.

Example 12.28
(The Command Line) 1   $ DATAFILE= 2   $ : ${DATAFILE:=$HOME/db/datafile}     $ echo $DATAFILE     /home/jody/ellie/db/datafile 3   $ : ${DATAFILE:=$HOME/junk}     $ echo $DATAFILE     /home/jody/ellie/db/datafile 

EXPLANATION

  1. The variable DATAFILE is assigned null.

  2. The colon command is a do-nothing command. The modifier (:=) returns a value that can be assigned to a variable or used in a test. In this example, the expression is passed as an argument to the do-nothing command. The shell will perform variable substitution; that is, assign the pathname to DATAFILE if DATAFILE does not already have a value. The variable DATAFILE is permanently set.

  3. Because the variable has already been set, it will not be reset with the default value provided on the right of the modifier.

Example 12.29
(The Script)     #!/bin/bash 1   # Scriptname: wholenum     # Purpose:The expr command tests that the user enters an     # integer     #     echo "Enter an integer."     read number 2   if expr "$number" + 0 >& /dev/null     then 3      :     else 4      echo "You did not enter an integer value."        exit 1 5   fi 

EXPLANATION

  1. The user is asked to enter an integer. The number is assigned to the variable number.

  2. The expr command evaluates the expression. If addition can be performed, the number is a whole number and expr returns a successful exit status. All output is redirected to the bit bucket /dev/null.

  3. If expr is successful, it returns a zero exit status, and the colon command does nothing.

  4. If the expr command fails, it returns a nonzero exit status, the echo command displays the message, and the program exits.

  5. The fi ends the if block.

12.5.8 The case Command

The case command is a multiway branching command used as an alternative to if/elif commands. The value of the case variable is matched against value1, value2, and so forth, until a match is found. When a value matches the case variable, the commands following the value are executed until the double semicolons are reached. Then execution starts after the word esac (case spelled backward).

If the case variable is not matched, the program executes the commands after the *), the default value, until ;; or esac is reached. The *) value functions the same as the else statement in if/else conditionals. The case values allow the use of shell wildcards and the vertical bar (pipe symbol) for oring two values.

FORMAT

case variable in value1)    command(s)    ;; value2)    command(s)    ;; *) command(s)    ;; esac 
Example 12.30
(The Script)     #!/bin/bash     # Scriptname: xcolors 1   echo -n "Choose a foreground color for your xterm window: "     read color 2   case "$color" in 3   [Bb]l??) 4       xterm -fg blue -fn terminal & 5       ;; 6   [Gg]ree*)         xterm -fg darkgreen -fn terminal &         ;; 7   red | orange)              # The vertical bar means "or"         xterm -fg "$color"  -fn terminal &         ;; 8   *)         xterm -fn terminal         ;; 9   esac 10  echo  "Out of case command" 

EXPLANATION

  1. The user is asked for input. The input is assigned to the variable color.

  2. The case command evaluates the expression $color.

  3. If the color begins with a B or b, followed by the letter l and any two characters, the case expression matches the first value. The value is terminated with a single closed parenthesis. The wildcards are shell metacharacters used for filename expansion.The xterm command sets the foreground color to blue.

  4. The statement is executed if the value in line number 3 matches the case expression.

  5. The double semicolons are required after the last command in this block of commands. Control branches to line 10 when the semicolons are reached. The script is easier to debug if the semicolons are on their own line.

  6. If the case expression matches a G or g, followed by the letters ree and ending in zero or more of any other characters, the xterm command is executed. The double semicolons terminate the block of statements and control branches to line 10.

  7. The vertical bar is used as an or conditional operator. If the case expression matches either red or orange, the xterm command is executed.

  8. This is the default value. If none of the above values match the case expression, the commands after the *) are executed.

  9. The esac statement terminates the case command.

  10. After one of the case values are matched, execution continues here.

Creating Menus with the here document and case Command. The here document and case command are often used together. The here document is used to create a menu of choices that will be displayed to the screen. The user will be asked to select one of the menu items, and the case command will test against the set of choices to execute the appropriate command.

Example 12.31
(From the .bash_profile File)     echo "Select a terminal type:  " 1   cat <<- ENDIT        1) unix        2) xterm        3) sun 2   ENDIT 3   read choice 4   case "$choice" in 5   1)  TERM=unix         export TERM         ;;     2)  TERM=xterm         export TERM         ;; 6   3)  TERM=sun         export TERM         ;; 7   esac 8   echo "TERM is $TERM." (The Output) $ . .bash_profile     Select a terminal type:     1) unix     2) xterm     3) sun     2                <-- User input     TERM is xterm. 

EXPLANATION

  1. If this segment of script is put in the .bash_profile, when you log on, you will be given a chance to select the proper terminal type. The here document is used to display a menu of choices.

  2. The user-defined ENDIT terminator marks the end of the here document.

  3. The read command stores the user input in the variable TERM.

  4. The case command evaluates the variable TERM and compares that value with one of the values preceding the closing parenthesis: 1, 2, or *.

  5. The first value tested is 1. If there is a match, the terminal is set to a unix. The TERM variable is exported so that subshells will inherit it.

  6. A default value is not required. The TERM variable is normally assigned in /etc/profile at login time. If the choice is 3, the terminal is set to a sun.

  7. The esac terminates the case command.

  8. After the case command has finished, this line is executed.

12.6 Looping Commands

Looping commands are used to execute a command or group of commands a set number of times or until a certain condition is met. The bash shell has three types of loops: the for loop, the while loop, and the until loop.

12.6.1 The for Command

The for looping command is used to execute commands a finite number of times on a list of items. For example, you might use this loop to execute the same commands on a list of files or usernames. The for command is followed by a user-defined variable, the keyword in, and a list of words. The first time in the loop, the first word from the wordlist is assigned to the variable, and then shifted off the list. Once the word is assigned to the variable, the body of the loop is entered, and commands between the do and done keywords are executed. The next time around the loop, the second word is assigned to the variable, and so on. The body of the loop starts at the do keyword and ends at the done keyword. When all of the words in the list have been shifted off, the loop ends and program control continues after the done keyword.

FORMAT

for variable in word_list do    command(s) done 
Example 12.32
(The Script)     #!/bin/bash     # Scriptname: forloop 1   for pal in Tom Dick Harry Joe 2   do 3       echo "Hi $pal" 4   done 5   echo "Out of loop" (The Output) $ forloop Hi Tom Hi Dick Hi Harry Hi Joe Out of loop 

EXPLANATION

  1. This for loop will iterate through the list of names, Tom, Dick, Harry, and Joe, shifting each one off (to the left and assigning its value to the user-defined variable, pal) after it is used. As soon as all of the words are shifted and the wordlist is empty, the loop ends and execution starts after the done keyword. The first time in the loop, the variable pal will be assigned the word Tom. The second time through the loop, pal will be assigned Dick, the next time pal will be assigned Harry, and the last time pal will be assigned Joe.

  2. The do keyword is required after the wordlist. If it is used on the same line, the list must be terminated with a semicolon. Example:

    for pal in Tom Dick Harry Joe; do 
  3. This is the body of the loop. After Tom is assigned to the variable pal, the commands in the body of the loop (i.e., all commands between the do and done keywords) are executed.

  4. The done keyword ends the loop. Once the last word in the list (Joe) has been assigned and shifted off, the loop exits, and execution starts at line 2.

  5. Control resumes here when the loop exits.

Example 12.33
(The Command Line) 1   $ cat mylist     tom     patty     ann     jake (The Script)     #!/bin/bash     # Scriptname: mailer 2   for person in $(cat mylist)     do 3      mail $person < letter        echo $person was sent a letter. 4   done 5   echo "The letter has been sent." 

EXPLANATION

  1. The contents of a file called mylist are displayed.

  2. Command substitution is performed and the contents of mylist becomes the wordlist. The first time in the loop, tom is assigned to the variable person, then it is shifted off to be replaced with patty, and so forth.

  3. In the body of the loop, each user is mailed a copy of a file called letter.

  4. The done keyword marks the end of this loop iteration.

  5. When all of the users in the list have been sent mail and the loop has exited, this line is executed.

Example 12.34
(The Script)     #!/bin/bash     # Scriptname: backup     # Purpose: Create backup files and store     # them in a backup directory.     # 1   dir=/home/jody/ellie/backupscripts 2   for file in memo[1-5]     do 3      if [ -f $file ]        then             cp $file $dir/$file.bak             echo "$file is backed up in $dir"        fi 4   done (The Output) memo1 is backed up in /home/jody/ellie/backupscripts memo2 is backed up in /home/jody/ellie/backupscripts memo3 is backed up in /home/jody/ellie/backupscripts memo4 is backed up in /home/jody/ellie/backupscripts memo5 is backed up in /home/jody/ellie/backupscripts 

EXPLANATION

  1. The variable dir is assigned the directory where the backup scripts are to be stored.

  2. The wordlist will consist of all files in the current working directory with names starting with memo and ending with numbers between 1 and 5. Each filename will be assigned, one at time, to the variable file for each iteration of the loop.

  3. When the body of the loop is entered, the file will be tested to make sure it exists and is a real file. If so, it will be copied into the directory /home/jody/ellie/backupscripts with the .bak extension appended to its name.

  4. The done marks the end of the loop.

The $* and $@ Variables in Wordlists. When expanded, the $* and $@ are the same unless enclosed in double quotes. $* evaluates to one string, whereas $@ evaluates to a list of separate words.

Example 12.35
(The Script)     #!/bin/bash     # Scriptname: greet 1   for name in $*        # same as for name in $@ 2   do        echo Hi $name 3   done (The Command Line) $ greet Dee Bert Lizzy Tommy Hi Dee Hi Bert Hi Lizzy Hi Tommy 

EXPLANATION

  1. $* and $@ expand to a list of all the positional parameters, in this case, the arguments passed in from the command line: Dee, Bert, Lizzy, and Tommy. Each name in the list will be assigned, in turn, to the variable name in the for loop.

  2. The commands in the body of the loop are executed until the list is empty.

  3. The done keyword marks the end of the loop body.

Example 12.36
(The Script)     #!/bin/bash     # Scriptname: permx 1   for file         # Empty wordlist     do 2     if [ -f $file -a ! -x $file ] or if [[ -f $file && ! -x $file ]][a]       then 3          chmod +x $file            echo $file now has execute permission       fi     done (The Command Line) 4   $ permx *     addon now has execute permission     checkon now has execute permission     doit now has execute permission 

[a] Bash 2.x only.

EXPLANATION

  1. If the for loop is not provided with a wordlist, it iterates through the positional parameters. This is the same as for file in $*.

  2. The filenames are coming in from the command line. The shell expands the asterisk (*) to all filenames in the current working directory. If the file is a plain file and does not have execute permission, line 3 is executed.

  3. Execute permission is added for each file being processed.

  4. At the command line, the asterisk will be evaluated by the shell as a wildcard and all files in the current directory will be replaced for the *. The files will be passed as arguments to the permx script.

12.6.2 The while Command

The while command evaluates the command following it and, if its exit status is zero, the commands in the body of the loop (commands between do and done) are executed. When the done keyword is reached, control is returned to the top of the loop and the while command checks the exit status of the command again. Until the exit status of the command being evaluated by the while becomes nonzero, the loop continues. When the exit status reaches nonzero, program execution starts after the done keyword.

FORMAT

while command do    command(s) done 
Example 12.37
(The Script)     #!/bin/bash     # Scriptname: num 1   num=0      # Initialize num 2   while (( $num < 10 ))[a]  # or  while [ num -lt 10 ]     do        echo  -n "$num " 3      let num+=1           # Increment num     done 4   echo -e "\nAfter loop exits, continue running here" (The Output)     0 1 2 3 4 5 6 7 8 9 4   After loop exits, continue running here 

[a] Versions of bash 2.x use this form.

EXPLANATION

  1. This is the initialization step. The variable num is assigned 0.

  2. The while command is followed by the let command. The let command evaluates the arithmetic expression, returning an exit status of 0 (true) if the condition is true; i.e., if the value of num is less than 10, the body of the loop is entered.

  3. In the body of the loop, the value of num is incremented by one. If the value of num never changes, the loop would iterate infinitely or until the process is killed.

  4. After the loop exits, the echo command (with the e option) prints a newline and the string.

Example 12.38
(The Script)     #!/bin/bash     # Scriptname: quiz 1   echo "Who was the 2nd U.S. president to be impeached?"     read answer 2   while [[ "$answer" != "Bill Clinton" ]] 3   do          echo "Wrong try again!" 4        read answer 5   done 6   echo You got it! (The Output) $ quiz Who was the 2nd U.S. president to be impeached? Ronald Reagan Wrong try again! Who was the 2nd U.S. president to be impeached?  I give up Wrong try again! Who was the 2nd U.S. president to be impeached?  Bill Clinton You got it! 

EXPLANATION

  1. The echo command prompts the user, Who was the 2nd U.S. president to be impeached? The read command waits for input from the user. The input will be stored in the variable answer.

  2. The while loop is entered and the test command, the bracket, tests the expression. If the variable answer does not exactly equal the string Bill Clinton, the body of the loop is entered and commands between the do and done are executed.

  3. The do keyword is the start of the loop body.

  4. The user is asked to re-enter input.

  5. The done keyword marks the end of the loop body. Control is returned to the top of the while loop, and the expression is tested again. As long as answer does not evaluate to Bill Clinton, the loop will continue to iterate. When the user enters Bill Clinton, the loop ends. Program control goes to line 6.

  6. When the body of the loop ends, control starts here.

Example 12.39
(The Script) $ cat sayit     #!/bin/bash     # Scriptname: sayit     echo  Type q to quit.     go=start 1   while [ -n "$go" ]   # Make sure to double quote the variable     do 2      echo -n I love you. 3      read word 4      if [[ $word == [Qq] ]]        then         # [ "$word" = q -o "$word" = Q ]  Old style            echo "I'll always love you!"            go=        fi     done (The Output) $ sayit Type q to quit. I love you.         <-- When user presses Enter, the program continues I love you. I love you I love you. I love you.q I'll always love you! $ 
  1. The command after the while is executed and its exit status tested. The n option to the test command tests for a non-null string. Because go initially has a value, the test is successful, producing a zero exit status. If the variable go is not enclosed in double quotes and the variable is null, the test command would complain

    go: test: argument expected 
  2. The loop is entered. The string I love you is echoed to the screen.

  3. The read command waits for user input.

  4. The expresson is tested. If the user enters a q or Q, the string I'll always love you! is displayed, and the variable go is set to null. When the while loop is re-entered, the test is unsuccessful because the variable is null. The loop terminates. Control goes to the line after the done statement. In this example, the script will terminate because there are no more lines to execute.

12.6.3 The until Command

The until command is used like the while command, but executes the loop statements only if the command after until fails, i.e., if the command returns an exit status of nonzero. When the done keyword is reached, control is returned to the top of the loop and the until command checks the exit status of the command again. Until the exit status of the command being evaluated by until becomes zero, the loop continues. When the exit status reaches zero, the loop exits, and program execution starts after the done keyword.

FORMAT

until command do    command(s) done 
Example 12.40
    #!/bin/bash 1   until who | grep linda 2   do        sleep 5 3   done     talk linda@dragonwings 

EXPLANATION

  1. The until loop tests the exit status of the last command in the pipeline, grep. The who command lists who is logged on this machine and pipes its output to grep. The grep command will return a zero exit status (success) only when it finds user linda.

  2. If user linda has not logged on, the body of the loop is entered and the program sleeps for five seconds.

  3. When linda logs on, the exit status of the grep command will be zero and control will go to the statements following the done keyword.

Example 12.41
(The Script) $ cat hour     #!/bin/bash     # Scriptname: hour 1   let hour=0 2   until (( hour > 24 ))[a] # or [ $hour -gt 24 ]     do 3      case "$hour" in        [0-9]|1[0-1])   echo "Good morning!"             ;;        12) echo "Lunch time."            ;;        1[3-7]) echo "Siesta time."            ;;        *)  echo "Good night."            ;;        esac 4      let hour+=1    # Don't forget to increment the hour 5   done (The Output) $ hour Good morning! Good morning     ... Lunch time. Siesta time.     ... Good night.     ... 

[a] Versions of bash 2.x use this form.

EXPLANATION

  1. The variable hour is initialized to 0.

  2. The let command tests the arithmetic expression to test for an hour greater than or equal to 24. If the hour is not greater than or equal to 24, the body of the loop is entered. The until loop is entered if the command following it returns a nonzero exit status. Until the condition is true, the loop continues to iterate.

  3. The case command evaluates the hour variable and tests each of the case statements for a match.

  4. The hour variable is incremented with the let command before control returns to the top of the loop.

  5. The done command marks the end of the loop body.

12.6.4 The select Command and Menus

The here document is an easy method for creating menus, but bash introduces another looping mechanism, called the select loop, used primarily for creating menus. A menu of numerically listed items is displayed to standard error. The PS3 prompt is used to prompt the user for input; by default, PS3 is #?. After the PS3 prompt is displayed, the shell waits for user input. The input should be one of the numbers in the menu list. The input is stored in the special shell REPLY variable. The number in the REPLY variable is associated with the string to the right of the parentheses in the list of selections.

The case command is used with the select command to allow the user to make a selection from the menu and, based on that selection, execute commands. The LINES and COLUMNS variables can be used to determine the layout of the menu items displayed on the terminal. (These variables are built in to versions of bash 2.x, but are not built in to earlier versions; if they have not been defined, you can define and export them in the .bash_profile.) The output is displayed to standard error, each item preceded by a number and closing parenthesis, and the PS3 prompt is displayed at the bottom of the menu. Because the select command is a looping command, it is important to remember to use either the break command to get out of the loop, or the exit command to exit the script.

FORMAT

select var in wordlist do    command(s) done 
Example 12.42
(The Script)     #!/bin/bash     # Scriptname: runit 1   PS3="Select a program to execute: " 2   select program  in 'ls -F' pwd date 3   do 4       $program 5   done (The Command Line) Select a program to execute: 2 1) ls -F 2) pwd 3) date /home/ellie Select a program to execute: 1 1) ls -F 2) pwd 3) date 12abcrty abc12  doit* progs/  xyz Select a program to execute: 3 1) ls -F 2) pwd 3) date Sun Mar 12 13:28:25 PST 2001 

EXPLANATION

  1. The PS3 prompt is assigned the string that will appear below the menu that the select loop displays. This prompt is $# by default and is sent to standard error, the screen.

  2. The select loop consists of a variable called program and the three-word list that will be displayed in the menu: ls F, pwd, and date. The words in this list are UNIX commands, but they could be any words, e.g., red, green, yellow, or cheese, bread, milk, crackers. If the word has a space, it must be quoted; e.g., 'ls F'

  3. The do keyword starts the body of the select loop.

  4. When the user selects numbers in the menu, that number will be equated with the word value to the right of the number after the parentheses. For example, if he selects number 2, that is associated with pwd and pwd is stored in the program variable. $program evaluates to the executable command, pwd; the command is executed.

  5. The done keyword marks the end of the body of statements in the select loop. Control will return to the top of the loop. This loop will continue to execute until the user presses ^C.

Example 12.43
(The Script)     #!/bin/bash     # Scriptname name: goodboys 1   PS3="Please choose one of the three boys : " 2   select choice in tom dan guy 3   do 4      case $choice in        tom)           echo Tom is a cool dude! 5         break;;           # break out of the select loop 6      dan | guy )           echo Dan and Guy are both wonderful.           break;;        *) 7         echo "$REPLY is not one of your choices" 1>&2           echo "Try again."           ;; 8      esac 9   done (The Command Line) $ goodboys 1) tom 2) dan 3) guy Please choose one of the three boys : 2 Dan and Guy are both wonderful. $ goodboys 1) tom 2) dan 3) guy Please choose one of the three boys : 4 4 is not one of your choices Try again. Please choose one of the three boys : 1 Tom is a cool dude! $ 

EXPLANATION

  1. The PS3 prompt will be printed above the menu created by the select loop on line 2.

  2. The select loop is entered. It causes the words in its list to be displayed as a numbered menu.

  3. The loop body starts here.

  4. The variable choice is assigned the first value on the list, after which the value is shifted off the list and the next item will be first.

  5. The break statement sends loop control after line 9.

  6. If either dan or guy are selected, the following echo command is executed, followed by the break command, sending control after line 9.

  7. The built-in REPLY variable contains the number of the current list item; i.e., 1, 2, or 3.

  8. This marks the end of the case command.

  9. The done marks the end of the select loop.

Example 12.44
(The Script)     #!/bin/bash     # Scriptname: ttype     # Purpose: set the terminal type     # Author: Andy Admin 1   COLUMNS=60 2   LINES=1 3   PS3="Please enter the terminal type: " 4   select choice in wyse50 vt200 xterm sun     do 5      case $REPLY in        1) 6          export TERM=$choice            echo "TERM=$choice"            break;;            # break out of the select loop        2 | 3 )            export TERM=$choice            echo "TERM=$choice"            break;;        4)            export TERM=$choice            echo "TERM=$choice"            break;;        *) 7          echo -e "$REPLY is not a valid choice. Try again\n" 1>&2 8          REPLY=    # Causes the menu to be redisplayed               ;;        esac 9   done (The Command Line) $ ttype 1) wyse50    2) vt200   3) xterm    4) sun Please enter the terminal type : 4 TERM=sun $ ttype 1) wyse50    2) vt200   3) xterm    4) sun Please enter the terminal type : 3 TERM=xterm $ ttype 1) wyse50    2) vt200   3) xterm    4) sun Please enter the terminal type : 7 7 is not a valid choice. Try again. 1) wyse50    2) vt200   3) xterm    4) sun Please enter the terminal type: 2 TERM=vt200 

EXPLANATION

  1. The COLUMNS variable is set to the width of the terminal display in columns for menus created with the select loop. The default is 80.

  2. The LINES variable controls the vertical display of the select menu on the terminal. The default is 24 lines. When you change the LINES value to 1, the menu items will be printed on one line, instead of vertically as in the last example.

  3. The PS3 prompt is set and will appear under the menu choices.

  4. The select loop will print a menu with four selections: wyse50, vt200, xterm, and sun. The variable choice will be assigned one of these values based on the user's response held in the REPLY variable. If REPLY is 1, wyse50 is assigned to choice; if REPLY is 2, vt200 is assigned to choice; if REPLY is 3, xterm is assigned to choice; and if REPLY is 4, sun is assigned to choice.

  5. The REPLY variable evaluates to the user's input selection.

  6. The terminal type is assigned, exported, and printed.

  7. If the user does not enter a number between 1 and 4, he or she will be prompted again. Note that the menu does not appear, just the PS3 prompt.

  8. If the REPLY variable is set to null, e.g., REPLY=, the menu will be redisplayed.

  9. The end of the select loop.

12.6.5 Loop Control

If some condition occurs, you may want to break out of a loop, return to the top of the loop, or provide a way to stop an infinite loop. The bash shell provides loop control commands to handle these kinds of situations.

The shift Command. The shift command shifts the parameter list to the left a specified number of times. The shift command without an argument shifts the parameter list once to the left. Once the list is shifted, the parameter is removed permanently. Often, the shift command is used in a while loop when iterating through a list of positional parameters.

FORMAT

shift [n] 
Example 12.45
(Without a Loop) (The Script)     #!/bin/bash     # Scriptname: shifter 1   set joe mary tom sam 2   shift 3   echo $* 4   set $(date) 5   echo  $* 6   shift 5 7   echo  $* 8   shift 2 (The Output) 3   mary tom sam 5   Thu Mar 16 10:00:12 PST 2001 7   2001 8   shift: shift count must be <= $# 

EXPLANATION

  1. The set command sets the positional parameters. $1 is assigned joe, $2 is assigned mary, $3 is assigned tom, and $4 is assigned sam. $* represents all of the parameters.

  2. The shift command shifts the positional parameters to the left; joe is shifted off.

  3. The parameter list is printed after the shift.

  4. The set command resets the positional parameters to the output of the UNIX date command.

  5. The new parameter list is printed.

  6. This time the list is shifted 5 times to the left.

  7. The new parameter list is printed.

  8. By attempting to shift more times than there are parameters, the shell sends a message to standard error stating that the shift command cannot shift more off more parameters that it has. $# is the total number of positional parameters. On versions of bash 2.x, no error message occurs.

Example 12.46
(With a Loop) (The Script)     #!/bin/bash     # Name: doit     # Purpose: shift through command line arguments     # Usage: doit [args] 1    while (( $# > 0 ))[a]  # or [ $# -gt 0 ]      do 2       echo  $* 3       shift 4    done (The Command Line) $ doit a b c d e a b c d e b c d e c d e d e e 

[a] Not implemented of versions of bash prior to 2.x.

EXPLANATION

  1. The let command tests the numeric expression. If the number of positional parameters ($#) is greater than 0, the body of the loop is entered. The positional parameters are coming from the command line as arguments. There are five.

  2. All positional parameters are printed.

  3. The parameter list is shifted once to the left.

  4. The body of the loop ends here; control returns to the top of the loop. Each time the loop is entered, the shift command causes the parameter list to be decreased by one. After the first shift, $# (number of positional parameters) is four. When $# has been decreased to zero, the loop ends.

Example 12.47
(The Script)     #!/bin/bash     # Scriptname: dater     # Purpose: set positional parameters with the set command     # and shift through the parameters. 1   set $(date) 2   while (( $# > 0 )) # or  [ $# -gt 0 ]  Old style     do 3       echo $1 4       shift     done (The Output) $ dater Wed Mar 15 19:25:00 PST 2001 

EXPLANATION

  1. The set command takes the output of the date command and assigns the output to positional parameters $1 through $6.

  2. The while command tests whether the number of positional parameters ($#) is greater than 0. If true, the body of the loop is entered.

  3. The echo command displays the value of $1, the first positional parameter.

  4. The shift command shifts the parameter list once to the left. Each time through the loop, the list is shifted until the list is empty. At that time, $# will be zero and the loop terminates.

The break Command. The built-in break command is used to force immediate exit from a loop, but not from a program. (To leave a program, the exit command is used.) After the break command is executed, control starts after the done keyword. The break command causes an exit from the innermost loop, so if you have nested loops, the break command takes a number as an argument, allowing you to break out of a specific outer loop. If you are nested in three loops, the outermost loop is loop number 3, the next nested loop is loop number 2, and the innermost nested loop is loop number 1. The break is useful for exiting from an infinite loop.

FORMAT

break [n] 
Example 12.48

graphics/12prfig01.gif

EXPLANATION

  1. The true command is a UNIX command that always exits with zero status. It is often used to start an infinite loop. It is okay to put the do statement on the same line as the while command, as long as a semicolon separates them. The body of the loop is entered.

  2. The user is asked for input. The user's input is assigned to the variable answer.

  3. If $answer evaluates to Y or y, control goes to line 4.

  4. The break command is executed, the loop is exited, and control goes to line 7. The line Here we are is printed. Until the user answers with a Y or y, the program will continue to ask for input. This could go on forever!

  5. If the test fails in line 3, the else commands are executed. When the body of the loop ends at the done keyword, control starts again at the top of the while at line 1.

  6. This is the end of the loop body.

  7. Control starts here after the break command is executed.

The continue Command. The continue command returns control to the top of the loop if some condition becomes true. All commands below the continue will be ignored. If nested within a number of loops, the continue command returns control to the innermost loop. If a number is given as its argument, control can then be started at the top of any loop. If you are nested in three loops, the outermost loop is loop number 3, the next nested loop is loop number 2, and the innermost nested loop is loop number 1.[4]

FORMAT

continue [n] 
Example 12.49

graphics/12prfig02.gif

EXPLANATION

  1. After command substitution, $(cat mail_list) or 'cat mail_list', the for loop will iterate through the list of names from the file called mail_list.

  2. If the name matches richard, the continue command is executed and control goes back to top of the loop where the loop expression is evaluated. Because richard has already been shifted off the list, the next user, melanie, will be assigned to the variable name. Old style: if [ "$name" = richard ] ; then

  3. The continue command returns control to the top of the loop, skipping any commands in the rest of the loop body.

  4. All users in the list, except richard, will be mailed a copy of the file memo.

  5. This is the end of the loop body.

Nested Loops and Loop Control. When you are using nested loops, the break and continue commands can be given a numeric, integer argument so that control can go from the inner loop to an outer loop.

Example 12.50

graphics/12prfig03.gif

EXPLANATION

  1. The outer for loop is started. The first time in the loop, Jan is assigned to month.

  2. The inner for loop starts. The first time in this loop, 1 is assigned to week. The inner loop iterates completely before going back to the outer loop.

  3. If the user enters either an n or presses Enter, line 4 is executed.

  4. The continue command with an argument of 2 starts control at the top of the second outermost loop. The continue without an argument returns control to the top of the innermost loop.

  5. Control is returned to the innermost for loop.

  6. This done terminates the innermost loop.

  7. This done terminates the outermost loop.

12.6.6 I/O Redirection and Subshells

Input can be piped or redirected to a loop from a file. Output can also be piped or redirected to a file from a loop. The shell starts a subshell to handle I/O redirection and pipes. Any variables defined within the loop will not be known to the rest of the script when the loop terminates.

Redirecting the Output of the Loop to a File. Output from a bash loop can be sent to a file rather than to the screen. See Example 12.51.

Example 12.51
(The Command Line) 1   $ cat memo     abc     def     ghi (The Script)     #!/bin/bash     # Program name: numberit     # Put line numbers on all lines of memo 2   if let $(( $# <  1 ))     then 3       echo "Usage: $0 filename " >&2         exit 1     fi 4   count=1                      # Initialize count 5   cat $1 | while read line     # Input is coming from file provided at command line     do 6      let $((count == 1)) && echo "Processing file $1..." > /dev/tty 7      echo -e "$count\t$line" 8      let count+=1 9   done > tmp$$              # Output is going to a temporary file 10  mv tmp$$ $1 (The Command Line) 11  $ numberit memo     Processing file memo... 12  $ cat memo     1   abc     2   def     3   ghi 

EXPLANATION

  1. The contents of file memo are displayed.

  2. If the user did not provide a command line argument when running this script, the number of arguments ($#) will be less than one and the error message appears.

  3. The usage message is sent to stderr (>&2) if the number of arguments is less than 1.

  4. The count variable is assigned the value 1.

  5. The UNIX cat command displays the contents of the filename stored in $1, and the output is piped to the while loop. The read command is assigned the first line of the file the first time in the loop, the second line of the file the next time through the loop, and so forth. The read command returns a zero exit status if it is successful in reading input and one if it fails.

  6. If the value of count is 1, the echo command is executed and its output is sent to /dev/tty, the screen.

  7. The echo command prints the value of count, followed by the line in the file.

  8. The count is incremented by one.

  9. The output of this entire loop, each line of the file in $1, is redirected to the file tmp$$, with the exception of the first line of the file, which is redirected to the terminal, /dev/tty.[a]

  10. The tmp file is renamed to the filename assigned to $1.

  11. The program is executed. The file to be processed is called memo.

  12. The file memo is displayed after the script has finished, demonstrating that line numbers have been prepended to each line.

[a] $$ expands to the PID number of the current shell. By appending this number to the filename, the filename is made unique.

Piping the Output of a Loop to a UNIX Command. Output can be either piped to another command(s) or redirected to a file.

Example 12.52
(The Script)     #!/bin/bash 1   for i in 7 9 2 3 4  5 2   do        echo  $i 3   done | sort  n (The Output) 2 3 4 5 7 9 

EXPLANATION

  1. The for loop iterates through a list of unsorted numbers.

  2. In the body of the loop, the numbers are printed. This output will be piped into the UNIX sort command, a numerical sort.

  3. The pipe is created after the done keyword. The loop is run in a subshell.

Running Loops in the Background. Loops can be executed to run in the background. The program can continue without waiting for the loop to finish processing.

Example 12.53
(The Script)     #!/bin/bash 1   for person in bob jim joe sam     do 2       mail $person < memo 3   done & 

EXPLANATION

  1. The for loop shifts through each of the names in the wordlist: bob, jim, joe, and sam. Each of the names is assigned to the variable person, in turn.

  2. In the body of the loop, each person is sent the contents of the memo file.

  3. The ampersand at the end of the done keyword causes the loop to be executed in the background. The program will continue to run while the loop is executing.

12.6.7 IFS and Loops

The shell's internal field separator (IFS) evaluates to spaces, tabs, and the newline character. It is used as a word (token) separator for commands that parse lists of words, such as read, set, and for. It can be reset by the user if a different separator will be used in a list. Before changing its value, it is a good idea to save the original value of the IFS in another variable. Then it is easy to return to its default value, if needed.

Example 12.54
(The Script ) $ cat runit2     #/bin/bash     # Script is called runit.     # IFS is the internal field separator and defaults to     # spaces, tabs, and newlines.     # In this script it is changed to a colon. 1   names=Tom:Dick:Harry:John 2   oldifs="$IFS"           # Save the original value of IFS 3   IFS=":" 4   for persons in $names     do 5       echo  Hi $persons     done 6   IFS="$oldifs"            # Reset the IFS to old value 7   set Jill Jane Jolene     # Set positional parameters 8   for girl in $*     do 9       echo Howdy $girl     done (The Output)     $ runit2 5   Hi Tom     Hi Dick     Hi Harry     Hi John 9   Howdy Jill     Howdy Jane     Howdy Jolene 

EXPLANATION

  1. The names variable is set to the string Tom:Dick:Harry:John. Each of the words is separated by a colon.

  2. The value of IFS, whitespace, is assigned to another variable, oldifs. Since the value of the IFS is whitespace, it must be quoted to preserve it.

  3. The IFS is assigned a colon. Now the colon is used to separate words.

  4. After variable substitution, the for loop will iterate through each of the names, using the colon as the internal field separator between the words.

  5. Each of the names in the wordlist are displayed.

  6. The IFS is reassigned its original value stored in oldifs.

  7. The positional parameters are set. $1 is assigned Jill, $2 is assigned Jane, and $3 is assigned Jolene.

  8. $* evaluates to all the positional parameters, Jill, Jane, and Jolene. The for loop assigns each of the names to the girl variable, in turn, through each iteration of the loop.

  9. Each of the names in the parameter list is displayed.

12.7 Functions

Functions were introduced to the Bourne shell in AT&T's UNIX SystemVR2 and have been enhanced in the Bourne Again shell. A function is a name for a command or group of commands. Functions are used to modularize your program and make it more efficient. They are executed in context of the current shell. In other words, a child process is not spawned as it is when running an executable program such as ls. You may even store functions in another file and load them into your script when you are ready to use them.

Here is a review of some of the important rules about using functions.

  1. The shell determines whether you are using an alias, a function, a built-in command, or an executable program (or script) found on the disk. It looks for aliases first, then functions, built-in commands, and last, executables.

  2. A function must be defined before it is used.

  3. The function runs in the current environment; it shares variables with the script that invoked it, and lets you pass arguments by assigning them as positional parameters. Local variables can be created within a function by using the local function.

  4. If you use the exit command in a function, you exit the entire script. If you exit the function, you return to where the script left off when the function was invoked.

  5. The return statement in a function returns the exit status of the last command executed within the function or the value of the argument given.

  6. Functions can be exported to subshells with the export f built-in command.

  7. To list functions and definitions, use the declare f command. To list just function names, use declare F.[5]

  8. Traps, like variables, are global within functions. They are shared by both the script and the functions invoked in the script. If a trap is defined in a function, it is also shared by the script. This could have unwanted side effects.

  9. If functions are stored in another file, they can be loaded into the current script with the source or dot command.

  10. Functions can be recursive; i.e., they can call themselves. There is no limit imposed for the number of recursive calls.

FORMAT

function function_name { commands ; commands; } 
Example 12.55
function dir { echo "Directories: ";ls -l|awk '/^d/ {print $NF}'; } 

EXPLANATION

The keyword function is followed by the name of the function dir. (Sometimes empty parentheses follow the function name, but they are not necessary.) The commands within the curly braces will be executed when dir is typed. The purpose of the function is to list only the subdirectories below the present working directory. The spaces surrounding the first curly brace are required.

To Unset a Function. To remove a function from memory, use the unset command.

FORMAT

unset -f function_name 

Exporting Functions. Functions may be exported so that subshells know about them.

FORMAT

export -f function_name 

12.7.1 Function Arguments and the Return Value

Because the function is executed within the current shell, the variables will be known to both the function and the shell. Any changes made to your environment in the function will also be made to the shell.

Arguments. Arguments can be passed to functions by using positional parameters. The positional parameters are private to the function; that is, arguments to the function will not affect any positional parameters used outside the function. See Example 12.56.

The Built-In local Function. To create local variables that are private to the function and will disappear after the function exits, use the built-in local function. See Example 12.57.

The Built-In return Function. The return command can be used to exit the function and return control to the program at the place where the function was invoked. (Remember, if you use exit anywhere in your script, including within a function, the script terminates.) The return value of a function is really just the value of the exit status of the last command in the script, unless you give a specific argument to the return command. If a value is assigned to the return command, that value is stored in the ? variable and can hold an integer value between zero and 256. Because the return command is limited to returning only an integer between zero and 256, you can use command substitution to capture the output of a function. Place the entire function in parentheses preceded by a $, e.g., $(function_name), or traditional backquotes to capture and assign the output to a variable just as you would if getting the output of a UNIX command.

Example 12.56
(Passing Arguments) (The Script)     #!/bin/bash     # Scriptname: checker     # Purpose: Demonstrate function and arguments 1   function Usage { echo "error: $*" 2>&1; exit 1; } 2   if (( $# != 2 ))     then 3       Usage "$0: requires two arguments"     fi 4   if [[ ! ( -r $1 && -w $1 ) ]]     then 5       Usage "$1: not readable and writeable"     fi 6   echo The arguments are: $*  <  Program continues here > (Output) $ checker error: checker: requires two arguments $ checker file1 file2 error: file1: not readable and writeable $ checker filex file2 The arguments are filex file2 

EXPLANATION

1. The function called Usage is defined. It will be used to send an error message to standard error (the screen). Its arguments consist of any string of characters sent when the function is called. The arguments are stored in $*, the special variable that contains all positional parameters. Within a function, positional parameters are local and have no effect on those positional parameters used outside the function.

2. If the number of arguments begin passed into the script from the command line does not equal 2, the program branches to line 3.

3. When the Usage function is called, the string $0: requires two arguments is passed to the function and stored in the $* varable. The echo statement will then send the message to standard error and the script will exit with an exit status of 1 to indicate that something went wrong.[a]

4,5. If the first argument coming into the program from the command line is not the name of a readable and writeable file, the Usage function will be called with $1: not readable and writeable as its argument.

6. The arguments coming into the script from the command line are stored in $*. This has no effect on the $* in the function.

[a] With the old test form, the expression is written if [ ! \( -r $1 -a -w $1 \) ].

Example 12.57
(Using the return Command) (The Script) $ cat do_increment     #!/bin/bash     # Scriptname: do_increment 1   increment ()  { 2      local sum;     # sum is known only in this function 3      let "sum=$1 + 1" 4      return $sum    # Return the value of sum to the script.     } 5   echo   n "The sum is " 6   increment 5    # Call function increment; pass 5 as a                    # parameter. 5 becomes $1 for the increment                    # function. 7   echo $?        # The return value is stored in $? 8   echo  $sum     # The variable "sum" is not known here (The Output)     $ do_increment 4,6 The sum is 6 8 

EXPLANATION

  1. The function called increment is defined.

  2. The built-in local function makes variable sum local (private) to this function. It will not exist outside the function. When the function exits, it will be removed.

  3. When the function is called, the value of the first argument, $1, will be incremented by one and the result assigned to sum.

  4. The return built-in command, when given an argument, returns to the main script after the line where the function was invoked. It stores its argument in the ? variable.

  5. The string is echoed to the screen.

  6. The increment function is called with an argument of 5.

  7. When the function returns, its exit status is stored in the ? variable. The exit status is the exit value of the last command executed in the function unless an explicit argument is used in the return statement. The argument for return must be an integer between 0 and 255.

  8. Although the sum was defined in the function increment, it is local in scope, and therefore also not known outside the function. Nothing is printed.

Example 12.58
(Using Command Substitution) (The Script) $ cat do_square     #!/bin/bash     # Scriptname: do_square 1   function square {       local sq     # sq is local to the function       let "sq=$1 * $1"       echo "Number to be squared is $1." 2     echo "The result is $sq "     } 3   echo "Give me a number to square. "     read number 4   value_returned=$(square $number) # Command substitution 5   echo   "$value_returned" (The Output)     $ do_square 3   Give me a number to square.     10 5   Number to be squared is 10.     The result is 100 

EXPLANATION

  1. The function called square is defined. Its purpose, when called, is to multiply its argument, $1, times itself.

  2. The result of squaring the number is printed.

  3. The user is asked for input. This is the line where the program starts executing.

  4. The function square is called with a number (input from the user) as its argument. Command substitution is performed because the function is enclosed in parentheses preceded by a $. The output of the function, both of its echo statements, is assigned to the variable value_returned.

  5. The value returned from the command substitution is printed.

12.7.2 Functions and the source (or dot) Command

Storing Functions. Functions are often defined in the .profile file so that when you log in, they will be defined. Functions can be exported, and they can be stored in a file. Then when you need the function, the source or dot command is used with the name of the file to activate the definitions of the functions within it.

Example 12.59
1   $ cat myfunctions 2   function go() {        cd $HOME/bin/prog        PS1=''pwd' > '        ls     } 3   function greetings() { echo "Hi $1! Welcome to my world." ; } 4   $ source myfunctions 5   $ greetings george     Hi george! Welcome to my world. 

EXPLANATION

  1. The file myfunctions is displayed. It contains two function definitions.

  2. The first function defined is called go. It sets the primary prompt to the present working directory.

  3. The second function defined is called greetings. It will greet the name of the user passed in as an argument.

  4. The source or dot command loads the contents of the file myfunctions into the shell's memory. Now both functions are defined for this shell.

  5. The greetings function is invoked and executed.

Example 12.60
(The dbfunctions file shown below contains functions to be used by the main program.  See cd for complete script.) 1   $ cat dbfunctions 2   function addon () {    # Function defined in file dbfunctions 3      while true        do           echo "Adding information "           echo "Type the full name of employee "           read name           echo "Type address for employee "           read address           echo "Type start date for employee (4/10/88 ) :"           read startdate           echo $name:$address:$startdate           echo  n "Is this correct? "           read ans           case "$ans"  in           [Yy]*)                   echo "Adding info..."                   echo $name:$address:$startdate>>datafile                   sort  u datafile  o datafile                   echo  n "Do you want to go back to the main menu? "              read ans              if [[ $ans == [Yy] ]]              then 4                return     # Return to calling program              else 5                continue   # Go to the top of the loop              fi              ;;           *)           echo "Do you want to try again? "           read answer           case "$answer" in           [Yy]*) continue;;           *) exit;;           esac              ;;        esac     done 6   }   # End of function definition ------------------------------------------------------------- (The Command Line) 7   $ more mainprog     #!/bin/bash     # Scriptname: mainprog     # This is the main script that will call the function, addon     datafile=$HOME/bourne/datafile 8   source dbfunctions   # The file is loaded into memory         if [ !  e $datafile ]     then         echo "$(basename $datafile) does not exist" >&2         exit 1     fi 9      echo "Select one: "        cat <<EOF            [1] Add info            [2] Delete info            [3] Update info            [4] Exit        EOF        read choice        case $choice in 10        1) addon       # Calling the addon function                  ;;           2) delete      # Calling the delete function                  ;;           3) update                  ;;           4)              echo Bye              exit 0                   ;;           *) echo Bad choice              exit 2                   ;;        esac        echo Returned from function call        echo The name is $name        # Variable set in the function are known in this shell.    done 

EXPLANATION

  1. The dbfunctions file is displayed.

  2. The addon function is defined. Its function is to add new information to the datafile.

  3. A while loop is entered. It will loop forever unless a loop control statement such as break or cotinue is included in the body of the loop.

  4. The return command sends control back to the calling program where the function was called.

  5. Control is returned to the top of the while loop.

  6. The closing curly brace ends the function definition.

  7. This is the main script. The function addon will be used in this script.

  8. The source command loads the file dbfunctions into the program's memory. Now the function addon is defined for this script and available for use. It is as though you had just defined the function right here in the script.

  9. A menu is displayed with the here document. The user is asked to select a menu item.

  10. The addon function is invoked.

12.8 Trapping Signals

While your program is running, if you press Control-C or Control-\, your program terminates as soon as the signal arrives. There are times when you would rather not have the program terminate immediately after the signal arrives. You could arrange to ignore the signal and keep running or perform some sort of cleanup operation before actually exiting the script. The trap command allows you to control the way a program behaves when it receives a signal.

A signal is defined as an asynchronous message that consists of a number that can be sent from one process to another, or by the operating system to a process if certain keys are pressed or if something exceptional happens.[6] The trap command tells the shell to terminate the command currently in execution upon the receipt of a signal. If the trap command is followed by commands within quotes, the command string will be executed upon receipt of a specified signal. The shell reads the command string twice, once when the trap is set, and again when the signal arrives. If the command string is surrounded by double quotes, all variable and command substitution will be performed when the trap is set the first time. If single quotes enclose the command string, variable and command substitution do not take place until the signal is detected and the trap is executed.

Use the command kill l or trap l to get a list of all signals. Table 12.6 provides a list of signal numbers and their corresponding names.

FORMAT

trap 'command; command' signal-number trap 'command; command' signal-name 
Example 12.61
trap 'rm tmp*; exit 1'  0 1 2 15 trap 'rm tmp*; exit 1'  EXIT HUP INT TERM 

EXPLANATION

When any of the signals 1 (hangup), 2 (interrupt), or 15 (software termination) arrives, remove all the tmp files and exit.

If an interrupt comes in while the script is running, the trap command lets you handle the interrupt signal in several ways. You can let the signal behave normally (default), ignore the signal, or create a handler function to be called when the signal arrives.

Signal names such as HUP and INT are normally prefixed with SIG, for example, SIGHUP, SIGINT, and so forth.[7] The bash shell allows you to use symbolic names for the signals, which are the signal names without the SIG prefix, or you can use the numeric value for the signal. See Table 12.6. A pseudo signal name EXIT, or the number 0, will cause the trap to be executed when the shell exits.

Table 12.6. Signal Numbers and Signals (kill l)
1) SIGHUP 9) SIGKILL 18) SIGCONT 26) SIGVTALRM
2) SIGINT 10) SIGUSR1 19) SIGSTOP 27) SIGPROF
3) SIGQUIT 11) SIGSEGV 20) SIGTSTP 28) SIGWINCH
4) SIGILL 12) SIGUSR2 21) SIGTTIN 29) SIGIO
5) SIGTRAP 13) SIGPIPE 22) SIGTTOU 30) SIGPWR
6) SIGABRT 14) SIGALRM 23) SIGURG  
7) SIGBUS 15) SIGTERM 24) SIGXCPU  
8) SIGFPE 17) SIGCHLD 25) SIGXFSZ  

Resetting Signals. To reset a signal to its default behavior, the trap command is followed by the signal name or number. Traps set in functions are recognized by the shell that invoked the function, once the function has been called. Any traps set outside the function are also recognized with the function.

Example 12.62
trap 2  or  trap INT 

EXPLANATION

Resets the default action for signal 2, SIGINT. The default action is to kill the process when the interrupt key (Control-C) is pressed.

Ignoring Signals . If the trap command is followed by a pair of empty quotes, the signals listed will be ignored by the process.

Example 12.63
trap " " 1 2  or    trap "" HUP INT 

EXPLANATION

Signals 1 (SIGHUP) and 2 (SIGINT) will be ignored by the shell process.

Listing Traps. To list all traps and the commands assigned to them, type trap.

Example 12.64
(At the command line) 1   $ trap 'echo "Caught ya!; exit"' 2 2   $ trap     trap -- 'echo "Caught ya!; exit 1"' SIGINT 3   $ trap - 

EXPLANATION

  1. The trap command is set to exit on signal 2 (Control-C).

  2. The trap command without an argument lists all set traps.

  3. If the argument is a dash, all signals are reset to their original values, i.e., whatever they were when the shell started up.

Example 12.65
(The Script)     #!/bin/bash     # Scriptname: trapping     # Script to illustrate the trap command and signals     # Can use the signal numbers or bash abbreviations seen     # below. Cannot use SIGINT, SIGQUIT, etc. 1   trap 'echo "Control C will not terminate $0."'  INT 2   trap 'echo "Control \ will not terminate $0."'  QUIT 3   trap 'echo "Control Z will not terminate $0."'  TSTP 4   echo  "Enter any string after the prompt.     When you are ready to exit, type \"stop\"." 5   while true     do 6       echo  n "Go ahead...> " 7       read 8       if [[ $REPLY == [Ss]top ]]         then 9          break         fi 10  done (The Output)     $ trapping 4   Enter any string after the prompt.     When you are ready to exit, type "stop". 6   Go ahead...> this is it^C 1   Control C will not terminate trapping. 6   Go ahead...> this is it again^Z 3   Control Z will not terminate trapping. 6   Go ahead...> this is never it|^\ 2   Control \ will not terminate trapping. 6   Go ahead...> stop     $ 

EXPLANATION

  1. The first trap catches the INT signal, Control-C. If Control-C is pressed while the program is running, the command enclosed in quotes will be executed. Instead of aborting, the program will print Control-C will not terminate trapping and continue to prompt the user for input.

  2. The second trap command will be executed when the user presses Control-\, the QUIT signal. The string Control-\ will not terminate trapping will be displayed and the program will continue to run. This signal, SIGQUIT by default, kills the process and produces a core file.

  3. The third trap command will be executed when the user presses Control-Z, the TSTP signal. The string Control-Z will not terminate trapping will be displayed, and the program will continue to run. This signal normally causes the program to be suspended in the background if job control is implemented.

  4. The user is prompted for input.

  5. The while loop is entered.

  6. The string Go ahead > is printed and the program waits for input (see read on the next line).

  7. The read command assigns user input to the built-in REPLY variable.

  8. If the value of REPLY matches Stop or stop, the break command causes the loop to exit and the program will terminate. Entering Stop or stop is the only way we will get out of this program unless it is killed with the kill command.

  9. The break command causes the body of the loop to be exited.

  10. The done keyword marks the end of the loop.

Resetting Signals. To reset a signal to its default behavior, the trap command is followed by the signal name or number.

Example 12.66
trap 2 

EXPLANATION

Resets the default action for signal 2, SIGINT, which is used to kill a process, i.e., Control-C.

Example 12.67
trap 'trap 2' 2 

EXPLANATION

Sets the default action for signal 2 (SIGINT) to execute the command string within quotes when the signal arrives. The user must press Control-C twice to terminate the program. The first trap catches the signal, the second trap resets the trap back to its default action, which is to kill the process.

Traps in Functions. If you use a trap to handle a signal in a function, it will affect the entire script, once the function is called. The trap is global to the script. In the following example, the trap is set to ignore the interrupt key, ^C. This script had to be killed with the kill command to stop the looping. It demonstrates potential undesirable side effects when using traps in functions.

Example 12.68
(The Script)     #!/bin/bash 1   function trapper () {        echo "In trapper" 2      trap 'echo "Caught in a trap!"' INT        # Once set, this trap affects the entire script. Anytime        # ^C is entered, the script will ignore it.     } 3   while :     do        echo "In the main script" 4      trapper 5      echo "Still in main"        sleep 5     done (The Output) $ trapper In the main script In trapper Still in main ^CCaught in a trap! In the main script In trapper Still in main ^CCaught in a trap! In the main script

EXPLANATION

  1. The trapper function is defined. All variables and traps set in the function are global to the script.

  2. The trap command will ignore INT, signal 2, the interrupt key (^C). If ^C is pressed, the message Caught in a trap is printed, and the script continues forever. The script can be killed with the kill command or Ctrl-\.

  3. The main script starts a forever loop.

  4. The function trapper is called.

  5. When the function returns, execution starts here.

12.9 Debugging

By using the n option to the bash command, you can check the syntax of your scripts without really executing any of the commands. If there is a syntax error in the script, the shell will report the error. If there are no errors, nothing is displayed.

The most commonly used method for debugging scripts is the set command with the x option, or bash invoked with the x option and the scriptname. See Table 12.7 for a list of debugging options. These options allow an execution trace of your script. Each command from your script is displayed after substitution has been performed, and then the command is executed. When a line from your script is displayed, it is preceded with a plus (+) sign.

With the verbose option turned on, or by invoking the shell with the v option (bash v scriptname), each line of the script will be displayed just as it was typed in the script, and then executed.

Table 12.7. Debugging Options
Command Option What It Does
bash x scriptname Echo option Displays each line of script after variable substitutions and before execution.
bash v scriptname Verbose option Displays each line of script before execution, just as you typed it.
bash n scriptname Noexec option Interprets but does not execute commands.
set x Turns on echo Traces execution in a script.
set +x Turns off echo Turns off tracing.
Example 12.69
(The Script) $ cat todebug     #!/bin/bash     # Scriptname: todebug 1   name="Joe Shmoe"     if [[ $name == "Joe Blow" ]]     then         printf "Hello $name\n"     fi     declare -i num=1     while (( num < 5 ))     do         let num+=1     done     printf "The total is %d\n", $num (The Output) 2 bash -x todebug + name=Joe Shmoe + [[ Joe Shmoe == \J\o\e\\B\l\o\w ]] + declare -i num=1 + ((  num < 5  )) + let num+=1 + ((  num < 5  )) + let num+=1 + ((  num < 5  )) + let num+=1 + ((  num < 5  )) + let num+=1 + ((  num < 5  )) + printf 'The total is %d\n,' 5 The total is 5 

EXPLANATION

  1. The script is called todebug. You can watch the script run with the x switch turned on. Each iteration of the loop is displayed and the values of variables are printed as they are set and when they change.

  2. Bash is invoked with the x option. Echoing is turned on. Each line of the script will be displayed to the screen prepended with a plus sign (+). Variable substitution is performed before the line is displayed. The result of the execution of the command appears after the line has been displayed.

12.10 Processing Command Line Options with getopts

If you are writing scripts that require a number of command line options, positional parameters are not always the most efficient. For example, the UNIX ls command takes a number of command line options and arguments. (An option requires a leading dash; an argument does not.) Options can be passed to the program in several ways: ls laFi, ls i a l F, ls ia F, and so forth. If you have a script that requires arguments, positional parameters might be used to process the arguments individually, such as ls l i F . Each dash option would be stored in $1, $2, and $3, respectively. But, what if the user listed all of the options as one dash option, as in ls liF? Now the liF would all be assigned to $1 in the script. The getopts function makes it possible to process command line options and arguments in the same way they are processed by the ls program.[8] The getopts function will allow the runit program to process its arguments using any variety of combinations.

Example 12.70
(The Command Line ) 1   $ runit  x  n  200 filex 2   $ runit  xn200 filex 3   $ runit  xy 4   $ runit  yx  n 30 5   $ runit  n250  xy filey ( any other combination of these arguments ) 

EXPLANATION

  1. The program runit takes four arguments: x is an option, n is an option requiring a number argument after it, and filex is an argument that stands alone.

  2. The program runit combines the options x and n and the number argument 200; filex is also an argument.

  3. The program runit is invoked with the x and y options combined.

  4. The program runit is invoked with the y and x options combined; the n option is passed separately, as is the number argument, 30.

  5. The program runit is invoked with the n option combined with the number argument, the x and y options are combined, and the filey is separate.

Before getting into all the details of the runit program, we examine the line from the program where getopts is used to see how it processes the arguments.

Example 12.71
(A Line from the Script Called "runit") while getopts :xyn: name 

EXPLANATION

  1. x, y, and n are the options. In this example the first option is preceded by a colon. This tells getopts to use silent error reporting. If there is a colon after one of the options, the option expects an argument separated from it by whitespace. An argument is a word that does not begin with a dash. n requires an argument.

  2. Any options typed at the command line must begin with a dash.

  3. Any options that do not contain a dash tell getopts that the option list has ended.

  4. Each time getopts is called, it places the next option value it finds in the variable name. (You can use any variable name here.) If an illegal argument is given, name is assigned a question mark.

getopts Scripts. The following examples illustrate how getopts processes arguments.

Example 12.72
(The Script) $ cat opts1     #!/bin/bash     # Program: opts1     # Using getopts   First try   1   while getopts xy options     do 2       case $options in 3       x) echo "you entered  x as an option";;         y) echo "you entered  y as an option";;         esac     done (The Command Line) 4   $ opts1  x     you entered  x as an option 5   $ opts1  xy     you entered  x as an option     you entered  y as an option 6   $ opts1  y     you entered  y as an option 7   $ opts1  b     opts1:   illegal option --  b 8   $ opts1 b 

EXPLANATION

  1. The getopts command is used as a condition for the while command. The valid options for this program are listed after the getopts command; they are x and y. Each option is tested in the body of the loop, one after the other. Each option will be assigned to the variable options, without the leading dash. When there are no longer any arguments to process, getopts will exit with a nonzero status, causing the while loop to terminate.

  2. The case command is used to test each of the possible options found in the options variable, either x or y.

  3. If x was an option, the string you entered x as an option is displayed.

  4. At the command line, the opts1 script is given an x option, a legal option to be processed by getopts.

  5. At the command line, the opts1 script is given an xy option; x and y are legal options to be processed by getopts.

  6. At the command line, the opts1 script is given a y option, a legal option to be processed by getopts.

  7. The opts1 script is given a b option, an illegal option. Getopts sends an error message to stderr.

  8. An option without a dash prepended to it is not an option and causes getopts to stop processing arguments.

Example 12.73
(The Script) $ cat opts2     #!/bin/bash     # Program: opts2     # Using getopts   Second try   1   while getopts xy options 2> /dev/null     do 2       case $options in         x) echo "you entered  x as an option";;         y) echo "you entered  y as an option";; 3       \?) echo "Only -x and -y are valid options"  1>&2;;         esac     done (The Command Line)     $ opts2  x     you entered  x as an option     $ opts2  y     you entered  y as an option     $ opts2 xy     $ opts2  xy     you entered  x as an option     you entered  y as an option 4   $ opts2  g     Only -x and -y are valid options 5   $ opts2  c     Only -x and -y are valid options 

EXPLANATION

  1. If there is an error message from getopts, it is redirected to /dev/null.

  2. If the option is a bad option, a question mark will be assigned to the options variable. The case command can be used to test for the question mark, allowing you to print your own error message to standard error.

  3. If the options variable is assigned the question mark, the case statement is executed. The question mark is protected with the backslash so that the shell does not see it as a wildcard and try to perform filename substitution.

  4. g is not a legal option. A question mark is assigned to the variable options and the error message is displayed.

  5. c is not a legal option. A question mark is assigned to the variable options and the error message is displayed.

Special getopts Variables. The getopts function provides two variables to help keep track of arguments: OPTIND and OPTARG. OPTIND is a special variable that is initialized to one and is incremented each time getopts completes processing a command line argument to the number of the next argument getopts will process. The OPTARG variable contains the value of a legal argument. See Examples 12.74 and 12.75.

Example 12.74
(The Script) $ cat opts3     #!/bin/bash     # Program: opts3     # Using getopts   Third try   1   while getopts dq: options     do        case $options in 2           d) echo " d is a valid switch ";; 3           q) echo "The argument for -q is $OPTARG";;             \?) echo "Usage:opts3 -dq filename ... " 1>&2;;        esac     done (The Command Line) 4   $ opts3  d      d is a valid switch 5   $ opts3 -q  foo     The argument for -q is foo 6   $ opts3 -q     Usage:opts3 -dq filename ... 7   $ opts3  e     Usage:opts3 -dq filename ... 8   $ opts3 e 

EXPLANATION

  1. The while command tests the exit status of getopts; if getopts can successfully process an argument, it returns zero exit status, and the body of the while loop is entered. The colon appended to the argument list means that the q option requires an argument. The argument will be stored in the special variable, OPTARG.

  2. One of the legal options is d. If d is entered as an option, the d (without the dash) is stored in the options variable.

  3. One of the legal options is q. The q option requires an argument. There must be a space between the q option and its argument. If q is entered as an option followed by an argument, the q, without the dash, is stored in the options variable and the argument is stored in the OPTARG variable. If an argument does not follow the q option, the question mark is stored in the variable options.

  4. The d option is a legal option to opts3.

  5. The q option with an argument is also a legal option to opts3.

  6. The q option without an argument is an error.

  7. The e option is invalid. A question mark is stored in the options variable if the option is illegal.

  8. The option is prepended with neither a dash nor a plus sign. The getopts command will not process it as an option and returns a nonzero exit status. The while loop is terminated.

Example 12.75
$ cat opts4     #!/bin/bash     # Program: opts4     # Using getopts   Fourth try   1   while getopts xyz: arguments 2>/dev/null     do        case $arguments  in 2      x) echo "you entered -x as an option .";;        y) echo "you entered -y as an option." ;; 3      z) echo "you entered -z as an option."            echo "\$OPTARG is $OPTARG.";; 4      \?) echo "Usage opts4 [-xy] [-z  argument]"            exit 1;;        esac     done 5   echo " The number of arguments passed was $(( $OPTIND - 1 ))" (The Command Line) $ opts4 -xyz foo You entered -x as an option. You entered -y as an option. You entered -z as an option. $OPTARG is foo. The number of arguments passed was 2. $ opts4 -x -y -z  boo You entered -x as an option. You entered -y as an option. You entered -z as an option. $OPTARG is boo. The number of arguments passed was 4. $ opts4 -d Usage: opts4  [-xy] [-z argument] 

EXPLANATION

  1. The while command tests the exit status of getopts; if getopts can successfully process an argument, it returns zero exit status, and the body of the while loop is entered. The colon appended to the z option tells getopts that an argument must follow the z option. If the option takes an argument, the argument is stored in the getopts built-in variable OPTARG.

  2. If x is given as an option, it is stored in the variable arguments.

  3. If z is given as an option with an argument, the argument is stored in the built-in variable OPTARG.

  4. If an invalid option is entered, the question mark is stored in the variable arguments and an error message is displayed.

  5. The special getopts variable OPTIND holds the number of the next option to be processed. Its value is always one more than the actual number of command line arguments.

12.11 The eval Command and Parsing the Command Line

The eval command evaluates a command line, performs all shell substitutions, and then executes the command line. It is used when normal parsing of the command line is not flexible enough for what we want to do.

Example 12.76
1   $ set a b c d 2   $ echo The last argument is \$$# 3   The last argument is $4 4   $ eval echo The last argument is \$$#     The last argument is d 5   $ set -x     $ eval echo The last argument is \$$#     + eval echo the last argument is '$4'     ++ echo the last argument is d     The last argument is d 

EXPLANATION

  1. Four positional parameters are set.

  2. The desired result is to print the value of the last positional parameter. The \$ will print a literal dollar sign. The $# evaluates to 4, the number of positional parameters. After the shell evaluates the $#, it does not parse the line again to get the value of $4.

  3. $4 is printed instead of the last argument.

  4. After the shell performs variable substitution, the eval command performs the variable substitution and then executes the echo command.

  5. Turn on the echoing to watch the order of parsing.

Example 12.77
(From Shutdown Program) 1   eval '/usr/bin/id | /usr/bin/sed 's/[^a-z0-9=].*//'' 2   if [ "${uid:=0}" -ne 0 ]     then 3      echo $0: Only root can run $0        exit 2     fi 

EXPLANATION

  1. This is a tricky one. The id program's output is sent to sed to extract the uid part of the string. The output for id is

    uid=9496(ellie) gid=40 groups=40 uid=0(root) gid=1(daemon) groups=1(daemon) 

    The sed regular expression reads: Starting at the beginning of the string, find any character that is not a letter, number, or an equal sign and remove that character and all characters following it. The result is to substitute everything from the first opening parenthesis to the end of the line with nothing. What is left is either: uid=9496 or uid=0.

    After eval evaluates the command line, it then executes the resulting command:

    uid=9496 

    or

    uid=0 

    For example, if the user's ID is root, the command executed would be uid=0. This creates a local variable in the shell called uid and assigns zero to it.

  2. The value of the uid variable is tested for zero, using command modifiers.

  3. If the uid is not zero, the echo command displays the scriptname ($0) and the message.

12.12 bash Options

12.12.1 Shell Invocation Options

When the shell is started using the bash command, it can take options to modify its behavior. There are two types of options: single-character options and multicharacter options. The single-character options consist of a single leading dash followed by a single character. The multicharacter options consist of two leading dashes and any number of characters. Multicharacter options must appear before single-character options. An interactive login shell normally starts up with i (start an interactive shell), s (read from standard input), and m (enable job control). See Table 12.8.

Table 12.8. bash 2.x Shell Invocation Options
Option Meaning
c string Commands are read from string. Any arguments after string are assigned to positional parameters, starting at $0.
D A list of double quoted strings, preceded by a $, are printed to standard output. These strings are subject to language translation when the current locale is not C or POSIX. The n option is implied; no commands will be executed.
i Shell is in the interactive mode. TERM, QUIT, and INTERRUPT are ignored.
s Commands are read from standard input and allow the setting of positional parameters.
r Starts a restricted shell.
Signals the end of options and disables further option processing. Any arguments after or are treated as filenames and arguments.
dump strings Same as D.
help Displays a usage message for a built-in command and exits.
login Causes bash to be invoked as a login shell.
noediting When bash is running interactively, does not use the Readline library.
noprofile When starting up, bash does not read the initialization files: /etc/profile, ~/.bash_profile, ~/.bash_login, or ~/.profile.
norc For interactive shells, bash will not read the ~/.bashrc file. Turned on by default, if running shell as sh.
posix Changes the behavior of bash to match the POSIX 1003.2 standard, if otherwise it wouldn't.
quiet Displays no information at shell startup, the default.
rcfile file If bash is interactive, uses this intialization file instead of ~/.bashrc.
restricted Starts a restricted shell.
verbose Turns on verbose; same as v.
version Displays version information about this bash shell and exit.

 

Table 12.9. bash (Versions Prior to 2.x) Shell Invocation Options
c string Commands are read from string. Any arguments after string are assigned to positional parameters, starting at $0.
D A list of double quoted strings, preceded by a $, are printed to standard output. These strings are subject to language translation when the current locale is not C or POSIX. The n option is implied; no commands will be executed.
i Shell is in the interactive mode. TERM, QUIT, and INTERRUPT are ignored.
s Commands are read from standard input and allows the setting of positional parameters.
r Starts a restricted shell.
Signals the end of options and disables further option processing. Any arguments after or are treated as filenames and arguments.
login Causes bash to be invoked as a login shell.
nobraceexpansion Curly brace expansion is turned off.
nolineediting When bash is running interactively, does not use the Readline library.
noprofile When starting up, bash does not read the initialization files: /etc/profile, ~/.bash_profile, ~/.bash_login, or ~/.profile.
posix Changes the behavior of bash to match the POSIX standard, if otherwise it wouldn't.
quiet Displays no information at shell startup, the default.
rcfile file If bash is interactive, uses this intialization file instead of ~/.bashrc.
verbose Turns on verbose; same as v.
version Displays version information about this bash shell and exit.

12.12.2 The set Command and Options

The set command can be used to turn shell options on and off, as well as for handling command line arguments. To turn an option on, the dash ( ) is prepended to the option; to turn an option off, the plus sign (+) is prepended to the option. See Table 12.10 for a list of set options.

Example 12.78
1   $ set -f 2   $ echo *     * 3   $ echo ??     ?? 4   $ set +f 

EXPLANATION

  1. The f option is turned on, disabling filename expansion.

  2. The asterisk is not expanded.

  3. The question marks are not expanded.

  4. The f is turned off; filename expansion is enabled.

 

Table 12.10. The Built-In set Command Options
Name of Option Shortcut Switch What It Does
allexport a Automatically marks new or modified variables for export from the time the option is set, until unset.
braceexpand B Enables brace expansion, and is a default setting.[a]
emacs   For command line editing, uses the emacs built-in editor, and is a default setting.
errexit e If a command returns a nonzero exit status (fails), exits. Not set when reading initialization files.
histexpand H Enables ! and !! when performing history substitution, and is a default setting.[a]
history   Enables command line history; on by default.[a]
ignoreeof   Disables EOF (Control-D) from exiting a shell; must type exit. Same as setting shell variable, IGNOREEOF=10.
keyword k Places keyword arguments in the environment for a command.[a]
interactive-comments   For interactive shells, a leading # is used to comment out any text remaining on the line.
monitor m Allows job control.
noclobber C Protects files from being overwritten when redirection is used.
noexec n Reads commands, but does not execute them. Used to check the syntax of scripts. Not on when running interactively.
noglob d Disables pathname expansion; i.e., turns off wildcards.
notify b Notifies user when background job finishes
nounset u Displays an error when expanding a variable that has not been set.
onecmd t Exits after reading and executing one command.[a]
physical P If set, does not follow symbolic links when typing cd or pwd. The physical directory is used instead.
posix   Shell behavior is changed if the default operation doesn't match the POSIX standard.
privileged p When set, the shell does not read the .profile or ENV file and shell functions are not inherited from the environment; automatically set for setuid scripts.
posix   Changes the default behavior to POSIX 1003.2.
verbose v Turns on the verbose mode for debugging.
vi   For command line editing, uses the vi built-in editor.
xtrace x Turns on the echo mode for debugging.

[a] Option applies only to versions of bash 2.x.

12.12.3 The shopt Command and Options

The shopt (bash 2.x) command can also be used to turn shell options on and off.

Table 12.11. The shopt Command Options
Option Meaning
cdable_vars If an argument to the cd built-in command is not a directory, it is assumed to be the name of a variable whose value is the directory to change to.
cdspell Corrects minor errors in the spelling of a directory name in a cd command. The errors checked for are transposed characters, a missing character, and a character too many. If a correction is found, the corrected path is printed, and the command proceeds. Only used by interactive shells.
checkhash Bash checks that a command found in the hash table exists before trying to execute it. If a hashed command no longer exists, a normal path search is performed.
checkwinsize Bash checks the window size after each command and, if necessary, updates the values of LINES and COLUMNS.
cmdhist Bash attempts to save all lines of a multiple-line command in the same history entry. This allows easy re-editing of multiline commands.
dotglob Bash includes filenames beginning with a dot (.) in the results of filename expansion.
execfail A noninteractive shell will not exit if it cannot execute the file specified as an argument to the exec built-in command. An interactive shell does not exit if exec fails.
expand_aliases Aliases are expanded. Enabled by default.
extglob The extended pattern matching features (regular expression metacharacters derived from Korn shell for filename expansion) are enabled.
histappend The history list is appended to the file named by the value of the HISTFILE variable when the shell exits, rather than overwriting the file.
histreedit If readline is being used, a user is given the opportunity to re-edit a failed history substitution.
histverify If set, and readline is being used, the results of history substitution are not immediately passed to the shell parser. Instead, the resulting line is loaded into the readline editing buffer, allowing further modification.
hostcomplete If set, and readline is being used, bash will attempt to perform hostname completion when a word containing an @ is being completed. Enabled by default.
huponexit If set, bash will send SIGHUP (hangup signal) to all jobs when an interactive login shell exits.
interactive_comments Allows a word beginning with # to cause that word and all remaining characters on that line to be ignored in an interactive shell. Enabled by default.
lithist If enabled, and the cmdhist option is enabled, multiline commands are saved to the history with embedded newlines rather than using semicolon separators where possible.
mailwarn If set, and a file that bash is checking for mail has been accessed since the last time it was checked, the message The mail in mailfile has been read is displayed.
nocaseglob If set, bash matches filenames in a case-insensitive fashion when performing filename expansion.
nullglob If set, bash allows filename patterns that match no files to expand to a null string, rather than themselves.
promptvars If set, prompt strings undergo variable and parameter expansion after being expanded. Enabled by default.
restricted_shell The shell sets this option if it is started in restricted mode. The value may not be changed. This is not reset when the startup files are executed, allowing the startup files to discover whether or not a shell is restricted.
shift_verbose If this is set, the shift built-in prints an error message when the shift count exceeds the number of positional parameters.
sourcepath If set, the source built-in uses the value of PATH to find the directory containing the file supplied as an argument. Enabled by default.
source A synonym for dot (.).

12.13 Shell Built-In Commands

The shell has a number of commands that are built-in to its source code. Because the commands are built-in, the shell doesn't have to locate them on disk, making execution much faster. The help feature provided with bash give you online help for any built-in command. The built-in commands are listed in Table 12.12.

Table 12.12. Built-In Commands
Command What It Does
. Executes program in context of current process; same as source.
. file The dot command reads and executes command from file.
: Do-nothing command; returns 0 exit status.
alias Lists and creates "nicknames" for existing commands.
bg Puts a job in the background.
bind Displays current key and function bindings, or binds keys to a readline function or macro.[a]
break Breaks out of the innermost loop.
break [n] See "The break Command" 780.
builtin [sh builtin [args]] Runs a shell built-in, passing it args, and returning 0 exit status. Useful if a function and built-in have the same name.[a]
cd [arg] Changes the directory to home if no arg or to value of arg.
command command [arg] Runs a command even if a function has the same name; i.e., bypasses function lookup.[a]
continue [n] See "The continue Command" on page 781.
declare [var] Displays all variables or declares variables with optional attributes.[a]
dirs Displays a list of currently remembered directories resulting from pushd.
disown Removes an active job from the job table.
echo [args] Displays args terminated with a newline.
enable Enables and disables shell built-in commands.[a]
eval [args] Reads args as input to the shell and executes the resulting command(s).
exec command Runs command in place of this shell.
exit [n] Exits the shell with status n.
export [var] Makes var known to subshells.
fc History's fix command for editing history commands.
fg Puts background job into foreground.
getopts Parses and processes command line options.
hash Controls the internal hash table for quicker searches for commands.
help [command] Displays helpful info about built-in commands and, if command is specified, detailed help about that built-in command.[a]
history Displays the history list with line numbers.
jobs Lists jobs put in the background.
kill [ signal process] Sends the signal to the PID number or job number of the process. Type kill l for a list of signals.
let Used for evaluating arithmetic expressions and assigning results of arithmetic calculations to variables.
local Used in functions to restrict the scope of variables to the function.
logout Exits the login shell.
popd Removes entries from the directory stack.
pushd Adds entries to the directory stack.
pwd Prints present working directory.
read [var] Reads line from standard input into variable var.
readonly [var] Makes variable var read-only. Cannot be reset.
return [n] Returns from a function where n is the exit value given to the return.
set Sets options and positional parameters. See Table 12.2.
shift [n] Shifts positional parameters to the left n times.
stop pid Halts execution of the process number PID.
suspend Stops execution of the current shell (but not if a login shell).
test Checks file types and evaluates conditional expressions.
times Prints accumulated user and system times for processes run from this shell.
trap [arg] [n] When shell receives signal n ( 0, 1, 2, or 15), executes arg.
type [command] Prints the type of command; e.g., pwd is a built-in shell command.
typeset Same as declare. Sets variables and gives them attributes.
ulimit Diplays and sets process resource limits.
umask [octal digits] Sets user file creation mode mask for owner, group, and others.
unalias Unsets aliases.
unset [name] Unset value of variable or function.
wait [pid#n] Waits for background process with PID number n and reports termination status.

[a] Option applies to bash 2.x. and later.

BASH SHELL LAB EXERCISES

Lab 49: First Script

1:

Write a script called greetme that will do the following:

  1. Contain a comment section with your name, the name of this script, and the purpose of this script.

  2. Greet the user.

  3. Print the date and the time.

  4. Print a calendar for this month.

  5. Print the name of your machine.

  6. Print the name and release of this operating system, (cat /etc/motd).

  7. Print a list of all files in your parent directory.

  8. Print all the processesroot is running.

  9. Print the value of the TERM, PATH, and HOME variables.

  10. Print your disk usage (du).

  11. Use theid command to print your group ID.

  12. Print Please couldn't you loan me $50.00?

  13. Tell the user Good bye and the current hour (see man pages for the date command).

2:

Make sure your script is executable.

chmod +x greetme 
3:

What was the first line of your script? Why do you need this line?

Lab 50: Command Line Arguments

1:

Write a script called rename that will take two arguments: the first argument is the name of the original file and the second argument is the new name for the file.

If the user does not provide two arguments, a usage message will appear on the screen and the script will exit. Here is an example of how the script works:

$ rename Usage: rename oldfilename newfilename $ $ rename file1 file2 file1 has been renamed file2 Here is a listing of the directory: a file2 b file.bak 
2:

The following find command (SunOS) will list all files in the root partition that are larger than 100K and that have been modified in the last week. (Check your man pages for the correct find syntax on your system.)

find /  xdev  mtime  7  size +200  print 
3:

Write a script called bigfiles that will take two arguments: one will be the mtime and one the size value. An appropriate error message will be sent to stderr if the user does not provide two arguments.

4:

If you have time, write a script called vib that creates backup files for vi. The backup files will have the extension .bak appended to the original name.

Lab 51: Getting User Input

1:

Write a script called nosy that will do the following:

  1. Ask the user's full name first, last, and middle name.

  2. Greet the user by his or her first name.

  3. Ask the user's year of birth and calculate his or her age (useexpr).

  4. Ask the user's login name and print his or her user ID (from /etc/passwd).

  5. Tell the user his or her home directory.

  6. Show the user the processes he or she is running.

  7. Tell the user the day of the week, and the current time in nonmilitary time. The output should resemble

    The day of the week is Tuesday and the current time is 04:07:38 PM.
     

2:

Create a text file called datafile (unless this file has already been provided for you). Each entry consists of fields separated by colons. The fields are as follows:

  1. First and last name

  2. Phone number

  3. Address

  4. Birth date

  5. Salary

3:

Create a script called lookup that will do the following:

  1. Contain a comment section with the scriptname, your name, the date, and the reason for writing this script.The reason for writing this script is to display the datafile in sorted order.

  2. Sort the datafile by last names.

  3. Show the user the contents of the datafile.

  4. Tell the user the number of entries in the file.

4:

Try the x and v options for debugging your script. How did you use these commands? How do they differ?

Lab 52: Conditional Statements

1:

Write a script called checking that will do the following:

  1. Take a command line argument, a user's login name.

  2. Test to see if a command line argument was provided.

  3. Check to see if the user is in the /etc/passwd file. If so, will print


    Found <user> in the /etc/passwd file.
     

    Otherwise, will print


    No such user on our system.
     

2:

In the lookup script, ask the user if he or she would like to add an entry to the datafile. If the answer is yes or y:

  1. Prompt the user for a new name, phone, address, birth date, and salary. Each item will be stored in a separate variable. You will provide the colons between the fields and append the information to the datafile.

  2. Sort the file by last names. Tell the user you added the entry, and show him or her the line preceded by the line number.

Lab 53: Conditionals and File Testing

1:

Rewrite checking. After checking whether the named user is in the /etc/passwd file, the program will check to see if the user is logged on. If so, the program will print all the processes that are running; otherwise it will tell the user

 <user> is not logged on.
 

2:

Use the let command to evaluate a set of grades. The script will ask the user for his or her numeric grade on an examination. ( Use declare i ). The script will test that the grade is within the allowable range between 0 and 100. If not, the program will exit. If the grade is within the range, the user's letter grade will be displayed, e.g., You received an A. Excellent! The range is as follows:

A (90 100) B (80 89) C (70 79) D (60 69) F (Below 60)

3:

The lookup script depends on the datafile in order to run. In the lookup script, check to see if the datafile exists and if it is readable and writeable. Add a menu to the lookup script to resemble the following:


[1] Add entry.
[2] Delete entry.
[3] View entry.
[4] Exit.
 

You already have the Add entry part of the script written. The Add entry routine should now include code that will check to see if the name is already in the datafile and if it is, tell the user so. If the name is not there, add the new entry.

Now write the code for the Delete entry, View entry, and Exit functions.

The Delete part of the script should first check to see if the entry exists before trying to remove it. If it does, notify the user; otherwise, remove the entry and tell the user you removed it. On exit, make sure that you use a digit to represent the appropriate exit status.

How do you check the exit status from the command line?

Lab 54: The case Statement

1:

The ps command is different on BSD (Berkeley UNIX) and System 5 (AT&T UNIX). UNIX uses the BSD options to ps. On System 5, the command to list all processes is

ps  ef 

On UNIX, the command is

ps aux 

Write a program called systype that will check for a number of different system types. The cases to test for will be


AIX
UNIX
HP UX
SCO
OSF1
ULTRIX
SunOS (Solaris / SunOs)
OS
 

Solaris, HP UX, SCO, and IRIX are AT&T-type systems. The rest are BSDish.

The version of UNIX you are using will be printed to stdout. The system name can be found with the uname s command or from the /etc/motd file.

2:

Write a script called timegreet that will do the following:

  1. Provide a comment section at the top of the script, with your name, the date, and the purpose of the program.

  2. Convert the following program to use the case command rather than if/elif.

    #!/bin/bash # Comment section you=$LOGNAME hour=$( date +%H ) echo "The time is: $( date +%T )" if (( hour > 0 && hour < 12 )) then    echo "Good morning,s $you!" elif (( hour == 12 )) then    echo "Lunch time!" elif (( hour > 12 && hour < 16 )) then    echo "Good afternoon, $you!" else    echo "Good night, $you. Sweet dreams." fi 

Lab 55: Loops

Select one of the following:

1:

Write a program called mchecker to check for new mail and write a message to the screen if new mail has arrived.

  1. The program will get the size of the mail spool file for the user. (The spool files are found in /usr/mail/$LOGNAME on AT&T systems and /usr/spool/mail/$USER on UNIX and UCB systems. Use the find command if you cannot locate the file.) The script will execute in a continuous loop, once every 30 seconds. Each time the loop executes, it will compare the size of the mail spool file with its size from the previous loop. If the new size is greater than the old size, a message will be printed on your screen, saying Username, You have new mail.

The size of a file can be found by looking at the output fromls l, wc c or from the find command.

2:

Write a script that will do the following:

  1. Provide a comment section at the top of the script, with your name, the date, and the purpose of the program.

  2. Use theselect loop to produce a menu of foods.

  3. Produce output to resemble the following:


    1) steak and potatoes
    2) fish and chips
    3) soup and salad
    Please make a selection. 1
    Stick to your ribs.
    Watch your cholesterol.
    Enjoy your meal.

    1) steak and potatoes
    2) fish and chips
    3) soup and salad
    Please make a selection. 2
    British are coming!
    Enjoy your meal.

    1) steak and potatoes
    2) fish and chips
    3) soup and salad
    Please make a selection. 3
    Health foods...
    Dieting is so boring.
    Enjoy your meal.
     

3:

Write a program called dusage that will mail a list of users, one at a time, a listing of the number of blocks they are currently using. The list of users will be in a file called potential_hogs. One of the users listed in the potential_hogs file will be admin.

  1. Use file testing to check that potential_hogs file exists and is readable.

  2. A loop will be used to iterate through the list of users. Only those users who are using over 500 blocks will be sent mail. The user admin will be skipped over (i.e., he or she does not get a mail message). The mail message will be stored in a here document in your dusage script.

  3. Keep a list of the names of each person who received mail. Do this by creating a log file. After everyone on the list has been sent mail, print the number of people who received mail and a list of their names.

Lab 56: Functions

1:

Rewrite the systype program as a function that returns the name of the system. Use this function to determine what options you will use with the ps command in the checking program.

2:

The ps command to list all processes on AT&T UNIX is

ps  ef 
3:

On UNIX/BSD UNIX, the command is

ps  aux  or ps aux[9] 
4:

Write a function called cleanup that will remove all temporary files and exit the script. If the interrupt or hangup signal is sent while the program is running, the trap command will call the cleanup function.

5:

Use a here document to add a new menu item to the lookup script to resemble the following:


[1] Add entry
[2] Delete entry
[3] Change entry
[4] View entry
[5] Exit
 

Write a function to handle each of the items in the menu. After the user has selected a valid entry, and the function has completed, ask if the user would like to see the menu again. If an invalid entry is entered, the program should print


Invalid entry, try again.
 

and the menu will be redisplayed.

6:

Create a submenu under View entry in the lookup script. The user will be asked if he or she would like to view specific information for a selected individual:


a) Phone
b) Address
c) Birth date
d) Salary
 

7:

Use the trap command in a script to perform a cleanup operation if the interrupt signal is sent while the program is running.

[1]  When bash starts interactively, if the norc or norc option is given, the BASH_ENV or ENV file will not be read.

[2]  Options a, e, and p are available only in bash versions 2.x.

[3]  Remember, without arguments, the set command displays all the variables that have been set for this shell, local and exported. With options, the set command turns on and off shell control options such as x and v.

[4]  If the continue command is given a number higher than the number of loops, the loop exits.

[5]   Only on bash versions 2.x.

[6]   Bolsky, Morris I. and Korn, David G., The New KornShell Command and Programming Language (Englewood Cliffs, NJ: Prentice Hall PTR, 1995), p. 327.

[7]  SIGKILL, number 9, often called a "sure kill," is not trapable.

[8]  See the UNIX manual pages (Section 3) for the C library function getopt.

[9]  Using the leading dash with UNIX will produce a warning. See the man page.

CONTENTS


UNIX Shells by Example
UNIX Shells by Example, 3rd Edition
ISBN: 013066538X
EAN: 2147483647
Year: 2001
Pages: 18
Authors: Ellie Quigley

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