Chapter 15. Internal Commands and Builtins

A builtin is a command contained within the Bash tool set, literally built in. This is either for performance reasons -- builtins execute faster than external commands, which usually require forking off [1] a separate process -- or because a particular builtin needs direct access to the shell internals.

A builtin may be a synonym to a system command of the same name, but Bash reimplements it internally. For example, the Bash echo command is not the same as /bin/echo, although their behavior is almost identical.
   1 #!/bin/bash
   2 
   3 echo "This line uses the \"echo\" builtin."
   4 /bin/echo "This line uses the /bin/echo system command."

A keyword is a reserved word, token or operator. Keywords have a special meaning to the shell, and indeed are the building blocks of the shell's syntax. As examples, for, while, do, and ! are keywords. Similar to a builtin, a keyword is hard-coded into Bash, but unlike a builtin, a keyword is not in itself a command, but a subunit of a command construct. [2]

I/O

echo

prints (to stdout) an expression or variable (see Example 4-1).
   1 echo Hello
   2 echo $a

An echo requires the -e option to print escaped characters. See Example 5-2.

Normally, each echo command prints a terminal newline, but the -n option suppresses this.

Note

An echo can be used to feed a sequence of commands down a pipe.

   1 if echo "$VAR" | grep -q txt   # if [[ $VAR = *txt* ]]
   2 then
   3   echo "$VAR contains the substring sequence \"txt\""
   4 fi

Note

An echo, in combination with command substitution can set a variable.

a=`echo "HELLO" | tr A-Z a-z`

See also Example 16-22, Example 16-3, Example 16-47, and Example 16-48.

Be aware that echo `command` deletes any linefeeds that the output of command generates.

The $IFS (internal field separator) variable normally contains \n (linefeed) as one of its set of whitespace characters. Bash therefore splits the output of command at linefeeds into arguments to echo. Then echo outputs these arguments, separated by spaces.

 bash$ ls -l /usr/share/apps/kjezz/sounds
 -rw-r--r--    1 root     root         1407 Nov  7  2000 reflect.au
 -rw-r--r--    1 root     root          362 Nov  7  2000 seconds.au
 
 
 
 
 bash$ echo `ls -l /usr/share/apps/kjezz/sounds`
 total 40 -rw-r--r-- 1 root root 716 Nov 7 2000 reflect.au -rw-r--r-- 1 root root ...
 	      

So, how can we embed a linefeed within an echoed character string?
   1 # Embedding a linefeed?
   2 echo "Why doesn't this string \n split on two lines?"
   3 # Doesn't split.
   4 
   5 # Let's try something else.
   6 
   7 echo
   8 	     
   9 echo $"A line of text containing
  10 a linefeed."
  11 # Prints as two distinct lines (embedded linefeed).
  12 # But, is the "$" variable prefix really necessary?
  13 
  14 echo
  15 
  16 echo "This string splits
  17 on two lines."
  18 # No, the "$" is not needed.
  19 
  20 echo
  21 echo "---------------"
  22 echo
  23 
  24 echo -n $"Another line of text containing
  25 a linefeed."
  26 # Prints as two distinct lines (embedded linefeed).
  27 # Even the -n option fails to suppress the linefeed here.
  28 
  29 echo
  30 echo
  31 echo "---------------"
  32 echo
  33 echo
  34 
  35 # However, the following doesn't work as expected.
  36 # Why not? Hint: Assignment to a variable.
  37 string1=$"Yet another line of text containing
  38 a linefeed (maybe)."
  39 
  40 echo $string1
  41 # Yet another line of text containing a linefeed (maybe).
  42 #                                    ^
  43 # Linefeed becomes a space.
  44 
  45 # Thanks, Steve Parker, for pointing this out.

Note

This command is a shell builtin, and not the same as /bin/echo, although its behavior is similar.

 bash$ type -a echo
 echo is a shell builtin
 echo is /bin/echo
 	      

printf

The printf, formatted print, command is an enhanced echo. It is a limited variant of the C language printf() library function, and its syntax is somewhat different.

printf format-string... parameter...

This is the Bash builtin version of the /bin/printf or /usr/bin/printf command. See the printf manpage (of the system command) for in-depth coverage.

Caution

Older versions of Bash may not support printf.


Example 15-2. printf in action

   1 #!/bin/bash
   2 # printf demo
   3 
   4 declare -r PI=3.14159265358979     # Read-only variable, i.e., a constant.
   5 declare -r DecimalConstant=31373
   6 
   7 Message1="Greetings,"
   8 Message2="Earthling."
   9 
  10 echo
  11 
  12 printf "Pi to 2 decimal places = %1.2f" $PI
  13 echo
  14 printf "Pi to 9 decimal places = %1.9f" $PI  # It even rounds off correctly.
  15 
  16 printf "\n"                                  # Prints a line feed,
  17                                              # Equivalent to 'echo' . . .
  18 
  19 printf "Constant = \t%d\n" $DecimalConstant  # Inserts tab (\t).
  20 
  21 printf "%s %s \n" $Message1 $Message2
  22 
  23 echo
  24 
  25 # ==========================================#
  26 # Simulation of C function, sprintf().
  27 # Loading a variable with a formatted string.
  28 
  29 echo 
  30 
  31 Pi12=$(printf "%1.12f" $PI)
  32 echo "Pi to 12 decimal places = $Pi12"      # Roundoff error!
  33 
  34 Msg=`printf "%s %s \n" $Message1 $Message2`
  35 echo $Msg; echo $Msg
  36 
  37 #  As it happens, the 'sprintf' function can now be accessed
  38 #+ as a loadable module to Bash,
  39 #+ but this is not portable.
  40 
  41 exit 0

Formatting error messages is a useful application of printf

   1 E_BADDIR=85
   2 
   3 var=nonexistent_directory
   4 
   5 error()
   6 {
   7   printf "$@" >&2
   8   # Formats positional params passed, and sends them to stderr.
   9   echo
  10   exit $E_BADDIR
  11 }
  12 
  13 cd $var || error $"Can't cd to %s." "$var"
  14 
  15 # Thanks, S.C.

See also Example 36-17.

read

"Reads" the value of a variable from stdin, that is, interactively fetches input from the keyboard. The -a option lets read get array variables (see Example 27-6).


Example 15-3. Variable assignment, using read

   1 #!/bin/bash
   2 # "Reading" variables.
   3 
   4 echo -n "Enter the value of variable 'var1': "
   5 # The -n option to echo suppresses newline.
   6 
   7 read var1
   8 # Note no '$' in front of var1, since it is being set.
   9 
  10 echo "var1 = $var1"
  11 
  12 
  13 echo
  14 
  15 # A single 'read' statement can set multiple variables.
  16 echo -n "Enter the values of variables 'var2' and 'var3' "
  17 echo =n "(separated by a space or tab): "
  18 read var2 var3
  19 echo "var2 = $var2      var3 = $var3"
  20 #  If you input only one value,
  21 #+ the other variable(s) will remain unset (null).
  22 
  23 exit 0

A read without an associated variable assigns its input to the dedicated variable $REPLY.


