Chapter 32. Gotchas

 

Turandot: Gli enigmi sono tre, la morte una!

Caleph: No, no! Gli enigmi sono tre, una la vita!

 Puccini

Assigning reserved words or characters to variable names.
   1 case=value0       # Causes problems.
   2 23skidoo=value1   # Also problems.
   3 # Variable names starting with a digit are reserved by the shell.
   4 # Try _23skidoo=value1. Starting variables with an underscore is o.k.
   5 
   6 # However...      using just the underscore will not work.
   7 _=25
   8 echo $_           # $_ is a special variable set to last arg of last command.
   9 
  10 xyz((!*=value2    # Causes severe problems.

Using a hyphen or other reserved characters in a variable name.
   1 var-1=23
   2 # Use 'var_1' instead.

Using the same name for a variable and a function. This can make a script difficult to understand.
   1 do_something ()
   2 {
   3   echo "This function does something with \"$1\"."
   4 }
   5 
   6 do_something=do_something
   7 
   8 do_something do_something
   9 
  10 # All this is legal, but highly confusing.

Using whitespace inappropriately. In contrast to other programming languages, Bash can be quite finicky about whitespace.
   1 var1 = 23   # 'var1=23' is correct.
   2 # On line above, Bash attempts to execute command "var1"
   3 # with the arguments "=" and "23".
   4 	
   5 let c = $a - $b   # 'let c=$a-$b' or 'let "c = $a - $b"' are correct.
   6 
   7 if [ $a -le 5]    # if [ $a -le 5 ]   is correct.
   8 # if [ "$a" -le 5 ]   is even better.
   9 # [[ $a -le 5 ]] also works.

Assuming uninitialized variables (variables before a value is assigned to them) are "zeroed out". An uninitialized variable has a value of "null", not zero.
   1 #!/bin/bash
   2 
   3 echo "uninitialized_var = $uninitialized_var"
   4 # uninitialized_var =

Mixing up = and -eq in a test. Remember, = is for comparing literal variables and -eq for integers.
   1 if [ "$a" = 273 ]      # Is $a an integer or string?
   2 if [ "$a" -eq 273 ]    # If $a is an integer.
   3 
   4 # Sometimes you can mix up -eq and = without adverse consequences.
   5 # However...
   6 
   7 
   8 a=273.0   # Not an integer.
   9 	   
  10 if [ "$a" = 273 ]
  11 then
  12   echo "Comparison works."
  13 else  
  14   echo "Comparison does not work."
  15 fi    # Comparison does not work.
  16 
  17 # Same with   a=" 273"  and a="0273".
  18 
  19 
  20 # Likewise, problems trying to use "-eq" with non-integer values.
  21 	   
  22 if [ "$a" -eq 273.0 ]
  23 then
  24   echo "a = $a'
  25 fi  # Aborts with an error message.  
  26 # test.sh: [: 273.0: integer expression expected

Mixing up integer and string comparison operators.
   1 #!/bin/bash
   2 # bad-op.sh	
   3 
   4 number=1
   5 
   6 while [ "$number" < 5 ]    # Wrong! Should be   while [ "number" -lt 5 ]
   7 do
   8   echo -n "$number "
   9   let "number += 1"
  10 done  
  11 
  12 # Attempt to run this bombs with the error message:
  13 # bad-op.sh: 5: No such file or directory

Sometimes variables within "test" brackets ([ ]) need to be quoted (double quotes). Failure to do so may cause unexpected behavior. See Example 7-6, Example 16-4, and Example 9-6.

Commands issued from a script may fail to execute because the script owner lacks execute permission for them. If a user cannot invoke a command from the command line, then putting it into a script will likewise fail. Try changing the attributes of the command in question, perhaps even setting the suid bit (as root, of course).

Attempting to use - as a redirection operator (which it is not) will usually result in an unpleasant surprise.
   1 command1 2> - | command2  # Trying to redirect error output of command1 into a pipe...
   2 #    ...will not work.	
   3 
   4 command1 2>& - | command2  # Also futile.
   5 
   6 Thanks, S.C.

Using Bash version 2+ functionality may cause a bailout with error messages. Older Linux machines may have version 1.XX of Bash as the default installation.
   1 #!/bin/bash
   2 
   3 minimum_version=2
   4 # Since Chet Ramey is constantly adding features to Bash,
   5 # you may set $minimum_version to 2.XX, or whatever is appropriate.
   6 E_BAD_VERSION=80
   7 
   8 if [ "$BASH_VERSION" \< "$minimum_version" ]
   9 then
  10   echo "This script works only with Bash, version $minimum or greater."
  11   echo "Upgrade strongly recommended."
  12   exit $E_BAD_VERSION
  13 fi
  14 
  15 ...

Using Bash-specific functionality in a Bourne shell script (#!/bin/sh) on a non-Linux machine may cause unexpected behavior. A Linux system usually aliases sh to bash, but this does not necessarily hold true for a generic UNIX machine.

A script with DOS-type newlines (\r\n) will fail to execute, since #!/bin/bash\r\n is not recognized, not the same as the expected #!/bin/bash\n. The fix is to convert the script to UNIX-style newlines.
   1 #!/bin/bash
   2 
   3 echo "Here"
   4 
   5 unix2dos $0    # Script changes itself to DOS format.
   6 chmod 755 $0   # Change back to execute permission.
   7                # The 'unix2dos' command removes execute permission.
   8 
   9 ./$0           # Script tries to run itself again.
  10                # But it won't work as a DOS file.
  11 
  12 echo "There"
  13 
  14 exit 0

A shell script headed by #!/bin/sh may not run in full Bash-compatibility mode. Some Bash-specific functions might be disabled. Scripts that need complete access to all the Bash-specific extensions should start with #!/bin/bash.

Putting whitespace in front of the terminating limit string of a here document will cause unexpected behavior in a script.

A script may not export variables back to its parent process, the shell, or to the environment. Just as we learned in biology, a child process can inherit from a parent, but not vice versa.
   1 WHATEVER=/home/bozo
   2 export WHATEVER
   3 exit 0
 bash$ echo $WHATEVER
 
 bash$ 
Sure enough, back at the command prompt, $WHATEVER remains unset.

Setting and manipulating variables in a subshell, then attempting to use those same variables outside the scope of the subshell will result an unpleasant surprise.


Example 32-1. Subshell Pitfalls

   1 #!/bin/bash
   2 # Pitfalls of variables in a subshell.
   3 
   4 outer_variable=outer
   5 echo
   6 echo "outer_variable = $outer_variable"
   7 echo
   8 
   9 (
  10 # Begin subshell
  11 
  12 echo "outer_variable inside subshell = $outer_variable"
  13 inner_variable=inner  # Set
  14 echo "inner_variable inside subshell = $inner_variable"
  15 outer_variable=inner  # Will value change globally?
  16 echo "outer_variable inside subshell = $outer_variable"
  17 
  18 # End subshell
  19 )
  20 
  21 echo
  22 echo "inner_variable outside subshell = $inner_variable"  # Unset.
  23 echo "outer_variable outside subshell = $outer_variable"  # Unchanged.
  24 echo
  25 
  26 exit 0

Piping echooutput to a read may produce unexpected results. In this scenario, the read acts as if it were running in a subshell. Instead, use the set command (as in Example 11-14).


Example 32-2. Piping the output of echo to a read

   1 #!/bin/bash
   2 #  badread.sh:
   3 #  Attempting to use 'echo and 'read'
   4 #+ to assign variables non-interactively.
   5 
   6 a=aaa
   7 b=bbb
   8 c=ccc
   9 
  10 echo "one two three" | read a b c
  11 # Try to reassign a, b, and c.
  12 
  13 echo
  14 echo "a = $a"  # a = aaa
  15 echo "b = $b"  # b = bbb
  16 echo "c = $c"  # c = ccc
  17 # Reassignment failed.
  18 
  19 # ------------------------------
  20 
  21 # Try the following alternative.
  22 
  23 var=`echo "one two three"`
  24 set -- $var
  25 a=$1; b=$2; c=$3
  26 
  27 echo "-------"
  28 echo "a = $a"  # a = one
  29 echo "b = $b"  # b = two
  30 echo "c = $c"  # c = three 
  31 # Reassignment succeeded.
  32 
  33 # ------------------------------
  34 
  35 #  Note also that an echo to a 'read' works within a subshell.
  36 #  However, the value of the variable changes *only* within the subshell.
  37 
  38 a=aaa          # Starting all over again.
  39 b=bbb
  40 c=ccc
  41 
  42 echo; echo
  43 echo "one two three" | ( read a b c;
  44 echo "Inside subshell: "; echo "a = $a"; echo "b = $b"; echo "c = $c" )
  45 # a = one
  46 # b = two
  47 # c = three
  48 echo "-----------------"
  49 echo "Outside subshell: "
  50 echo "a = $a"  # a = aaa
  51 echo "b = $b"  # b = bbb
  52 echo "c = $c"  # c = ccc
  53 echo
  54 
  55 exit 0

In fact, as Anthony Richardson points out, piping to any loop can cause a similar problem.

   1 # Loop piping troubles.
   2 # This example by Anthony Richardson.
   3 
   4 
   5 foundone=false
   6 find $HOME -type f -atime +30 -size 100k |
   7 while true
   8 do
   9    read f
  10    echo "$f is over 100KB and has not been accessed in over 30 days"
  11    echo "Consider moving the file to archives."
  12    foundone=true
  13 done
  14    
  15 #  foundone will always be false here since it is
  16 #+ set to true inside a subshell
  17 if [ $foundone = false ]
  18 then
  19    echo "No files need archiving."
  20 fi
  21 
  22 # =====================Now, here is the correct way:=================
  23 
  24 foundone=false
  25 for f in $(find $HOME -type f -atime +30 -size 100k)  # No pipe here.
  26 do
  27    echo "$f is over 100KB and has not been accessed in over 30 days"
  28    echo "Consider moving the file to archives."
  29    foundone=true
  30 done
  31    
  32 if [ $foundone = false ]
  33 then
  34    echo "No files need archiving."
  35 fi

--

Using "suid" commands within scripts is risky, as it may compromise system security. [1]

Using shell scripts for CGI programming may be problematic. Shell script variables are not "typesafe", and this can cause undesirable behavior as far as CGI is concerned. Moreover, it is difficult to "cracker-proof" shell scripts.

Bash does not handle the double slash (//) string correctly.

Bash scripts written for Linux or BSD systems may need fixups to run on a commercial UNIX machine. Such scripts often employ GNU commands and filters which have greater functionality than their generic UNIX counterparts. This is particularly true of such text processing utilites as tr.

 

Danger is near thee --

Beware, beware, beware, beware.

Many brave hearts are asleep in the deep.

So beware --

Beware.

 A.J. Lamb and H.W. Petrie

Notes

[1]

Setting the suid permission on the script itself has no effect.