================================================== Errors in Linux Shells by Example by Ellie Quigley ================================================== Textbook Errata: First Edition (2000) This file contains the errors and misinformation found in the above book. ----- CDROM ----- Many of the script files on the CDROM are saved in DOS/Windows format (with CR/LF line ends) and will not run if executed on Linux/Unix. The scripts also contain Windows extended characters instead of plain ASCII, so things such as minus signs / hyphens don't work: echo "Good~Vbye for now, $1 " (EXAMPLE 9.12) It appears as though someone edited these script files under Windows, mangling them in the process. --------- Chapter 1 --------- p.4 - the file name should be /etc/shells (plural) p.7 - missing slash: "or bin/csh" should be "or /bin/csh" p.8 - 7&8 happen at the same time, not sequentially p.16 - Linux ignores setuid on shell scripts (because it is unsafe) p.17 - the umask bits are not subtracted; they are a bit mask p.25 - missing "e" in: wc tempfile p.27 - dup() does not close fd 4; it makes a copy; you must close fd 4 explicitly (see the simplepipe.c program) p.28 - dup() does not close fd 3; it makes a copy; you must close fd 3 explicitly (see the simplepipe.c program) p.29 - The actual numbers of the signals (and sometimes the newer names) change between different versions of Unix/Linux. - There is no signal number "0" and no signal called "EXIT". (The shell "trap" command uses the number 0 to mean "on script exit"; this is a shell feature and is *not* a Unix signal number.) - SIGSTP should be spelled SIGTSTP (Control-Z) p.30 - NOTE: The script in Figure 1.14 will hang waiting for input because the "cat" command has no argument and is reading from stdin. - add "-f" as an argument to the line "#!/bin/tcsh" in the script (this is done correctly at the top of Example 1.10 on p.31) - The last line of the page is wrong - you often have to put "./" in front of your script to execute it out of the current directory. p.34 - add "-u" as an argument to the line "#!/bin/bash" in the script (do this for all the Bourne shell scripts you write) p.36 - add "-u" as an argument to the line "#!/bin/sh" in the script (do this for all the Bourne shell scripts you write) - change the misspelled keyword "d" to "do" in the "for" loop p.38 - add "-u" as an argument to the line "#!/bin/ksh" in the script (do this for all the Bourne shell scripts you write) --------- Chapter 2 --------- p.41 - The introduction to Chapter 2 talks as if grep, sed, and awk are covered in this chapter ("will be discussed here"). They are not; they are in separate chapters. The name of the chapter is wrong: it should be called "Regular Expressions". - Footnote #2 is out of context and probably wrong. p.43 - Table 2.1 is treating regular expressions in the way they are used by grep to select lines, not as used by sed or vi to change the text matched by the regular expression. Change the title of the table to be: Regular Expression Metacharacters when Used to Select Lines (e.g. in the grep command) p.44 - /[^A-Z]ove/ does not match ":over". It matches ":ove". p.47 - example 2.5 is missing some boldface for matches of pattern o*ve (i.e. clOVEr, foreVEr, liVE) p.52 - the letter 's' is missing after 1,$ under the EXPLANATION: 1,$s/\([Oo]ccur\)ence/\1rence/g p.53 - EXAMPLE 2.13: the "1." indicating a footnoted line is not properly left-indented - it looks like part of what you have to type into VI. - The example fails to note that your cursor must be on the line containing "square and fair" for this substitution to work. It would be better to prefix the substitution with the line range "1,$" so that the cursor position doesn't matter. - The example is silly, since one can much better say: :1,$s/square and fair/fair and square/g No registers are needed at all. Use a better example! --------- Chapter 3 --------- p.55 - No, the name "grep" traces back to the original Unix "ed" line editor, not to the Berkeley "ex". "ed" predates "ex" by many years, and "ed" was based on Bell Labs "qed" from years before. "qed" had the g/re/p syntax a decade before Berkeley. p.56 - Section 3.1.2 confuses shell quoting with grep patterns. Grep patterns, like all command arguments, must be properly quoted to protect metacharacters from the shell. Single quotes provide full protection. Of course, if the pattern doesn't contain any shell metacharacters (including no blanks), it doesn't need quotes. - Grep sends its output to standard output, not to the screen. - Grep changes the access times of the files being read. - under FORMAT change it to: grep pattern [ filename ... ] (the file names are optional - grep reads standard input too) - under EXPLANATION change "standard input or a pipe" to "standard input, including a pipe," A pipe *is* a form of standard input. p.57 - Section 3.1.3 is worded very badly. It is confusing and misleading. - \< and \> are not basic regexp metacharacters - they only work in some programs or some versions of some programs. Move them to the list of extended regular expression metacharacters. p.58 - Table 3.2 is largely a duplication of 2.1 on p.44 and 4.3 on p.99. Having slightly different information repeated this way is confusing to the students. It makes the book bigger without adding any useful information. - \w includes the underscore character as a "letter"; \W excludes it - the table has digit font problems inside the braces - in many programs, an asterisk can repeat the previous regular expression, not just the previous character, e.g. \(abc\)* matches abcabcabc - the example for "*" of "*love" is typeset to look as if there is nothing in front of the asterisk. Asterisk must follow some regular expression. (A visible space should be inserted in front.) - the expanation "Matches lines with zero or more spaces, of the preceding characters followed by the pattern love" makes no grammatical sense. p.59 - in many programs, a question mark can make the previous regular expression optional, not just the previous character, e.g. (abc)? matches abc or nothing - in many programs, a plus can repeat the entire previous regular expression, not just the previous character, e.g. (abc)+ matches abcabcabc - font problems with digits (inconsistent Courier vs. Times) - font problems in a. and b. notes: sometimes egrep is in italics - The POSIX [:alnum:] character class is explicitly *NOT* another way of saying A-Za-z0-9, except for North American ASCII character sets. The POSIX class works portably, internationally, and includes *all* letters in the local language, not just ASCII characters. p.68 - section 3.2 should say (see Table 3.5) p.68,69 - these pages duplicate the tables on p.58,59 - this is unnecessary. p.70,71 - In EXAMPLE 3.24, line 3 shows no output and line 4 shows the correct output. In EXAMPLE 3.25, following the same format, it looks as if the first two lines (egrep and grep -E) produce no output. In fact, they produce the same output as the third line, and the rest of the examples behave the same way. This is poor layout. p.77 - a space is missing in front of the -R option to grep and the -? option to rgrep - EXAMPLE 3.34 should be in section 3.3 (fgrep) not in 3.4 (rgrep). - say "from standard input" not "from a pipe" p.78 - What is the difference between Table 3.6 and 3.7? They have the same title "Gnu Grep Options". Make them one table. p.88,89 - The examples in the egrep section duplicate those in the grep section. The egrep section should only contain examples that use and demonstrate the extended regexp metacharacters. --------- Chapter 4 --------- p.93 - "sed" is a "stream" (as in "I/O stream") editor, not a "streamlined" editor (see the title of the man page!) - Dangerous Nonsense: "It does not change your file unless you save the output with shell redirection." Wrong! Even if you *do* use shell output redirection, you cannot use sed to edit a file "in place". The moment you use shell output redirection to redirect output to the same file sed is reading, the shell truncates the file to zero size. That leaves nothing for sed to edit. Get rid of this sentence. p.94 - "By storing each line in a temporary buffer and performing edits on that line, the original file is never altered or destroyed." Why mention this? This is no different to the way that 99.99% of Unix commands work, including tr, cat, vi, perl, etc. I can't even think of a command that edits a file directly on disk - they *all* take a copy of the file into memory. Get rid of this line. p.95 - item 1 should say "lines of datafile" - item 2 should say "the pattern John or john in datafile" - there are Courier/Times font problems in EXPLANATION 4.2 - in EXPLANATION 4.2 change "except lines 1, 2, and 3 or delete lines 1, 2, and 3." to read "except lines 1, 2, and 3; or, delete lines 1, 2, and 3." - Delete the line "The % is the shell prompt." It's a bit late to provide that helpful information, given that % has been used in examples all through the previous 3 chapters. p.97 - Under "Substitution Flags", draw a big line between w and x. w is a substitution flag; x and y are commands, not substitution flags. - the y command is missing its argument delimiter strings: y/source/dest/ p.99 - In many versions of Unix sed, the character * matches zero or more of the preceding grouped expression, not just the preceding character: /\(abc\)*/ matches abcabcabc Using * and + this way works under Linux sed, but not in the Solaris version of sed. (So don't depend on it.) - GNU sed also accepts extended metacharacters \+ \| and \? (but Solaris sed does not, so don't depend on using them everywhere.) --------- Chapter 5 --------- p.138 - The last line is wrong - NR is incremented after a line is *read in*, not after it is *processed*. NR is incremented *before* the action for that line is executed. When the file is finished, NR is still set to the number of the last line of the file, not lastline+1. - more errata still to be found in Chapter 5 --------- Chapter 6 --------- - more errata still to be found in Chapter 6 --------- Chapter 7 --------- p.183 - Last line is wrong. To coerce a number to be a string, concatenate the empty (null) string to it: "" (the book shows a blank: " ") p.183 - should say "the expression x-- is equivalent to x = x - 1" p.209 - one of the braces { should be a brace } in the first IF statement p.243 - The revised bundle/unbundle program in the EXPLANATION on this page will not work for any file containing any records with only one field. (The field will become a new file name!) This program is junk; ignore it. - more errata still to be found in Chapter 7 --------- Chapter 8 --------- p.262 - in 8.1.3: missing leading slash on file name /etc/profile - footnote 3, missing second dash: the option is --noprofile p.270 - the --noprofile option is incorrectly split over two lines p.279 - delete the trailing colon from the PATH= line in example 8.14 (a trailing colon is the same as putting dot in the PATH) p.281,282 - the command name is not the first word; it is the first word left *after all the redirection has been processed and removed from the command line*, e.g. "echo hi >out" and ">out echo hi" are equivalent. p.282 - reword "The command is executed according to its type in the following order" into "The command name is searched for in the following order" p.283 - in 8.18 item 2: missing "d" on: help pwd p.314 - aliases *are* available to subshells: $ alias foo="echo hi" $ foo hi $ ( sleep 5 ; foo ) <== subshell in parentheses hi $ bash <== new shell (not a subshell) $ foo bash: foo: command not found Perhaps the book means to say "aliases are not passed on in the environment inherited by child processes (except for immediate subshells of the current shell)". - bash aliases *can* take arguments, in the sense that anything following the alias on the command line becomes part of the command being executed: $ alias foo='echo "hello"' $ foo there hello there p.315 - change "precedethe" to "precede the" - quoting a command name will also stop alias processing p.317 - No, "wildcards" are a DOS term for what Unix people call "globbing", (global file matching) and therefore "wildcards" apply only to file name matching metacharacters. Backslash, ampersand, semicolon, and dollar are *not* wildcard characters; they are just shell metacharacters. Missing from the metacharacter list in table 8.14: pipe, redirect ('<' and '>'), tilde, parentheses, quotes. p.317-318 - table 8.14 and 8.15 are largely duplicated - book doesn't explain {commands} vs. {file,pattern,expansion} p.318 - delete the word "for" from all lines of Table 8.14 and from the last line on the page - you need spaces around braces to have them mean "in current shell" p.321 - Brace expansion is *not* related to globbing. The brace expansion pattern is generated regardless of the files in the current directory. No matching against file names is done at all. $ mkdir empty ; cd empty ; echo a{b,c}{d,e}f abdf abef acdf acef p.327,p.330 - read only variables can not be reassigned; they are read only! - example 8.57 is wrong; you cannot assign to a read-only variable $ bash --version GNU bash, version 2.05.8(1)-release (i386-redhat-linux-gnu) Copyright 2000 Free Software Foundation, Inc. p.332 - A nested shell is *not* a subshell. A subshell is a forked copy of the current shell without an intervening exec, usually found inside parentheses: $ ( echo This is a subshell. ; date ; who ) A subshell contains all the aliases, functions, and variables of its parent (because it is an exact copy). A nested shell only contains the things that are passed in the environment across an exec() system call. p.338 - the built-in bash version of printf does not accept any "--help" option. p.355 - line 2 in the example has blanks around the ' = '; this is wrong p.357 - In 8.3.6 items 3-6 are done *at the same time*, left-to-right, not one after the other. You can't have parameter that contains a variable, or a variable that contains a command substitution, or a command substitution that contains an arithmetic expansion. All these are expanded simultaneously, left to right on the line. p.361 - line 8 starts a new "nested shell", not a "sub-shell" - the comment for line 7 should read: ... available to nested shells. p.362 - the output of line 8 should be: jane anna p.363 - the &>foo and >&foo syntaxes are not portable; they only work in BASH. (The >&foo also works in tcsh.) Use the portable ">foo 2>&1" instead. It works in all Bourne-style shells. p.365 - exec 5<&4 will fail because fd 4 was opened for output, not input you must mean 5>&4 p.380 - missing blank in 7d. "source .bash_profile" --------- Chapter 9 --------- p.385 - add "-u" as an argument to the line "#!/bin/bash" in the script (do this for all the Bourne shell scripts you write) p.386 - the output from ls -lF is wrong: the size is shown as zero and the day of the month is missing - change "the joker file" to read "the myscript file" in explanation #2 p.387 - add "-u" as an argument to the line "#!/bin/bash" in the script (do this for all the Bourne shell scripts you write) p.388 - 9.2.2: as far as the shell user is concerned, the newline is removed; it doesn't appear in the string as any character. Saying it turns in to null byte (as if you could count it) is misleading. p.391 - declaring variables to be integer means you get *no error message* if you accidentally include text in your assignments. Don't use integer declarations - you won't find out your errors. p.397 - EXAMPLE 9.11 The script name is wrong in the second line of output in lines 2, 3, and 4. It should be "greetings2", not "greetings". p.398 - if OLDARGS contains a leading dash, Line 14 will treat it as a shell option, not arguments. Insert '--' after "set" wherever it is used (especially Line 14). p.403 - 9.5.2: Yes, unquoted shell metacharacters *do* expand when used in the "test" command. There is nothing special about the way the "test" command treats command line arguments - it evaluates them just as if the command name were "cat", "less", "ls", etc. Try: touch Tom ; test Tom = T* && echo You do see this. What the book should say is that *quoted* glob patterns are not seen as glob patterns and are not treated specially by the "test" command. The "test" command does not do pattern matching on strings. Try: touch Tom ; test Tom = 'T*' && echo You do NOT see this. p.403 - Shell glob chars may expand in script Line 8. If the current directory contains a file named "Tom", the test returns 0! Put quotes around the special characters: "[Tt]??" p.406 - Logical Test: A string can't be true. Only an expression can be true. Change "string" to "expression" in all three lines. p.407 - 9.5.3 Change "whether they find the pattern." to "whether they find the pattern or not." - 9.5.3 Delete the sentence "The criteria for success with sed and gawk is correct syntax, not functionality." This is nonsense. Sed and gawk do a dozen things other than look for patterns - what return code should they return if you aren't even looking for a pattern? Both programs are very functional. p.408 - FORMAT: if you put the keyword "then" on the same line as the "if", it must be preceded by a semicolon. Fix the example: if [ string/numeric expression ] ; then if [[ string expression ]] ; then The semicolon stands in for the missing newline. p.411 - EXAMPLE 9.21 - The line "$ cat bigfiles" is not part of the script. Delete it. - #!/bin/bash -u is missing as the first line of the script. - The comment is wrong; it should say "...and are larger than m blocks where m is the second command line argument and must be greater than 20." - User input (parameters) should be quoted wherever used to prevent splitting on blanks and glob expansion. - Line 7 - missing + in front of argument to -mtime: -mtime +"$1" - Running this script as an ordinary user will likely generate a lot of "permission denied" messages. It's a bad example. - The error messages are missing the name of the script, they don't appear on standard error, they don't give the user feedback about the actual value s/he typed in, and they don't print the acceptable range of values. This is poor programming. p.412-413 - 9.2.2 The leading "X" in the quoted strings is unnecessary (because the string is already quoted and won't be missing as an argument to the test command), and thus the explanation on p.413 is wrong. - The variable "$name" is undefined in Example 9.23. It should probably be "$1" or "$*" instead. - If the user enters a pattern in $name containing shell glob characters and blanks, they may expand in: Found $name! Variables containing user input must be double-quoted in scripts. - The indentation in EXAMPLE 9.23 is inconsistent between the IF and ELSE clauses. p.414 - The Explanation at the top of this page is nonsense; it doesn't match the script on the previous page. It talks about a nonexistent ypmatch command. - The gawk line is unnecessary; $(id -u) produces the same output with much less code. p.416 - Double quotes are missing around all the variables; an input line with two items in it will cause the script to generate large numbers of syntax errors. If the input contains a glob char, even more errors may appear. - The string of cascading if/elif statements has logic errors and large amounts of redundant and unnecessary code. The "-le 12" overlaps the "-ge 12". The first IF reduntantly tests for "$age -ge 0" when that was already assured by the previous IF statement. The cascading ELIF statements redundantly test the lower bound, when each lower bound was already assured by the previous test. Half the tests in this file should be removed. p.417 - Same logic errors as p.416, except "<= 19" overlaps ">= 19" here. p.419 - The backslash inside double quotes doesn't strip leading blanks or tabs - the resulting error message is very ugly. The backslash should be put outside quotes. - The code only works if the file name doesn't contain blanks or other shell special characters (glob patterns). Variables must be double-quoted in shell scripts! p.420 - Example 9.27. - The indentation of the ":" should match the ELSE clause indent. - The ">& /dev/null" syntax is not portable; it only works in BASH and CSH. Use the more portable syntax ">/dev/null 2>&1" in scripts instead. The portable syntax works in all Bourne-style shells (sh, ksh, bash, etc.). p.421 - Example 9.29. Read the man page for expr. "expr" returns 1 if the arithmetic result is zero, so the script fails with an incorrect message if the user enters a zero. (Zero is an integer.) - The ">& /dev/null" syntax is not portable; it only works in BASH and CSH. Use the more portable syntax ">/dev/null 2>&1" in scripts instead. The portable syntax works in all Bourne-style shells (sh, ksh, bash, etc.). p.422 - In the FORMAT section: - The command(s) under the asterisk "*)" default case are missing their indentation. Move them right. - The "case" statment takes a string, not a "variable". The string might be inside a variable; but, it doesn't have to be a variable. p.423 - Bad indentation: the ";;" does not always line up with the code above it. p.424 - It's bad style not to have a "default" case in a switch statement, even if it does nothing. If you leave it out, people think you forgot something. Example 9.31 should have a comment that says "leave TERM unchanged" for the default case. p.425 - 9.6.1: The FOR command can be followed by *any* variable name, including shell-defined names, not just a "user-defined" variable name. p.429 - Example 9.36. Line 2 is missing a comment "#" in front of the "or ..." - The script will die a horrible syntax-error death if any of the arguments contain blanks or glob chars that expand. Double-quote your variables! p.430 - EXPLANATION. Change 1 to read: If the for loop is not provided with a wordlist, it iterates through the positional prameters. This is the same as: for file in "$@" ; do (the book uses an unquoted $*, which does NOT work if any of the script arguments contain blanks or glob characters) - EXAMPLE 9.37: change "(( $num" to "(( num" and "[ num" to "[ $num". The "test" command does not expand variables like the "((" command. p.436 - Example 9.42. The whole example output is wrong. The "select" prompt appears *after* the menu list and before the output. The prompt does not appear before the menu list like the book shows. p.437 - The typography (bold/italic) does not match the previous example using the same shell construct. The book is inconsistent. - EXPLANATION 1 is wrong. The prompt is printed *below* the menu, not above it. p.444 - EXAMPLE 9.49 - The use of "ELSE" is superfluous after flow control statements such as "return", "exit", "break", "continue", etc. - This example could be coded much more neatly by changing the test to != and getting rid of the ELSE clause entirely. Less code is better code. p.445 - EXAMPLE 9.50 - The use of "ELSE" is superfluous after flow control statements such as "return", "exit", "break", "continue", etc. Get rid of it and the unnecessary indentation. p.446 - 9.6.6: Modern versions of the shell do not start a subshell to handle file I/O redirection on loops - variables *are* available when the loop ends. Old Bourne shells do start subshells and the changes done inside the loop are not available outside the loop. Input or output pipes attached to loops always create subshells. p.449 - EXAMPLE 9.54: change #/bin/bash to #!/bin/bash in the script p.450 - Functions: Line 4 is nonsense - it contradicts itself. Replace it with: If you use the "exit" command in a function, you exit the entire script. If you use the "return" command in a function, you exit only the function (and not the script); execution resumes from the place where the function was called. - "export -f" exports functions to *child* shells (via the environment), not to subshells. Subshells already have all the functions defined; because they are memory clones of the original parent shell. p.451 - "export -f" exports functions to *child* shells (via the environment), not to subshells. Subshells already have all the functions defined; because they are memory clones of the original parent shell. p.452 - The return status must be 0-255, not 0-256. p.453 - The footnote "a" has nothing to do with the text context (line 3). It should probably be a footnote to line 4, the IF test, not to line 3. p.454 - EXAMPLE 9.57: The first line of output should be flagged as lines 5,7 not 4,6. The last line should be flagged as output from line 8, not line 7. p.455 - EXAMPLE 9.58: It's bad style for functions to output a lot of text, since it makes their output unusable by other programs. Functions should output simple values and leave it to the main program to embellish the output with other text. p.457 - the indentation is wrong under the [Yy]* case; move 5 lines left. - the "else ; continue ; fi" can be simplified to "fi ; continue". - the program inconsistenly accepts "Yes" for two answers but only "Y" for the middle answer. Change the [Yy] to be [Yy]* in the middle. - the indentation is wrong under line 8; move the IF left p.469 - use a leading colon to suppress error messages, not 2>/dev/null - the error message should echo the character that the user used p.475,477,478,479,481 - The tables of options and built-in commands are incomplete and omit things already discussed. The collection looks like a random sample of the bash man page; how were these selected? If the text isn't going to discuss an item, why mention it? I can get a more accurate list from the man page. ---------- Appendix A ---------- p.674 - "banner" doesn't exist in most Linux distributions p.696 - "line" doesn't exist in most Linux distributions p.706 - "pack" doesn't exist in most Linux distributions p.708 - "pcat" doesn't exist in most Linux distributions p.722 - "tabs" doesn't exist in most Linux distributions p.724 - the syntax for test is "-gt" not "gt" p.725 - "timex" doesn't exist in most Linux distributions p.730 - "unpack" doesn't exist in most Linux distributions ---------- Appendix C ---------- p.745 - 2b) backslash is *not* special inside single quotes in any shell (in other words, single quotes do protect backslash characters) - however, the "echo" and "print" commands may eat backslashes... p.746 - Both tcsh and csh need a backslash in front of ! to inhibit history substitution; only in special cases can it be omitted (e.g. at the end of a line or a single quotation). - The lines containg "echo '\\\\'" are flawed, since the echo and print commands may themselves eat backslashes and thus you don't know if it is the shell or the command that is eating the backslashes.