Example 15-4. What happens when read has no variable

   1 #!/bin/bash
   2 # read-novar.sh
   3 
   4 echo
   5 
   6 # -------------------------- #
   7 echo -n "Enter a value: "
   8 read var
   9 echo "\"var\" = "$var""
  10 # Everything as expected here.
  11 # -------------------------- #
  12 
  13 echo
  14 
  15 # ------------------------------------------------------------------- #
  16 echo -n "Enter another value: "
  17 read           #  No variable supplied for 'read', therefore...
  18                #+ Input to 'read' assigned to default variable, $REPLY.
  19 var="$REPLY"
  20 echo "\"var\" = "$var""
  21 # This is equivalent to the first code block.
  22 # ------------------------------------------------------------------- #
  23 
  24 echo
  25 echo "========================="
  26 echo
  27 
  28 
  29 #  This example is similar to the "reply.sh" script.
  30 #  However, this one shows that $REPLY is available
  31 #+ even after a 'read' to a variable in the conventional way.
  32 
  33 
  34 # ================================================================= #
  35 
  36 #  In some instances, you might wish to discard the first value read.
  37 #  In such cases, simply ignore the $REPLY variable.
  38 
  39 { # Code block.
  40 read            # Line 1, to be discarded.
  41 read line2      # Line 2, saved in variable.
  42   } <$0
  43 echo "Line 2 of this script is:"
  44 echo "$line2"   #   # read-novar.sh
  45 echo            #   #!/bin/bash  line discarded.
  46 
  47 # See also the soundcard-on.sh script.
  48 
  49 exit 0

Normally, inputting a \ suppresses a newline during input to a read. The -r option causes an inputted \ to be interpreted literally.


Example 15-5. Multi-line input to read

   1 #!/bin/bash
   2 
   3 echo
   4 
   5 echo "Enter a string terminated by a \\, then press <ENTER>."
   6 echo "Then, enter a second string (no \\ this time), and again press <ENTER>."
   7 
   8 read var1     # The "\" suppresses the newline, when reading $var1.
   9               #     first line \
  10               #     second line
  11 
  12 echo "var1 = $var1"
  13 #     var1 = first line second line
  14 
  15 #  For each line terminated by a "\"
  16 #+ you get a prompt on the next line to continue feeding characters into var1.
  17 
  18 echo; echo
  19 
  20 echo "Enter another string terminated by a \\ , then press <ENTER>."
  21 read -r var2  # The -r option causes the "\" to be read literally.
  22               #     first line \
  23 
  24 echo "var2 = $var2"
  25 #     var2 = first line \
  26 
  27 # Data entry terminates with the first <ENTER>.
  28 
  29 echo 
  30 
  31 exit 0

The read command has some interesting options that permit echoing a prompt and even reading keystrokes without hitting ENTER.

   1 # Read a keypress without hitting ENTER.
   2 
   3 read -s -n1 -p "Hit a key " keypress
   4 echo; echo "Keypress was "\"$keypress\""."
   5 
   6 # -s option means do not echo input.
   7 # -n N option means accept only N characters of input.
   8 # -p option means echo the following prompt before reading input.
   9 
  10 # Using these options is tricky, since they need to be in the correct order.

The -n option to read also allows detection of the arrow keys and certain of the other unusual keys.


Example 15-6. Detecting the arrow keys

   1 #!/bin/bash
   2 # arrow-detect.sh: Detects the arrow keys, and a few more.
   3 # Thank you, Sandro Magi, for showing me how.
   4 
   5 # --------------------------------------------
   6 # Character codes generated by the keypresses.
   7 arrowup='\[A'
   8 arrowdown='\[B'
   9 arrowrt='\[C'
  10 arrowleft='\[D'
  11 insert='\[2'
  12 delete='\[3'
  13 # --------------------------------------------
  14 
  15 SUCCESS=0
  16 OTHER=65
  17 
  18 echo -n "Press a key...  "
  19 # May need to also press ENTER if a key not listed above pressed.
  20 read -n3 key                      # Read 3 characters.
  21 
  22 echo -n "$key" | grep "$arrowup"  #Check if character code detected.
  23 if [ "$?" -eq $SUCCESS ]
  24 then
  25   echo "Up-arrow key pressed."
  26   exit $SUCCESS
  27 fi
  28 
  29 echo -n "$key" | grep "$arrowdown"
  30 if [ "$?" -eq $SUCCESS ]
  31 then
  32   echo "Down-arrow key pressed."
  33   exit $SUCCESS
  34 fi
  35 
  36 echo -n "$key" | grep "$arrowrt"
  37 if [ "$?" -eq $SUCCESS ]
  38 then
  39   echo "Right-arrow key pressed."
  40   exit $SUCCESS
  41 fi
  42 
  43 echo -n "$key" | grep "$arrowleft"
  44 if [ "$?" -eq $SUCCESS ]
  45 then
  46   echo "Left-arrow key pressed."
  47   exit $SUCCESS
  48 fi
  49 
  50 echo -n "$key" | grep "$insert"
  51 if [ "$?" -eq $SUCCESS ]
  52 then
  53   echo "\"Insert\" key pressed."
  54   exit $SUCCESS
  55 fi
  56 
  57 echo -n "$key" | grep "$delete"
  58 if [ "$?" -eq $SUCCESS ]
  59 then
  60   echo "\"Delete\" key pressed."
  61   exit $SUCCESS
  62 fi
  63 
  64 
  65 echo " Some other key pressed."
  66 
  67 exit $OTHER
  68 
  69 # ========================================= #
  70 
  71 #  Mark Alexander came up with a simplified
  72 #+ version of the above script (Thank you!).
  73 #  It eliminates the need for grep.
  74 
  75 #!/bin/bash
  76 
  77   uparrow=$'\x1b[A'
  78   downarrow=$'\x1b[B'
  79   leftarrow=$'\x1b[D'
  80   rightarrow=$'\x1b[C'
  81 
  82   read -s -n3 -p "Hit an arrow key: " x
  83 
  84   case "$x" in
  85   $uparrow)
  86      echo "You pressed up-arrow"
  87      ;;
  88   $downarrow)
  89      echo "You pressed down-arrow"
  90      ;;
  91   $leftarrow)
  92      echo "You pressed left-arrow"
  93      ;;
  94   $rightarrow)
  95      echo "You pressed right-arrow"
  96      ;;
  97   esac
  98 
  99 exit $?
 100 
 101 # ========================================= #
 102 
 103 # Antonio Macchi has a simpler alternative.
 104 
 105 #!/bin/bash
 106 
 107 while true
 108 do
 109   read -sn1 a
 110   test "$a" == `echo -en "\e"` || continue
 111   read -sn1 a
 112   test "$a" == "[" || continue
 113   read -sn1 a
 114   case "$a" in
 115     A)  echo "up";;
 116     B)  echo "down";;
 117     C)  echo "right";;
 118     D)  echo "left";;
 119   esac
 120 done
 121 
 122 # ========================================= #
 123 
 124 #  Exercise:
 125 #  --------
 126 #  1) Add detection of the "Home," "End," "PgUp," and "PgDn" keys.

Note

The -n option to read will not detect the ENTER (newline) key.

The -t option to read permits timed input (see Example 9-4 and Example A-41).

The -u option takes the file descriptor of the target file.

The read command may also "read" its variable value from a file redirected to stdin. If the file contains more than one line, only the first line is assigned to the variable. If read has more than one parameter, then each of these variables gets assigned a successive whitespace-delineated string. Caution!


