------------------------------------------------ Linux Shells by Example: Chapter 9 Reading Guide ------------------------------------------------ -IAN! idallen@ncf.ca Here is a reading guide and some review questions for Chapter 9 "Programming with the Bash Shell". In this chapter, skip over all material that is specific to Bash version 2.x. (Much of this material is flagged with footnotes telling you it only works under Bash 2.x.) Learn the material that applies to the traditional Bash shell. Don't use the 2.x-only programming syntax. Remember to read the text_errata.txt file (under Notes) and correct all the mistakes in this Chapter. *) True or False: the #! line can be anywhere in the top of the shell script. (p.385) *) You will save yourself debugging effort if you add "-u" to the #!/bin/bash -u line in your scripts. (p.385) *) True or False: The #! line and all lines beginning with "#" are comment lines to the shell. (p.386) *) True or False: When you write out a shell script in the VI editor, it automatically gets execute permissions. (p.386) *) True or False: to make a script executable, type: umask +w ./script *) True or False: shell command substitution happens inside `` quotes. *) True or False: `foo bar` and $(foo bar) are identical to the bash shell. *) How do you execute a shell script in the current directory? (p.387) *) How do you declare a local variable in the shell? (p.388) *) How do you declare a global variable in the shell? (p.388) *) How many spaces are allowed around the "=" in a variable assignment? (p.388,392) *) True or False: changing a global variable in one shell changes it for all other shells in all windows. (Try it.) *) Explain the difference: $ variable='u=rwx' $ echo "It contains $variable" $ cat "$variable" $ ls "$variable" $ umask "$variable" $ chmod "$variable" filename *) How much input does the built-in "read" command read? (p.388) *) If I enter "a b c" in response to "read one two", how many words of the input get assigned to $one and $two? (p.388) *) How do these two lines of commands differ? (Enter the word "bar" in response to the "read" statement.) $ x=one ; one=foo ; read x ; echo $x ; echo $one $ x=one ; one=foo ; read $x ; echo $x ; echo $one (Remember your command line processing rules!) *) Avoid declaring variables as integer (p.391). Doing so means that you never get error messages about bad syntax, e.g. $ declare -i foo $ foo=garbage $ echo $foo 0 Never ignore bad input. *) Skip over the use of different bases (p.393). *) How many spaces are allowed around the "=" in a "let" assignment? (p.394) *) How do you add one to a bash variable? (p.394) *) Skip over section 9.3.2 (Floating Point). (p.395) *) Review parameters and arguments in Chapter 9 (section 9.4). Make sure you actually *do* and *understand* the examples shown! You must know the meaning of everything in Table 9.2 p.396. *) What does the "set" command do? (p.397) *) How do I reset all the positional parameters to be empty? (p.397) *) True or False: The following sets $1 to be "-o" and $2 to be "noclobber": set -- set -o noclobber echo "one is $1 and two is $2" *) True or False: The following sets $1 to be "o" and $2 to be "noclobber": set -- set o noclobber echo "one is $1 and two is $2" *) True or False: The following sets $1 to be "-o" and $2 to be "noclobber": set -- set -- -o noclobber echo "one is $1 and two is $2" *) True or False: The following sets $1 to be "-o" and $2 to be "noclobber": set -- var="-o noclobber" set $var echo "one is $1 and two is $2" *) Why does EXAMPLE 9.12 (p.398) print a huge list of variables if you execute it with no arguments? From which line does the large output come? Fix the script not to do this. (Hint: use -- ) *) If you execute EXAMPLE 9.12 with a quoted glob character, e.g.: $ ./args "*" the last line of output ($1 $2 $3) is incorrect. The correct should be the glob character and nothing else. Fix the script to have the correct output. (Hint: use quotes...) *) If you execute EXAMPLE 9.12 with three quoted arguments, as follows: $ ./args "a b" "c d" "e f" the last line of output ($1 $2 $3) is incorrect: a b c The correct output should be all three arguments: a b c d e f Fix the script to have the correct output. (Hint: What special shell variable expands to be all the command line arguments, each individually quoted? See p.396,400) *) How do "$*" and "$@" differ? (p.396,400) *) Do $* and $@ differ in behaviour if they are not in double quotes? (p.401) *) Review the "exit" command in Chapter 9 (section 9.5.1, 9.5.3). Make sure you actually *do* and *understand* the examples shown! *) True or False: the "exit" command is built in to the shell. *) True or False: the "exit" command takes a single string as an argument. *) Study the "test" command (and its single-square-bracket equivalent) in Chapter 9 (section 9.5.2 p.402). Fix the text examples - you must quote all the variables! Skip over the "Bash 2.x" examples that use double parentheses ((...)) and double brackets [[...]]. Study only the examples using the traditional [ ... ] syntax. *) How does the test command indicate success? failure? (p.402) *) True or False: The "test" command outputs 0 or 1 depending on whether the expression is true or false. (p.402) *) True or False: The "test" command produces no output. (p.402) *) True or False: The following lines are equivalent: $ test 'dog' = 'cat' ; echo "exit status $?" $ test 'dog'='cat' ; echo "exit status $?" The test command must see three separate arguments to compare strings or integers. *) True or False: The following lines are equivalent: $ [ 'dog' = 'cat' ] ; echo "exit status $?" $ ['dog'='cat'] ; echo "exit status $?" The square-bracket is a command name; it is not punctuation. It must stand alone on the command line. It cannot be concatenated with any other characters. *) True or False: The following lines are equivalent: $ test 'dog' = 'cat' ; echo "exit status $?" $ [ 'dog' = 'cat' ] ; echo "exit status $?" The test command expects exactly three arguments when doing a comparison. The square-bracket command expects four (the last of which is the closing bracket, which is ignored). *) In Table 9.3 (p.406), know how to use everything except "Logical Test (Compound Test)" that works in Bash 2.x only. Memorize the other tests. Do not use the "Logical Test (Compound Test)" operators. *) Which ones of these statements compare two integers? (p.406) Which ones of these statements compare two strings? If the variables a b c d e f g h contain the digits 1 to 8, then: What is the output (on your screen) of these commands? What is the exit status of these commands? [ "$a" -ne "$b" ] [ "$a" -lt "$b" ] [ "$c" = "$d" ] test "$e" -eq "$f" test "$e" -ge "$f" test "$g" != "$h" Note that the equality operator is '=', not '=='. '==' only works in bash 2.x scripts. Use = not == in your scripts. *) In Table 9.4 (p.407), learn the traditional operators and skip over the Bash 2.x operators. Do not use the bash 2.x operators. *) How do you multiply a bash variable by two? (p.394,407) *) Study the "IF/ELSE/ELIF" statement in Chapter 9 (section 9.5.2/9.5.3/9.5.4/9.5.5.) Concentrate on the "old format" in the examples (the format that uses single square brackets [...], not double sets of parentheses ((...)) or double sets of brackets [[...]]). Skip over the "new format (Bash 2.x)" examples. *) True or False: in bash, the keyword IF is followed by an expression. *) True or False: in bash, the keyword IF is followed by a Unix command. *) True or False: The IF body is executed if the command following the IF outputs a zero (0) indicating good status. *) True or False: The IF body is executed if the command following the IF has an exit status of zero (0). (p.407) *) True or False: If a command produces no output, its exit status must indicate failure (be non-zero). *) True or False: The exit status of a command has nothing to do with how much output is produced. Some commands fail with no output; some fail and produce output. Some commands succeed with no output; some succeed and produce output. The exit status is independent. *) Which one of these statements has correct syntax for comparing two integers and prints "hi"? (p.406) if 4>3 ; then echo hi ; fi if 4 > 3 ; then echo hi ; fi if 4-gt3 ; then echo hi ; fi if 4 -gt 3 ; then echo hi ; fi if test 4 > 5 ; then echo hi ; fi if test 4-gt5 ; then echo hi ; fi if test 4 -gt 3 ; then echo hi ; fi if [4>5] ; then echo hi ; fi if [4 > 5] ; then echo hi ; fi if [ 4 > 5 ] ; then echo hi ; fi if [4 -gt 3] ; then echo hi ; fi if [ 4-gt5 ] ; then echo hi ; fi if [ 4 -gt 3 ] ; then echo hi ; fi if [ test 4 -gt 3 ] ; then echo hi ; fi Hint: Only two of the above lines are correct. The rest are wrong. *) What is the output of each command sequence? $ x=5 ; y=5 $ if x = y ; then echo equal ; fi $ if test x = y ; then echo equal ; fi $ if test $x = $y ; then echo equal ; fi Which one is correct? *) What is the output of each command sequence? Some of the lines below have the wrong syntax: $ grep nosuchthing /etc/passwd $ if [ grep nosuchthing /etc/passwd ] ; then echo found it ; fi $ if test grep nosuchthing /etc/passwd ; then echo found it ; fi $ if grep nosuchthing /etc/passwd ; then echo found it ; fi Which line uses the correct syntax? What kind of thing follows the IF keyword? Some of the lines below have the wrong syntax: $ grep root /etc/passwd $ if [ grep root /etc/passwd ] ; then echo found it ; fi $ if test grep root /etc/passwd ; then echo found it ; fi $ if grep root /etc/passwd ; then echo found it ; fi Which line uses the correct syntax? What kind of thing follows the IF keyword? *) How do you check for a variable containing nothing (a null value)? (p.412) *) Fix EXAMPLE 9.23 (p.413) to use the first argument as the pattern. Fix the missing quotes in the script. *) 9.5.6 Table 9.5: Learn these "test" file operation letters: -[defrwxs] Ignore the rest. *) Fix EXAMPLE 9.26 (p.419) to use the first argument as the file name. (Also add a check to make sure that there is a single argument.) Fix the missing quotes around variables in the script. Test it with glob characters. *) What is the "null" command in bash? (p.420) Why would you use it in an IF statement? *) Skip EXAMPLE 9.28 (p.421). *) What valid integer can be input to EXAMPLE 9.29 (p.421) that will cause the script to say "You did not enter an integer value."? *) A bash "case" statement is similar to the "switch()" statement in C language. True or False: The string after the keyword "case" must be an integer. (p.422) *) True or False: The shell uses glob-like pattern matching in "case" statements. (p.422) *) True or False: Each case in a "case" statement ends in a triple semi-colon. (p.422) *) True or False: The shell executes every one of the cases that match after a "case" statement, even if more than one match. (p.422) *) True or False: If no cases match, the shell prints an error. *) Put EXAMPLE 9.31 into a file (begin with #!/bin/bash -u) and run it using ./filename. Like this: $ echo $TERM xterm $ ./filename Select a terminal type 1) linux 2) xterm 3) sun 1 TERM is linux. $ echo $TERM <...what prints here...> Did it change the value of your TERM variable? Explain the output. *) Fix EXAMPLE 9.31 p.424 to print the menu on standard error, not on standard output. (All menus and prompts must be sent to standard error, not standard output.) Add a request to the user to enter a menu choice at the bottom of the menu, before the "read" command. (The prompt to enter input must also, always, appear on standard error!) *) How does the "for" command differ between C language and Bash? (p.425) *) Modify EXAMPLE 9.32 to contain the quoted string "John Doe" instead of the name Dick. What is the output? *) Comment out the "mail" command in EXAMPLE 9.33 and test the script. Change "patty" to be "John Doe" in file mylist and re-run the script. Explain the output. *) Fix the quoting around the variables in EXAMPLE 9.34 p.427-428. Create a backup directory in your HOME directory and then change the "dir" in the script to be that directory. Create some memo files and test the script. *) Run EXAMPLE 9.35 with various arguments, glob chars, etc. Give arguments containing blanks too, e.g. ./greet a b "c d" e f Put double quotes around $* and run it again. Now change "$*" to "$@" (with the quotes) and re-run the script. Explain the differences in output. *) Fix the errors in EXAMPLE 9.36 (and the EXPLANATION on the top of the next page) and test the script. *) True or False: These two lines are equivalent: for var do # no list given for var in "$@" ; do # list comes from "$@" *) True or False: The "while" command in Bash is followed by a numeric expression, e.g. while $i < 10 *) True or False: The "while" command in Bash is followed by a Unix command line, e.g. while test $i -lt 10 *) Fix the errors in EXAMPLE 9.37 and test the script. *) Convert EXAMPLE 9.38 to traditional syntax and test the script. *) Convert EXAMPLE 9.39 to traditional syntax and test the script. *) EXAMPLE 9.40: Change the "until" line 1 to read: until [ $(who | grep -c "$LOGNAME") -gt 2 ] and replace the last "talk" line with this one: xmessage "$LOGNAME has logged on more than two times" and then open a few more windows to see if you can get the script to trigger on your logins. *) You can always replace a "while" with an "until" (and vice-versa) by reversing the sense of the command being tested. Change EXAMPLE 9.41 to use "while" instead of "until" by testing that the hour is <= 24. Use traditional syntax, not Bash 2.x syntax. *) True or False: The opposite of "less than" is "greater than", so these two Bash lines are equivalent: while [ $i -lt 10 ] ; do until [ $i -gt 10 ] ; do *) Skip section 9.6.4 (p.435+) on "select". Wherever the book says to use a "select" statement, recode this as a "here" document (containing the menu) followed by a prompt for input, followed by "read answer" followed by a "case" statement to select the appropriate case to execute. (The "select" statement doesn't work in many versions of Unix.) See Example 9.31. *) Fix the errors in EXAMPLE 9.42 p.436 and recode it as a "here" document containing a menu, followed by "read program", followed by a "case" statement to turn the number into the appropriate command to execute. (Follow the pattern in Example 9.31.) *) Fix the errors in EXAMPLE 9.43 p.437 and recode it as a "here" document containing a menu, followed by "read choice", followed by a "case" statement to echo the appropriate comments. (Follow the pattern in Example 9.31.) *) Try EXAMPLE 9.45 (p.440) and EXAMPLE 9.46. (Convert 9.46 to traditional bash syntax; do not use the Bash 2.x syntax shown.) Pass an asterisk in as one of the script arguments. Fix the script by quoting all the variables! *) True or False: The "shift" command must be used inside a loop. (p.440) *) What output is generated by these lines (p.440): $ set -- a b c ; echo $1 $ set -- a b c ; shift ; echo $1 $ set -- a b c ; shift ; shift ; echo $1 $ set -- a b c ; shift ; shift ; shift ; echo $1 *) Try EXAMPLE 9.47 (p.442). Convert it to traditonal syntax. *) Skip over the use of numbers as arguments to the "break" and "continue" commands - using them makes for difficult-to-understand code and numbers on these statements are rarely used in Unix programming. Just use "break" and "continue", without numbers. *) True or False: The "break" command must be used inside a loop. (p.442) *) True or False: The "continue" command must be used inside a loop. (p.443) *) EXAMPLE 9.48 p.443: Replace the command "true" with ":" (the NULL command). Convert the script to traditional syntax. Simplify the script. (Note that the "else" is not needed; because, the loop exits after "break".) *) EXAMPLE 9.49 p.444: Replace the "mail" command with just "echo" when testing this script. Convert it to traditional syntax. Simplify the script. (Note that the "else" is not needed; because, the loop resumes after "continue".) *) Be careful of EXAMPLE 9.50, p.445. Don't use numbers on "break" and "continue" - they make the code hard to follow; one level of break/continue is more than enough. Simplify the script. (Note that neither "else" is needed; because, the loop resumes after "continue".) *) 9.6.6 p.446: When you apply file I/O or pipe redirection to a loop, it may create a "subshell" in which changes to variables do not get propagated back up to the parent shell. Different versions of the shells handle this differently; don't depend on the post-loop values of variables used inside loops that have I/O redirected. *) You almost can treat the entire loop as if it were a single Unix command for the purposes of I/O redirection and pipes. You can pipe input into the start of a loop, and you can pipe or redirect output at the end of the loop. (p.447) (Unlike doing redirection on single Unix commands, you *must* put output redirection after the *end* of the loop; you can't put it before the while/for/until keyword that starts the loop.) i=1 while [ $i -lt 10 ]; do let i=i+1 ; echo $i ; done >out ls -la | while read a b c ; do echo "$a and $b" ; done *) EXAMPLE 9.52 p.448: Modify this script to loop over all the command line arguments instead of using the fixed list of numbers. Fix the quoting. *) p.448 Running loops in the background is rare in shell scripts. *) EXAMPLE 9.54: Fix the typing error in the first line of this script. Be very careful when changing IFS - it affects word-splitting and a non-default value can make your scripts behave unpredictably. Nothing in this course will require changing IFS. *) Fix the text errors in the introduction to 9.7 Functions (p.450). (Fix all the text errors - read the text_errata.txt file.) *) If you have a function named "date", does it get executed instead of the system "date" command, or does the system "date" command take precedence? (p.450) *) Use the traditional way to define functions, not the Bash-only way: Traditional: foo () { echo hi ; } Bash-only: function foo { echo hi ; } *) If you set a variable named "i" to 1 inside a function (e.g. i=1), is that variable also set to 1 outside of the function? (p.452) *) True or False: using positional parameters (e.g. $1, $2, $*, etc.) inside a function definition refers to the command line arguments. (p.452) *) True or False: The return status of a function is the return status of the last command executed inside the function, unless you use an explicit "return" statement. (p.452) *) True or False: The "return" statement can return anything, including a string of characters. (p.452) *) True or False: The "return" statement causes its argument to be printed on standard output. (p.452) *) EXAMPLE 9.56 p.452: Convert this script to traditional syntax (including the function definition) and test it. Rename the "Usage" function to "UsageAndExit" to reflect the fact that using it causes the script to exit. Add the name of the script to all error messages and make the error messages appear on standard error, not stdout. *) EXAMPLE 9.57 p.454: This script already uses the traditional syntax for a function definition. Test the script. Change the line "increment 5" to "increment 255" and see what output you get. Explain the output. (Don't use return values or exit statues for arithmetic!) *) EXAMPLE 9.58 p.455: What happens if the user enters a blank line for input? How would you fix the script to handle this better? *) If I have a file "myfuncs" containing a function definition for a function named "foo", how many of the following command lines will work to define and then execute foo: $ source myfuncs ; foo $ ./myfuncs ; foo $ . myfuncs ; foo $ myfuncs ; foo $ . ./myfuncs ; foo $ ./source myfuncs ; foo Hint: Only three of the lines succeed in defining foo. Why? (p.281,p.455) *) Skip over EXAMPLE 9.60. *) Skip over signal traps (Section 9.8 p.459-464) *) 9.9 Debugging: Learn to use -x on your scripts! It is a good way to see how the shell expands variables and glob patterns in lines: $ bash -x -u ./myscript arg1 arg2 .... *) Skip over getopts (9.10 p.466-472) *) Skip over eval (9.11 p.472-474) *) Skip over Bash Options (9.12 p.474-480) *) You already know some shell built-in commands (9.13 p.480). Be aware that others exist; but, don't memorize them. (Don't try to name a script using a built-in command name!) *) That's all. ---------------------------------------- Chapter 9 Lab Exercise Questions (p.483) ---------------------------------------- After reading Chapter 9, you should be able to do all these scripts: Lab 1 2 3 4 5 6 7 8 (all labs) Wherever the book says to use a "select" statement, recode this as a "here" document (containing the menu) followed by a prompt for input, followed by "read answer" followed by a "case" statement to select the appropriate case to execute. (The "select" statement doesn't work in many versions of Unix.)