Example 15-7. Using read with file redirection

   1 #!/bin/bash
   2 
   3 read var1 <data-file
   4 echo "var1 = $var1"
   5 # var1 set to the entire first line of the input file "data-file"
   6 
   7 read var2 var3 <data-file
   8 echo "var2 = $var2   var3 = $var3"
   9 # Note non-intuitive behavior of "read" here.
  10 # 1) Rewinds back to the beginning of input file.
  11 # 2) Each variable is now set to a corresponding string,
  12 #    separated by whitespace, rather than to an entire line of text.
  13 # 3) The final variable gets the remainder of the line.
  14 # 4) If there are more variables to be set than whitespace-terminated strings
  15 #    on the first line of the file, then the excess variables remain empty.
  16 
  17 echo "------------------------------------------------"
  18 
  19 # How to resolve the above problem with a loop:
  20 while read line
  21 do
  22   echo "$line"
  23 done <data-file
  24 # Thanks, Heiner Steven for pointing this out.
  25 
  26 echo "------------------------------------------------"
  27 
  28 # Use $IFS (Internal Field Separator variable) to split a line of input to
  29 # "read", if you do not want the default to be whitespace.
  30 
  31 echo "List of all users:"
  32 OIFS=$IFS; IFS=:       # /etc/passwd uses ":" for field separator.
  33 while read name passwd uid gid fullname ignore
  34 do
  35   echo "$name ($fullname)"
  36 done </etc/passwd   # I/O redirection.
  37 IFS=$OIFS              # Restore original $IFS.
  38 # This code snippet also by Heiner Steven.
  39 
  40 
  41 
  42 #  Setting the $IFS variable within the loop itself
  43 #+ eliminates the need for storing the original $IFS
  44 #+ in a temporary variable.
  45 #  Thanks, Dim Segebart, for pointing this out.
  46 echo "------------------------------------------------"
  47 echo "List of all users:"
  48 
  49 while IFS=: read name passwd uid gid fullname ignore
  50 do
  51   echo "$name ($fullname)"
  52 done </etc/passwd   # I/O redirection.
  53 
  54 echo
  55 echo "\$IFS still $IFS"
  56 
  57 exit 0

Note

Piping output to a read, using echo to set variables will fail.

Yet, piping the output of cat seems to work.

   1 cat file1 file2 |
   2 while read line
   3 do
   4 echo $line
   5 done

However, as Bjön Eriksson shows:


Example 15-8. Problems reading from a pipe

   1 #!/bin/sh
   2 # readpipe.sh
   3 # This example contributed by Bjon Eriksson.
   4 
   5 ### shopt -s lastpipe
   6 
   7 last="(null)"
   8 cat $0 |
   9 while read line
  10 do
  11     echo "{$line}"
  12     last=$line
  13 done
  14 
  15 echo
  16 echo "++++++++++++++++++++++"
  17 printf "\nAll done, last: $last\n" #  The output of this line
  18                                    #+ changes if you uncomment line 5.
  19                                    #  (Bash, version -ge 4.2 required.)
  20 
  21 exit 0  # End of code.
  22         # (Partial) output of script follows.
  23         # The 'echo' supplies extra brackets.
  24 
  25 #############################################
  26 
  27 ./readpipe.sh 
  28 
  29 {#!/bin/sh}
  30 {last="(null)"}
  31 {cat $0 |}
  32 {while read line}
  33 {do}
  34 {echo "{$line}"}
  35 {last=$line}
  36 {done}
  37 {printf "nAll done, last: $lastn"}
  38 
  39 
  40 All done, last: (null)
  41 
  42 The variable (last) is set within the loop/subshell
  43 but its value does not persist outside the loop.

The gendiff script, usually found in /usr/bin on many Linux distros, pipes the output of find to a while read construct.
   1 find $1 \( -name "*$2" -o -name ".*$2" \) -print |
   2 while read f; do
   3 . . .

Tip

It is possible to paste text into the input field of a read (but not multiple lines!). See Example A-38.

Filesystem

cd

The familiar cd change directory command finds use in scripts where execution of a command requires being in a specified directory.

   1 (cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -)
[from the previously cited example by Alan Cox]

The -P (physical) option to cd causes it to ignore symbolic links.

cd - changes to $OLDPWD, the previous working directory.

Caution

The cd command does not function as expected when presented with two forward slashes.
 bash$ cd //
 bash$ pwd
 //
 	      
The output should, of course, be /. This is a problem both from the command-line and in a script.

pwd

Print Working Directory. This gives the user's (or script's) current directory (see Example 15-9). The effect is identical to reading the value of the builtin variable $PWD.

pushd, popd, dirs

This command set is a mechanism for bookmarking working directories, a means of moving back and forth through directories in an orderly manner. A pushdown stack is used to keep track of directory names. Options allow various manipulations of the directory stack.

pushd dir-name pushes the path dir-name onto the directory stack (to the top of the stack) and simultaneously changes the current working directory to dir-name

popd removes (pops) the top directory path name off the directory stack and simultaneously changes the current working directory to the directory now at the top of the stack.

dirs lists the contents of the directory stack (compare this with the $DIRSTACK variable). A successful pushd or popd will automatically invoke dirs.

Scripts that require various changes to the current working directory without hard-coding the directory name changes can make good use of these commands. Note that the implicit $DIRSTACK array variable, accessible from within a script, holds the contents of the directory stack.


Example 15-9. Changing the current working directory

   1 #!/bin/bash
   2 
   3 dir1=/usr/local
   4 dir2=/var/spool
   5 
   6 pushd $dir1
   7 # Will do an automatic 'dirs' (list directory stack to stdout).
   8 echo "Now in directory `pwd`." # Uses back-quoted 'pwd'.
   9 
  10 # Now, do some stuff in directory 'dir1'.
  11 pushd $dir2
  12 echo "Now in directory `pwd`."
  13 
  14 # Now, do some stuff in directory 'dir2'.
  15 echo "The top entry in the DIRSTACK array is $DIRSTACK."
  16 popd
  17 echo "Now back in directory `pwd`."
  18 
  19 # Now, do some more stuff in directory 'dir1'.
  20 popd
  21 echo "Now back in original working directory `pwd`."
  22 
  23 exit 0
  24 
  25 # What happens if you don't 'popd' -- then exit the script?
  26 # Which directory do you end up in? Why?

Variables

let

The let command carries out arithmetic operations on variables. [3] In many cases, it functions as a less complex version of expr.


Example 15-10. Letting let do arithmetic.

   1 #!/bin/bash
   2 
   3 echo
   4 
   5 let a=11            # Same as 'a=11'
   6 let a=a+5           # Equivalent to  let "a = a + 5"
   7                     # (Double quotes and spaces make it more readable.)
   8 echo "11 + 5 = $a"  # 16
   9 
  10 let "a <<= 3"       # Equivalent to  let "a = a << 3"
  11 echo "\"\$a\" (=16) left-shifted 3 places = $a"
  12                     # 128
  13 
  14 let "a /= 4"        # Equivalent to  let "a = a / 4"
  15 echo "128 / 4 = $a" # 32
  16 
  17 let "a -= 5"        # Equivalent to  let "a = a - 5"
  18 echo "32 - 5 = $a"  # 27
  19 
  20 let "a *=  10"      # Equivalent to  let "a = a * 10"
  21 echo "27 * 10 = $a" # 270
  22 
  23 let "a %= 8"        # Equivalent to  let "a = a % 8"
  24 echo "270 modulo 8 = $a  (270 / 8 = 33, remainder $a)"
  25                     # 6
  26 
  27 
  28 # Does "let" permit C-style operators?
  29 # Yes, just as the (( ... )) double-parentheses construct does.
  30 
  31 let a++             # C-style (post) increment.
  32 echo "6++ = $a"     # 6++ = 7
  33 let a--             # C-style decrement.
  34 echo "7-- = $a"     # 7-- = 6
  35 # Of course, ++a, etc., also allowed . . .
  36 echo
  37 
  38 
  39 # Trinary operator.
  40 
  41 # Note that $a is 6, see above.
  42 let "t = a<7?7:11"   # True
  43 echo $t  # 7
  44 
  45 let a++
  46 let "t = a<7?7:11"   # False
  47 echo $t  #     11
  48 
  49 exit

Caution

The let command can, in certain contexts, return a surprising exit status.

   1 # Evgeniy Ivanov points out:
   2 
   3 var=0
   4 echo $?     # 0
   5             # As expected.
   6 
   7 let var++
   8 echo $?     # 1
   9             # The command was successful, so why isn't $?=0 ???
  10             # Anomaly!
  11 
  12 let var++
  13 echo $?     # 0
  14             # As expected.
  15 
  16 
  17 # Likewise . . .
  18 
  19 let var=0
  20 echo $?     # 1
  21             # The command was successful, so why isn't $?=0 ???
  22 
  23 #  However, as Jeff Gorak points out,
  24 #+ this is part of the design spec for 'let' . . .
  25 # "If the last ARG evaluates to 0, let returns 1;
  26 #  let returns 0 otherwise." ['help let']

eval

eval arg1 [arg2] ... [argN]

Combines the arguments in an expression or list of expressions and evaluates them. Any variables within the expression are expanded. The net result is to convert a string into a command.

Tip

The eval command can be used for code generation from the command-line or within a script.

 bash$ command_string="ps ax"
 bash$ process="ps ax"
 bash$ eval "$command_string" | grep "$process"
 26973 pts/3    R+     0:00 grep --color ps ax
 26974 pts/3    R+     0:00 ps ax
 	      

Each invocation of eval forces a re-evaluation of its arguments.
   1 a='$b'
   2 b='$c'
   3 c=d
   4 
   5 echo $a             # $b
   6                     # First level.
   7 eval echo $a        # $c
   8                     # Second level.
   9 eval eval echo $a   # d
  10                     # Third level.
  11 
  12 # Thank you, E. Choroba.


Example 15-11. Showing the effect of eval

   1 #!/bin/bash
   2 # Exercising "eval" ...
   3 
   4 y=`eval ls -l`  #  Similar to y=`ls -l`
   5 echo $y         #+ but linefeeds removed because "echoed" variable is unquoted.
   6 echo
   7 echo "$y"       #  Linefeeds preserved when variable is quoted.
   8 
   9 echo; echo
  10 
  11 y=`eval df`     #  Similar to y=`df`
  12 echo $y         #+ but linefeeds removed.
  13 
  14 #  When LF's not preserved, it may make it easier to parse output,
  15 #+ using utilities such as "awk".
  16 
  17 echo
  18 echo "==========================================================="
  19 echo
  20 
  21 eval "`seq 3 | sed -e 's/.*/echo var&=ABCDEFGHIJ/'`"
  22 # var1=ABCDEFGHIJ
  23 # var2=ABCDEFGHIJ
  24 # var3=ABCDEFGHIJ
  25 
  26 echo
  27 echo "==========================================================="
  28 echo
  29 
  30 
  31 # Now, showing how to do something useful with "eval" . . .
  32 # (Thank you, E. Choroba!)
  33 
  34 version=3.4     #  Can we split the version into major and minor
  35                 #+ part in one command?
  36 echo "version = $version"
  37 eval major=${version/./;minor=}     #  Replaces '.' in version by ';minor='
  38                                     #  The substitution yields '3; minor=4'
  39                                     #+ so eval does minor=4, major=3
  40 echo Major: $major, minor: $minor   #  Major: 3, minor: 4


Example 15-12. Using eval to select among variables

   1 #!/bin/bash
   2 # arr-choice.sh
   3 
   4 #  Passing arguments to a function to select
   5 #+ one particular variable out of a group.
   6 
   7 arr0=( 10 11 12 13 14 15 )
   8 arr1=( 20 21 22 23 24 25 )
   9 arr2=( 30 31 32 33 34 35 )
  10 #       0  1  2  3  4  5      Element number (zero-indexed)
  11 
  12 
  13 choose_array ()
  14 {
  15   eval array_member=\${arr${array_number}[element_number]}
  16   #                 ^       ^^^^^^^^^^^^
  17   #  Using eval to construct the name of a variable,
  18   #+ in this particular case, an array name.
  19 
  20   echo "Element $element_number of array $array_number is $array_member"
  21 } #  Function can be rewritten to take parameters.
  22 
  23 array_number=0    # First array.
  24 element_number=3
  25 choose_array      # 13
  26 
  27 array_number=2    # Third array.
  28 element_number=4
  29 choose_array      # 34
  30 
  31 array_number=3    # Null array (arr3 not allocated).
  32 element_number=4
  33 choose_array      # (null)
  34 
  35 # Thank you, Antonio Macchi, for pointing this out.


Example 15-13. Echoing the command-line parameters

   1 #!/bin/bash
   2 # echo-params.sh
   3 
   4 # Call this script with a few command-line parameters.
   5 # For example:
   6 #     sh echo-params.sh first second third fourth fifth
   7 
   8 params=$#              # Number of command-line parameters.
   9 param=1                # Start at first command-line param.
  10 
  11 while [ "$param" -le "$params" ]
  12 do
  13   echo -n "Command-line parameter "
  14   echo -n \$$param     #  Gives only the *name* of variable.
  15 #         ^^^          #  $1, $2, $3, etc.
  16                        #  Why?
  17                        #  \$ escapes the first "$"
  18                        #+ so it echoes literally,
  19                        #+ and $param dereferences "$param" . . .
  20                        #+ . . . as expected.
  21   echo -n " = "
  22   eval echo \$$param   #  Gives the *value* of variable.
  23 # ^^^^      ^^^        #  The "eval" forces the *evaluation*
  24                        #+ of \$$
  25                        #+ as an indirect variable reference.
  26 
  27 (( param ++ ))         # On to the next.
  28 done
  29 
  30 exit $?
  31 
  32 # =================================================
  33 
  34 $ sh echo-params.sh first second third fourth fifth
  35 Command-line parameter $1 = first
  36 Command-line parameter $2 = second
  37 Command-line parameter $3 = third
  38 Command-line parameter $4 = fourth
  39 Command-line parameter $5 = fifth


Example 15-14. Forcing a log-off

   1 #!/bin/bash
   2 # Killing ppp to force a log-off.
   3 # For dialup connection, of course.
   4 
   5 # Script should be run as root user.
   6 
   7 SERPORT=ttyS3
   8 #  Depending on the hardware and even the kernel version,
   9 #+ the modem port on your machine may be different --
  10 #+ /dev/ttyS1 or /dev/ttyS2.
  11 
  12 
  13 killppp="eval kill -9 `ps ax | awk '/ppp/ { print $1 }'`"
  14 #                     -------- process ID of ppp -------  
  15 
  16 $killppp                     # This variable is now a command.
  17 
  18 
  19 # The following operations must be done as root user.
  20 
  21 chmod 666 /dev/$SERPORT      # Restore r+w permissions, or else what?
  22 #  Since doing a SIGKILL on ppp changed the permissions on the serial port,
  23 #+ we restore permissions to previous state.
  24 
  25 rm /var/lock/LCK..$SERPORT   # Remove the serial port lock file. Why?
  26 
  27 exit $?
  28 
  29 # Exercises:
  30 # ---------
  31 # 1) Have script check whether root user is invoking it.
  32 # 2) Do a check on whether the process to be killed
  33 #+   is actually running before attempting to kill it.   
  34 # 3) Write an alternate version of this script based on 'fuser':
  35 #+      if [ fuser -s /dev/modem ]; then . . .


Example 15-15. A version of rot13

   1 #!/bin/bash
   2 # A version of "rot13" using 'eval'.
   3 # Compare to "rot13.sh" example.
   4 
   5 setvar_rot_13()              # "rot13" scrambling
   6 {
   7   local varname=$1 varvalue=$2
   8   eval $varname='$(echo "$varvalue" | tr a-z n-za-m)'
   9 }
  10 
  11 
  12 setvar_rot_13 var "foobar"   # Run "foobar" through rot13.
  13 echo $var                    # sbbone
  14 
  15 setvar_rot_13 var "$var"     # Run "sbbone" through rot13.
  16                              # Back to original variable.
  17 echo $var                    # foobar
  18 
  19 # This example by Stephane Chazelas.
  20 # Modified by document author.
  21 
  22 exit 0

Here is another example of using eval to evaluate a complex expression, this one from an earlier version of YongYe's Tetris game script.

   1 eval ${1}+=\"${x} ${y} \"

Example A-53 uses eval to convert array elements into a command list.

The eval command occurs in the older version of indirect referencing.
   1 eval var=\$$var

Tip

The eval command can be used to parameterize brace expansion.

Caution

The eval command can be risky, and normally should be avoided when there exists a reasonable alternative. An eval $COMMANDS executes the contents of COMMANDS, which may contain such unpleasant surprises as rm -rf *. Running an eval on unfamiliar code written by persons unknown is living dangerously.

set

The set command changes the value of internal script variables/options. One use for this is to toggle option flags which help determine the behavior of the script. Another application for it is to reset the positional parameters that a script sees as the result of a command (set `command`). The script can then parse the fields of the command output.


Example 15-16. Using set with positional parameters

   1 #!/bin/bash
   2 # ex34.sh
   3 # Script "set-test"
   4 
   5 # Invoke this script with three command-line parameters,
   6 # for example, "sh ex34.sh one two three".
   7 
   8 echo
   9 echo "Positional parameters before  set \`uname -a\` :"
  10 echo "Command-line argument #1 = $1"
  11 echo "Command-line argument #2 = $2"
  12 echo "Command-line argument #3 = $3"
  13 
  14 
  15 set `uname -a` # Sets the positional parameters to the output
  16                # of the command `uname -a`
  17 
  18 echo
  19 echo +++++
  20 echo $_        # +++++
  21 # Flags set in script.
  22 echo $-        # hB
  23 #                Anomalous behavior?
  24 echo
  25 
  26 echo "Positional parameters after  set \`uname -a\` :"
  27 # $1, $2, $3, etc. reinitialized to result of `uname -a`
  28 echo "Field #1 of 'uname -a' = $1"
  29 echo "Field #2 of 'uname -a' = $2"
  30 echo "Field #3 of 'uname -a' = $3"
  31 echo \#\#\#
  32 echo $_        # ###
  33 echo
  34 
  35 exit 0

More fun with positional parameters.


Example 15-17. Reversing the positional parameters

   1 #!/bin/bash
   2 # revposparams.sh: Reverse positional parameters.
   3 # Script by Dan Jacobson, with stylistic revisions by document author.
   4 
   5 
   6 set a\ b c d\ e;
   7 #     ^      ^     Spaces escaped 
   8 #       ^ ^        Spaces not escaped
   9 OIFS=$IFS; IFS=:;
  10 #              ^   Saving old IFS and setting new one.
  11 
  12 echo
  13 
  14 until [ $# -eq 0 ]
  15 do          #      Step through positional parameters.
  16   echo "### k0 = "$k""     # Before
  17   k=$1:$k;  #      Append each pos param to loop variable.
  18 #     ^
  19   echo "### k = "$k""      # After
  20   echo
  21   shift;
  22 done
  23 
  24 set $k  #  Set new positional parameters.
  25 echo -
  26 echo $# #  Count of positional parameters.
  27 echo -
  28 echo
  29 
  30 for i   #  Omitting the "in list" sets the variable -- i --
  31         #+ to the positional parameters.
  32 do
  33   echo $i  # Display new positional parameters.
  34 done
  35 
  36 IFS=$OIFS  # Restore IFS.
  37 
  38 #  Question:
  39 #  Is it necessary to set an new IFS, internal field separator,
  40 #+ in order for this script to work properly?
  41 #  What happens if you don't? Try it.
  42 #  And, why use the new IFS -- a colon -- in line 17,
  43 #+ to append to the loop variable?
  44 #  What is the purpose of this?
  45 
  46 exit 0
  47 
  48 $ ./revposparams.sh
  49 
  50 ### k0 = 
  51 ### k = a b
  52 
  53 ### k0 = a b
  54 ### k = c a b
  55 
  56 ### k0 = c a b
  57 ### k = d e c a b
  58 
  59 -
  60 3
  61 -
  62 
  63 d e
  64 c
  65 a b

Invoking set without any options or arguments simply lists all the environmental and other variables that have been initialized.

 bash$ set
 AUTHORCOPY=/home/bozo/posts
 BASH=/bin/bash
 BASH_VERSION=$'2.05.8(1)-release'
 ...
 XAUTHORITY=/home/bozo/.Xauthority
 _=/etc/bashrc
 variable22=abc
 variable23=xzy
 	      

Using set with the -- option explicitly assigns the contents of a variable to the positional parameters. If no variable follows the -- it unsets the positional parameters.


Example 15-18. Reassigning the positional parameters

   1 #!/bin/bash
   2 
   3 variable="one two three four five"
   4 
   5 set -- $variable
   6 # Sets positional parameters to the contents of "$variable".
   7 
   8 first_param=$1
   9 second_param=$2
  10 shift; shift        # Shift past first two positional params.
  11 # shift 2             also works.
  12 remaining_params="$*"
  13 
  14 echo
  15 echo "first parameter = $first_param"             # one
  16 echo "second parameter = $second_param"           # two
  17 echo "remaining parameters = $remaining_params"   # three four five
  18 
  19 echo; echo
  20 
  21 # Again.
  22 set -- $variable
  23 first_param=$1
  24 second_param=$2
  25 echo "first parameter = $first_param"             # one
  26 echo "second parameter = $second_param"           # two
  27 
  28 # ======================================================
  29 
  30 set --
  31 # Unsets positional parameters if no variable specified.
  32 
  33 first_param=$1
  34 second_param=$2
  35 echo "first parameter = $first_param"             # (null value)
  36 echo "second parameter = $second_param"           # (null value)
  37 
  38 exit 0

See also Example 11-2 and Example 16-56.

unset

The unset command deletes a shell variable, effectively setting it to null. Note that this command does not affect positional parameters.

 bash$ unset PATH
 
 bash$ echo $PATH
 
 bash$ 


Example 15-19. "Unsetting" a variable

   1 #!/bin/bash
   2 # unset.sh: Unsetting a variable.
   3 
   4 variable=hello                       #  Initialized.
   5 echo "variable = $variable"
   6 
   7 unset variable                       #  Unset.
   8                                      #  In this particular context,
   9                                      #+ same effect as:   variable=
  10 echo "(unset) variable = $variable"  #  $variable is null.
  11 
  12 if [ -z "$variable" ]                #  Try a string-length test.
  13 then
  14   echo "\$variable has zero length."
  15 fi
  16 
  17 exit 0

Note

In most contexts, an undeclared variable and one that has been unset are equivalent. However, the ${parameter:-default} parameter substitution construct can distinguish between the two.

export

The export [4] command makes available variables to all child processes of the running script or shell. One important use of the export command is in startup files, to initialize and make accessible environmental variables to subsequent user processes.

Caution

Unfortunately, there is no way to export variables back to the parent process, to the process that called or invoked the script or shell.


Example 15-20. Using export to pass a variable to an embedded awk script

   1 #!/bin/bash
   2 
   3 #  Yet another version of the "column totaler" script (col-totaler.sh)
   4 #+ that adds up a specified column (of numbers) in the target file.
   5 #  This uses the environment to pass a script variable to 'awk' . . .
   6 #+ and places the awk script in a variable.
   7 
   8 
   9 ARGS=2
  10 E_WRONGARGS=85
  11 
  12 if [ $# -ne "$ARGS" ] # Check for proper number of command-line args.
  13 then
  14    echo "Usage: `basename $0` filename column-number"
  15    exit $E_WRONGARGS
  16 fi
  17 
  18 filename=$1
  19 column_number=$2
  20 
  21 #===== Same as original script, up to this point =====#
  22 
  23 export column_number
  24 # Export column number to environment, so it's available for retrieval.
  25 
  26 
  27 # -----------------------------------------------
  28 awkscript='{ total += $ENVIRON["column_number"] }
  29 END { print total }'
  30 # Yes, a variable can hold an awk script.
  31 # -----------------------------------------------
  32 
  33 # Now, run the awk script.
  34 awk "$awkscript" "$filename"
  35 
  36 # Thanks, Stephane Chazelas.
  37 
  38 exit 0

Tip

It is possible to initialize and export variables in the same operation, as in export var1=xxx.

However, as Greg Keraunen points out, in certain situations this may have a different effect than setting a variable, then exporting it.

 bash$ export var=(a b); echo ${var[0]}
 (a b)
 
 
 
 bash$ var=(a b); export var; echo ${var[0]}
 a
 	      

Note

A variable to be exported may require special treatment. See Example M-2.

declare, typeset

The declare and typeset commands specify and/or restrict properties of variables.

readonly

Same as declare -r, sets a variable as read-only, or, in effect, as a constant. Attempts to change the variable fail with an error message. This is the shell analog of the C language const type qualifier.

getopts

This powerful tool parses command-line arguments passed to the script. This is the Bash analog of the getopt external command and the getopt library function familiar to C programmers. It permits passing and concatenating multiple options [5] and associated arguments to a script (for example scriptname -abc -e /usr/local).

The getopts construct uses two implicit variables. $OPTIND is the argument pointer (OPTion INDex) and $OPTARG (OPTion ARGument) the (optional) argument attached to an option. A colon following the option name in the declaration tags that option as having an associated argument.

A getopts construct usually comes packaged in a while loop, which processes the options and arguments one at a time, then increments the implicit $OPTIND variable to point to the next.

Note

  1. The arguments passed from the command-line to the script must be preceded by a dash (-). It is the prefixed - that lets getopts recognize command-line arguments as options. In fact, getopts will not process arguments without the prefixed -, and will terminate option processing at the first argument encountered lacking them.

  2. The getopts template differs slightly from the standard while loop, in that it lacks condition brackets.

  3. The getopts construct is a highly functional replacement for the traditional getopt external command.

   1 while getopts ":abcde:fg" Option
   2 # Initial declaration.
   3 # a, b, c, d, e, f, and g are the options (flags) expected.
   4 # The : after option 'e' shows it will have an argument passed with it.
   5 do
   6   case $Option in
   7     a ) # Do something with variable 'a'.
   8     b ) # Do something with variable 'b'.
   9     ...
  10     e)  # Do something with 'e', and also with $OPTARG,
  11         # which is the associated argument passed with option 'e'.
  12     ...
  13     g ) # Do something with variable 'g'.
  14   esac
  15 done
  16 shift $(($OPTIND - 1))
  17 # Move argument pointer to next.
  18 
  19 # All this is not nearly as complicated as it looks <grin>.


Example 15-21. Using getopts to read the options/arguments passed to a script

   1 #!/bin/bash
   2 # ex33.sh: Exercising getopts and OPTIND
   3 #          Script modified 10/09/03 at the suggestion of Bill Gradwohl.
   4 
   5 
   6 # Here we observe how 'getopts' processes command-line arguments to script.
   7 # The arguments are parsed as "options" (flags) and associated arguments.
   8 
   9 # Try invoking this script with:
  10 #   'scriptname -mn'
  11 #   'scriptname -oq qOption' (qOption can be some arbitrary string.)
  12 #   'scriptname -qXXX -r'
  13 #
  14 #   'scriptname -qr'
  15 #+      - Unexpected result, takes "r" as the argument to option "q"
  16 #   'scriptname -q -r' 
  17 #+      - Unexpected result, same as above
  18 #   'scriptname -mnop -mnop'  - Unexpected result
  19 #   (OPTIND is unreliable at stating where an option came from.)
  20 #
  21 #  If an option expects an argument ("flag:"), then it will grab
  22 #+ whatever is next on the command-line.
  23 
  24 NO_ARGS=0 
  25 E_OPTERROR=85
  26 
  27 if [ $# -eq "$NO_ARGS" ]    # Script invoked with no command-line args?
  28 then
  29   echo "Usage: `basename $0` options (-mnopqrs)"
  30   exit $E_OPTERROR          # Exit and explain usage.
  31                             # Usage: scriptname -options
  32                             # Note: dash (-) necessary
  33 fi  
  34 
  35 
  36 while getopts ":mnopq:rs" Option
  37 do
  38   case $Option in
  39     m     ) echo "Scenario #1: option -m-   [OPTIND=${OPTIND}]";;
  40     n | o ) echo "Scenario #2: option -$Option-   [OPTIND=${OPTIND}]";;
  41     p     ) echo "Scenario #3: option -p-   [OPTIND=${OPTIND}]";;
  42     q     ) echo "Scenario #4: option -q-\
  43                   with argument \"$OPTARG\"   [OPTIND=${OPTIND}]";;
  44     #  Note that option 'q' must have an associated argument,
  45     #+ otherwise it falls through to the default.
  46     r | s ) echo "Scenario #5: option -$Option-";;
  47     *     ) echo "Unimplemented option chosen.";;   # Default.
  48   esac
  49 done
  50 
  51 shift $(($OPTIND - 1))
  52 #  Decrements the argument pointer so it points to next argument.
  53 #  $1 now references the first non-option item supplied on the command-line
  54 #+ if one exists.
  55 
  56 exit $?
  57 
  58 #   As Bill Gradwohl states,
  59 #  "The getopts mechanism allows one to specify:  scriptname -mnop -mnop
  60 #+  but there is no reliable way to differentiate what came
  61 #+ from where by using OPTIND."
  62 #  There are, however, workarounds.

Script Behavior

source, . (dot command)

This command, when invoked from the command-line, executes a script. Within a script, a source file-name loads the file file-name. Sourcing a file (dot-command) imports code into the script, appending to the script (same effect as the #include directive in a C program). The net result is the same as if the "sourced" lines of code were physically present in the body of the script. This is useful in situations when multiple scripts use a common data file or function library.


Example 15-22. "Including" a data file

   1 #!/bin/bash
   2 #  Note that this example must be invoked with bash, i.e., bash ex38.sh
   3 #+ not  sh ex38.sh !
   4 
   5 . data-file    # Load a data file.
   6 # Same effect as "source data-file", but more portable.
   7 
   8 #  The file "data-file" must be present in current working directory,
   9 #+ since it is referred to by its basename.
  10 
  11 # Now, let's reference some data from that file.
  12 
  13 echo "variable1 (from data-file) = $variable1"
  14 echo "variable3 (from data-file) = $variable3"
  15 
  16 let "sum = $variable2 + $variable4"
  17 echo "Sum of variable2 + variable4 (from data-file) = $sum"
  18 echo "message1 (from data-file) is \"$message1\""
  19 #                                  Escaped quotes
  20 echo "message2 (from data-file) is \"$message2\""
  21 
  22 print_message This is the message-print function in the data-file.
  23 
  24 
  25 exit $?

File data-file for Example 15-22, above. Must be present in same directory.

   1 # This is a data file loaded by a script.
   2 # Files of this type may contain variables, functions, etc.
   3 # It loads with a 'source' or '.' command from a shell script.
   4 
   5 # Let's initialize some variables.
   6 
   7 variable1=23
   8 variable2=474
   9 variable3=5
  10 variable4=97
  11 
  12 message1="Greetings from *** line $LINENO *** of the data file!"
  13 message2="Enough for now. Goodbye."
  14 
  15 print_message ()
  16 {   # Echoes any message passed to it.
  17 
  18   if [ -z "$1" ]
  19   then
  20     return 1 # Error, if argument missing.
  21   fi
  22 
  23   echo
  24 
  25   until [ -z "$1" ]
  26   do             # Step through arguments passed to function.
  27     echo -n "$1" # Echo args one at a time, suppressing line feeds.
  28     echo -n " "  # Insert spaces between words.
  29     shift        # Next one.
  30   done  
  31 
  32   echo
  33 
  34   return 0
  35 }

If the sourced file is itself an executable script, then it will run, then return control to the script that called it. A sourced executable script may use a return for this purpose.

Arguments may be (optionally) passed to the sourced file as positional parameters.
   1 source $filename $arg1 arg2

It is even possible for a script to source itself, though this does not seem to have any practical applications.


Example 15-23. A (useless) script that sources itself

   1 #!/bin/bash
   2 # self-source.sh: a script sourcing itself "recursively."
   3 # From "Stupid Script Tricks," Volume II.
   4 
   5 MAXPASSCNT=100    # Maximum number of execution passes.
   6 
   7 echo -n  "$pass_count  "
   8 #  At first execution pass, this just echoes two blank spaces,
   9 #+ since $pass_count still uninitialized.
  10 
  11 let "pass_count += 1"
  12 #  Assumes the uninitialized variable $pass_count
  13 #+ can be incremented the first time around.
  14 #  This works with Bash and pdksh, but
  15 #+ it relies on non-portable (and possibly dangerous) behavior.
  16 #  Better would be to initialize $pass_count to 0 before incrementing.
  17 
  18 while [ "$pass_count" -le $MAXPASSCNT ]
  19 do
  20   . $0   # Script "sources" itself, rather than calling itself.
  21          # ./$0 (which would be true recursion) doesn't work here. Why?
  22 done  
  23 
  24 #  What occurs here is not actually recursion,
  25 #+ since the script effectively "expands" itself, i.e.,
  26 #+ generates a new section of code
  27 #+ with each pass through the 'while' loop',
  28 #  with each 'source' in line 20.
  29 #
  30 #  Of course, the script interprets each newly 'sourced' "#!" line
  31 #+ as a comment, and not as the start of a new script.
  32 
  33 echo
  34 
  35 exit 0   # The net effect is counting from 1 to 100.
  36          # Very impressive.
  37 
  38 # Exercise:
  39 # --------
  40 # Write a script that uses this trick to actually do something useful.

exit

Unconditionally terminates a script. [6] The exit command may optionally take an integer argument, which is returned to the shell as the exit status of the script. It is good practice to end all but the simplest scripts with an exit 0, indicating a successful run.

Note

If a script terminates with an exit lacking an argument, the exit status of the script is the exit status of the last command executed in the script, not counting the exit. This is equivalent to an exit $?.

Note

An exit command may also be used to terminate a subshell.

exec

This shell builtin replaces the current process with a specified command. Normally, when the shell encounters a command, it forks off a child process to actually execute the command. Using the exec builtin, the shell does not fork, and the command exec'ed replaces the shell. When used in a script, therefore, it forces an exit from the script when the exec'ed command terminates. [7]


Example 15-24. Effects of exec

   1 #!/bin/bash
   2 
   3 exec echo "Exiting \"$0\" at line $LINENO."   # Exit from script here.
   4 # $LINENO is an internal Bash variable set to the line number it's on.
   5 
   6 # ----------------------------------
   7 # The following lines never execute.
   8 
   9 echo "This echo fails to echo."
  10 
  11 exit 99                       #  This script will not exit here.
  12                               #  Check exit value after script terminates
  13                               #+ with an 'echo $?'.
  14                               #  It will *not* be 99.


Example 15-25. A script that exec's itself

   1 #!/bin/bash
   2 # self-exec.sh
   3 
   4 # Note: Set permissions on this script to 555 or 755,
   5 #       then call it with ./self-exec.sh or sh ./self-exec.sh.
   6 
   7 echo
   8 
   9 echo "This line appears ONCE in the script, yet it keeps echoing."
  10 echo "The PID of this instance of the script is still $$."
  11 #     Demonstrates that a subshell is not forked off.
  12 
  13 echo "==================== Hit Ctl-C to exit ===================="
  14 
  15 sleep 1
  16 
  17 exec $0   #  Spawns another instance of this same script
  18           #+ that replaces the previous one.
  19 
  20 echo "This line will never echo!"  # Why not?
  21 
  22 exit 99                            # Will not exit here!
  23                                    # Exit code will not be 99!

An exec also serves to reassign file descriptors. For example, exec <zzz-file replaces stdin with the file zzz-file.

Note

The -exec option to find is not the same as the exec shell builtin.

shopt

This command permits changing shell options on the fly (see Example 25-1 and Example 25-2). It often appears in the Bash startup files, but also has its uses in scripts. Needs version 2 or later of Bash.

   1 shopt -s cdspell
   2 # Allows minor misspelling of directory names with 'cd'
   3 # Option -s sets, -u unsets.
   4 
   5 cd /hpme  # Oops! Mistyped '/home'.
   6 pwd       # /home
   7           # The shell corrected the misspelling.

caller

Putting a caller command inside a function echoes to stdout information about the caller of that function.

   1 #!/bin/bash
   2 
   3 function1 ()
   4 {
   5   # Inside function1 ().
   6   caller 0   # Tell me about it.
   7 }
   8 
   9 function1    # Line 9 of script.
  10 
  11 # 9 main test.sh
  12 # ^                 Line number that the function was called from.
  13 #   ^^^^            Invoked from "main" part of script.
  14 #        ^^^^^^^    Name of calling script.
  15 
  16 caller 0     # Has no effect because it's not inside a function.

A caller command can also return caller information from a script sourced within another script. Analogous to a function, this is a "subroutine call."

You may find this command useful in debugging.

Commands

true

A command that returns a successful (zero) exit status, but does nothing else.

 bash$ true
 bash$ echo $?
 0
 	      

   1 # Endless loop
   2 while true   # alias for ":"
   3 do
   4    operation-1
   5    operation-2
   6    ...
   7    operation-n
   8    # Need a way to break out of loop or script will hang.
   9 done

false

A command that returns an unsuccessful exit status, but does nothing else.

 bash$ false
 bash$ echo $?
 1
 	      

   1 # Testing "false" 
   2 if false
   3 then
   4   echo "false evaluates \"true\""
   5 else
   6   echo "false evaluates \"false\""
   7 fi
   8 # false evaluates "false"
   9 
  10 
  11 # Looping while "false" (null loop)
  12 while false
  13 do
  14    # The following code will not execute.
  15    operation-1
  16    operation-2
  17    ...
  18    operation-n
  19    # Nothing happens!
  20 done   

type [cmd]

Similar to the which external command, type cmd identifies "cmd." Unlike which, type is a Bash builtin. The useful -a option to type identifies keywords and builtins, and also locates system commands with identical names.

 bash$ type '['
 [ is a shell builtin
 bash$ type -a '['
 [ is a shell builtin
 [ is /usr/bin/[
 
 
 bash$ type type
 type is a shell builtin
 	      

The type command can be useful for testing whether a certain command exists.

hash [cmds]

Records the path name of specified commands -- in the shell hash table [8] -- so the shell or script will not need to search the $PATH on subsequent calls to those commands. When hash is called with no arguments, it simply lists the commands that have been hashed. The -r option resets the hash table.

bind

The bind builtin displays or modifies readline [9] key bindings.

help

Gets a short usage summary of a shell builtin. This is the counterpart to whatis, but for builtins. The display of help information got a much-needed update in the version 4 release of Bash.

 bash$ help exit
 exit: exit [n]
    Exit the shell with a status of N.  If N is omitted, the exit status
    is that of the last command executed.
 	      

15.1. Job Control Commands

Certain of the following job control commands take a job identifier as an argument. See the table at end of the chapter.

jobs

Lists the jobs running in the background, giving the job number. Not as useful as ps.

Note

It is all too easy to confuse jobs and processes. Certain builtins, such as kill, disown, and wait accept either a job number or a process number as an argument. The fg, bg and jobs commands accept only a job number.

 bash$ sleep 100 &
 [1] 1384
 
 bash $ jobs
 [1]+  Running                 sleep 100 &

"1" is the job number (jobs are maintained by the current shell). "1384" is the PID or process ID number (processes are maintained by the system). To kill this job/process, either a kill %1 or a kill 1384 works.

Thanks, S.C.

disown

Remove job(s) from the shell's table of active jobs.

fg, bg

The fg command switches a job running in the background into the foreground. The bg command restarts a suspended job, and runs it in the background. If no job number is specified, then the fg or bg command acts upon the currently running job.

wait

Suspend script execution until all jobs running in background have terminated, or until the job number or process ID specified as an option terminates. Returns the exit status of waited-for command.

You may use the wait command to prevent a script from exiting before a background job finishes executing (this would create a dreaded orphan process).


Example 15-26. Waiting for a process to finish before proceeding

   1 #!/bin/bash
   2 
   3 ROOT_UID=0   # Only users with $UID 0 have root privileges.
   4 E_NOTROOT=65
   5 E_NOPARAMS=66
   6 
   7 if [ "$UID" -ne "$ROOT_UID" ]
   8 then
   9   echo "Must be root to run this script."
  10   # "Run along kid, it's past your bedtime."
  11   exit $E_NOTROOT
  12 fi  
  13 
  14 if [ -z "$1" ]
  15 then
  16   echo "Usage: `basename $0` find-string"
  17   exit $E_NOPARAMS
  18 fi
  19 
  20 
  21 echo "Updating 'locate' database..."
  22 echo "This may take a while."
  23 updatedb /usr &     # Must be run as root.
  24 
  25 wait
  26 # Don't run the rest of the script until 'updatedb' finished.
  27 # You want the the database updated before looking up the file name.
  28 
  29 locate $1
  30 
  31 #  Without the 'wait' command, in the worse case scenario,
  32 #+ the script would exit while 'updatedb' was still running,
  33 #+ leaving it as an orphan process.
  34 
  35 exit 0

Optionally, wait can take a job identifier as an argument, for example, wait%1 or wait $PPID. [10] See the job id table.

Tip

Within a script, running a command in the background with an ampersand (&) may cause the script to hang until ENTER is hit. This seems to occur with commands that write to stdout. It can be a major annoyance.
   1 #!/bin/bash
   2 # test.sh		  
   3 
   4 ls -l &
   5 echo "Done."
 bash$ ./test.sh
 Done.
 [bozo@localhost test-scripts]$ total 1
 -rwxr-xr-x    1 bozo     bozo           34 Oct 11 15:09 test.sh
 _
                

    As Walter Brameld IV explains it:

    As far as I can tell, such scripts don't actually hang. It just
    seems that they do because the background command writes text to
    the console after the prompt. The user gets the impression that
    the prompt was never displayed. Here's the sequence of events:

    1. Script launches background command.
    2. Script exits.
    3. Shell displays the prompt.
    4. Background command continues running and writing text to the
       console.
    5. Background command finishes.
    6. User doesn't see a prompt at the bottom of the output, thinks script
       is hanging.

Placing a wait after the background command seems to remedy this.
   1 #!/bin/bash
   2 # test.sh		  
   3 
   4 ls -l &
   5 echo "Done."
   6 wait
 bash$ ./test.sh
 Done.
 [bozo@localhost test-scripts]$ total 1
 -rwxr-xr-x    1 bozo     bozo           34 Oct 11 15:09 test.sh
                
Redirecting the output of the command to a file or even to /dev/null also takes care of this problem.

suspend

This has a similar effect to Control-Z, but it suspends the shell (the shell's parent process should resume it at an appropriate time).

logout

Exit a login shell, optionally specifying an exit status.

times

Gives statistics on the system time elapsed when executing commands, in the following form:
 0m0.020s 0m0.020s

This capability is of relatively limited value, since it is not common to profile and benchmark shell scripts.

kill

Forcibly terminate a process by sending it an appropriate terminate signal (see Example 17-6).


Example 15-27. A script that kills itself

   1 #!/bin/bash
   2 # self-destruct.sh
   3 
   4 kill $$  # Script kills its own process here.
   5          # Recall that "$$" is the script's PID.
   6 
   7 echo "This line will not echo."
   8 # Instead, the shell sends a "Terminated" message to stdout.
   9 
  10 exit 0   # Normal exit? No!
  11 
  12 #  After this script terminates prematurely,
  13 #+ what exit status does it return?
  14 #
  15 # sh self-destruct.sh
  16 # echo $?
  17 # 143
  18 #
  19 # 143 = 128 + 15
  20 #             TERM signal

Note

kill -l lists all the signals (as does the file /usr/include/asm/signal.h). A kill -9 is a sure kill, which will usually terminate a process that stubbornly refuses to die with a plain kill. Sometimes, a kill -15 works. A zombie process, that is, a child process that has terminated, but that the parent process has not (yet) killed, cannot be killed by a logged-on user -- you can't kill something that is already dead -- but init will generally clean it up sooner or later.

killall

The killall command kills a running process by name, rather than by process ID. If there are multiple instances of a particular command running, then doing a killall on that command will terminate them all.

Note

This refers to the killall command in /usr/bin, not the killall script in /etc/rc.d/init.d.

command

The command directive disables aliases and functions for the command immediately following it.

 bash$ command ls
               

Note

This is one of three shell directives that effect script command processing. The others are builtin and enable.

builtin

Invoking builtin BUILTIN_COMMAND runs the command BUILTIN_COMMAND as a shell builtin, temporarily disabling both functions and external system commands with the same name.

enable

This either enables or disables a shell builtin command. As an example, enable -n kill disables the shell builtin kill, so that when Bash subsequently encounters kill, it invokes the external command /bin/kill.

The -a option to enable lists all the shell builtins, indicating whether or not they are enabled. The -f filename option lets enable load a builtin as a shared library (DLL) module from a properly compiled object file. [11].

autoload

This is a port to Bash of the ksh autoloader. With autoload in place, a function with an autoload declaration will load from an external file at its first invocation. [12] This saves system resources.

Note that autoload is not a part of the core Bash installation. It needs to be loaded in with enable -f (see above).


Table 15-1. Job identifiers

NotationMeaning
%NJob number [N]
%SInvocation (command-line) of job begins with string S
%?SInvocation (command-line) of job contains within it string S
%%"current" job (last job stopped in foreground or started in background)
%+"current" job (last job stopped in foreground or started in background)
%-Last job
$!Last background process

Notes

[1]

As Nathan Coulter points out, "while forking a process is a low-cost operation, executing a new program in the newly-forked child process adds more overhead."

[2]

An exception to this is the time command, listed in the official Bash documentation as a keyword ("reserved word").

[3]

Note that let cannot be used for setting string variables.

[4]

To Export information is to make it available in a more general context. See also scope.

[5]

An option is an argument that acts as a flag, switching script behaviors on or off. The argument associated with a particular option indicates the behavior that the option (flag) switches on or off.

[6]

Technically, an exit only terminates the process (or shell) in which it is running, not the parent process.

[7]

Unless the exec is used to reassign file descriptors.

[8]

Hashing is a method of creating lookup keys for data stored in a table. The data items themselves are "scrambled" to create keys, using one of a number of simple mathematical algorithms (methods, or recipes).

An advantage of hashing is that it is fast. A disadvantage is that collisions -- where a single key maps to more than one data item -- are possible.

For examples of hashing see Example A-20 and Example A-21.

[9]

The readline library is what Bash uses for reading input in an interactive shell.

[10]

This only applies to child processes, of course.

[11]

The C source for a number of loadable builtins is typically found in the /usr/share/doc/bash-?.??/functions directory.

Note that the -f option to enable is not portable to all systems.

[12]

The same effect as autoload can be achieved with typeset -fu.