目录
当前位置: 首页 > 文档资料 > Shell 中文文档 >

Chapter 11. Internal Commands and Builtins

优质
小牛编辑
139浏览
2023-12-01

A builtinis a commandcontained within the Bash toolset, literally built in. This is eitherfor performance reasons -- builtins execute faster than externalcommands, which usually require forking off a separate process-- or because a particular builtin needs direct access to theshell internals.

When a command orthe shell itself initiates (orspawns) a newsubprocess to carry out a task, this is calledforking. This new processis the child, and the processthat forkedit off is theparent. While the childprocessis doing its work, theparent processis stillexecuting.

Note that while a parent processgets the process IDof thechild process, and can thuspass arguments to it, the reverse is nottrue. Thiscan create problems that are subtle and hard to trackdown.

Example 11-1. A script that forks off multiple instances of itself

   1 #!/bin/bash
   2 # spawn.sh
   3
   4
   5 PIDS=$(pidof sh $0)  # Process IDs of the various instances of this script.
   6 P_array=( $PIDS )    # Put them in an array (why?).
   7 echo $PIDS           # Show process IDs of parent and child processes.
   8 let "instances = ${#P_array[*]} - 1"  # Count elements, less 1.
   9                                       # Why subtract 1?
  10 echo "$instances instance(s) of this script running."
  11 echo "[Hit Ctl-C to exit.]"; echo
  12
  13
  14 sleep 1              # Wait.
  15 sh $0                # Play it again, Sam.
  16
  17 exit 0               # Not necessary; script will never get to here.
  18                      # Why not?
  19
  20 #  After exiting with a Ctl-C,
  21 #+ do all the spawned instances of the script die?
  22 #  If so, why?
  23
  24 # Note:
  25 # ----
  26 # Be careful not to run this script too long.
  27 # It will eventually eat up too many system resources.
  28
  29 #  Is having a script spawn multiple instances of itself
  30 #+ an advisable scripting technique.
  31 #  Why or why not?

Generally, a Bash builtindoes not fork a subprocess when it executes withina script. An external system command or filter ina script usually willfork asubprocess.

A builtin may be a synonym to a system command of the samename, but Bash reimplements it internally. For example,the Bash echocommand is not the same as/bin/echo, although their behavior isalmost 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 keywordis a reservedword, token oroperator. Keywords have a special meaning to the shell,and indeed are the building blocks of the shell'ssyntax. As examples, "for","while", "do", and"!"are keywords. Similar to a builtin, a keyword is hard-coded intoBash, but unlike a builtin, a keyword isnot by itself a command, but part of a larger command structure.

I/O

echo

prints (to stdout) an expressionor variable (see Example 4-1).

   1 echo Hello
   2 echo $a

An echorequires the-eoption to print escaped characters. SeeExample 5-2.

Normally, each echocommand printsa terminal newline, but the -noptionsuppresses this.

 

An echocan be used to feed asequence 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

 

An echo, in combination withcommand substitutioncan set a variable.

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

See also Example 12-19, Example 12-3, Example 12-42, and Example 12-43.

Be aware that echo `command`deletes any linefeeds that the outputof commandgenerates.

The $IFS(internal fieldseparator) variable normally contains

\n(linefeed) as one of its set ofwhitespacecharacters. Bash therefore splits the output ofcommandat linefeedsinto arguments to echo. Thenechooutputs 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 362 Nov 7 2000 seconds.au

So, how can we embed a linefeed within anechoedcharacter 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.

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

 
bash$ 


type -a echo

 
echo is a shell builtin
 echo is /bin/echo
 	      

printf

The printf, formatted print, command is anenhanced echo. It is a limited variantof the C language printf()libraryfunction, and its syntax is somewhat different.

printfformat-string... parameter...

This is the Bash builtin versionof the /bin/printfor/usr/bin/printfcommand. See theprintfmanpage (of the system command)for in-depth coverage.

Older versions of Bash may not supportprintf.

Example 11-2. printfin action

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

Formatting error messages is a useful application ofprintf

   1 E_BADDIR=65
   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.

read

"Reads"the valueof a variable from stdin, thatis, interactively fetches input from the keyboard. The-aoption lets readget array variables (see Example 26-6).

Example 11-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' (separated by a space or tab): "
  17 read var2 var3
  18 echo "var2 = $var2      var3 = $var3"
  19 # If you input only one value, the other variable(s) will remain unset (null).
  20
  21 exit 0

A readwithout an associated variableassigns its input to the dedicated variable $REPLY.

Example 11-4. What happens when readhas novariable

   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
  26 exit 0

Normally, inputting a \suppresses a newline during input toa read. The -roption causes an inputted \to beinterpreted literally.

Example 11-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, and again press <ENTER>."
   7 read var1     # The "\" suppresses the newline, when reading $var1.
   8               #     first line \
   9               #     second line
  10
  11 echo "var1 = $var1"
  12 #     var1 = first line second line
  13
  14 #  For each line terminated by a "\"
  15 #+ you get a prompt on the next line to continue feeding characters into var1.
  16
  17 echo; echo
  18
  19 echo "Enter another string terminated by a \\ , then press <ENTER>."
  20 read -r var2  # The -r option causes the "\" to be read literally.
  21               #     first line \
  22
  23 echo "var2 = $var2"
  24 #     var2 = first line \
  25
  26 # Data entry terminates with the first <ENTER>.
  27
  28 echo
  29
  30 exit 0

The readcommand has some interestingoptions that permit echoing a prompt and even reading keystrokeswithout 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 -noption to readalso allows detection of the arrow keysand certain of the other unusual keys.

Example 11-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 #  Exercises:
  70 #  ---------
  71 #  1) Simplify this script by rewriting the multiple "if" tests
  72 #+    as a 'case' construct.
  73 #  2) Add detection of the "Home," "End," "PgUp," and "PgDn" keys.

 

The -noption to readwill not detect the ENTER(newline)key.

The -toption to readpermits timed input (see Example 9-4).

The readcommand may also"read"its variable value from a fileredirectedtostdin. If the file containsmore than one line, only the first line is assignedto the variable. If readhas more than one parameter, then each ofthese variables gets assigned a successive whitespace-delineatedstring. Caution!

Example 11-7. Using readwithfile 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

 

Pipingoutputto a read, using echoto set variables will fail.

Yet, piping the output of catseemstowork.

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

However, as Bj鰊 Eriksson shows:

Example 11-8. Problems reading from a pipe

   1 #!/bin/sh
   2 # readpipe.sh
   3 # This example contributed by Bjon Eriksson.
   4
   5 last="(null)"
   6 cat $0 |
   7 while read line
   8 do
   9     echo "{$line}"
  10     last=$line
  11 done
  12 printf "\nAll done, last:$last\n"
  13
  14 exit 0  # End of code.
  15         # (Partial) output of script follows.
  16         # The 'echo' supplies extra brackets.
  17
  18 #############################################
  19
  20 ./readpipe.sh
  21
  22 {#!/bin/sh}
  23 {last="(null)"}
  24 {cat $0 |}
  25 {while read line}
  26 {do}
  27 {echo "{$line}"}
  28 {last=$line}
  29 {done}
  30 {printf "nAll done, last:$lastn"}
  31
  32
  33 All done, last:(null)
  34
  35 The variable (last) is set within the subshell but unset outside.

The gendiffscript, usually found in/usr/binon many Linux distros, pipes theoutput of findto awhile readconstruct.

   1 find $1 \( -name "*$2" -o -name ".*$2" \) -print |
   2 while read f; do
   3 . . .

Filesystem

cd

The familiar cdchange directorycommand finds use in scripts where execution of a commandrequires being in a specified directory.

   1 (cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -)

[from the previously citedexample by Alan Cox]

The -P(physical) option tocdcauses it to ignore symboliclinks.

cd -changes to $OLDPWD, the previous workingdirectory.

 

The cdcommand does not functionas 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 11-9). The effect is identical toreading 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 orderlymanner. A pushdown stack is used to keep track of directory names.Options allow various manipulations of the directory stack.

pushddir-namepushes the pathdir-nameonto the directorystack and simultaneously changes the current workingdirectory to dir-name

popdremoves(pops) the top directory path name off the directory stackand simultaneously changes the current working directoryto that directory popped from the stack.

dirslists the contents of the directorystack (compare this with the $DIRSTACKvariable).A successful pushdorpopdwill automatically invokedirs.

Scripts that require various changes to the currentworking directory without hard-coding the directory namechanges can make good use of these commands. Note thatthe implicit $DIRSTACKarray variable,accessible from within a script, holds the contents ofthe directory stack.

Example 11-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 letcommand carries out arithmeticoperations on variables. In many cases, it functions as a lesscomplex version of expr.

Example 11-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 echo
  28
  29 exit 0
eval

eval arg1 [arg2] ... [argN]

Combines the arguments in an expression or list ofexpressions and evaluatesthem. Anyvariables contained within the expression are expanded. Theresult translates into a command. This can be useful forcode generation from the command line or within a script.

 
bash$ 


process=xterm

 
bash$ 


show_process="eval ps ax | grep $process"

 
bash$ 


$show_process

 
1867 tty1     S      0:02 xterm
 2779 tty1     S      0:00 xterm
 2886 pts/1    S      0:00 grep xterm
 	      

Example 11-11. Showing the effect of eval

   1 #!/bin/bash
   2
   3 y=`eval ls -l`  #  Similar to y=`ls -l`
   4 echo $y         #+ but linefeeds removed because "echoed" variable is unquoted.
   5 echo
   6 echo "$y"       #  Linefeeds preserved when variable is quoted.
   7
   8 echo; echo
   9
  10 y=`eval df`     #  Similar to y=`df`
  11 echo $y         #+ but linefeeds removed.
  12
  13 #  When LF's not preserved, it may make it easier to parse output,
  14 #+ using utilities such as "awk".
  15
  16 echo
  17 echo "==========================================================="
  18 echo
  19
  20 # Now, showing how to "expand" a variable using "eval" . . .
  21
  22 for i in 1 2 3 4 5; do
  23   eval value=$i
  24   #  value=$i has same effect. The "eval" is not necessary here.
  25   #  A variable lacking a meta-meaning evaluates to itself --
  26   #+ it can't expand to anything other than its literal self.
  27   echo $value
  28 done
  29
  30 echo
  31 echo "---"
  32 echo
  33
  34 for i in ls df; do
  35   value=eval $i
  36   #  value=$i has an entirely different effect here.
  37   #  The "eval" evaluates the commands "ls" and "df" . . .
  38   #  The terms "ls" and "df" have a meta-meaning,
  39   #+ since they are interpreted as commands,
  40   #+ rather than just character strings.
  41   echo $value
  42 done
  43
  44
  45 exit 0

Example 11-12. Forcing a log-off

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

Example 11-13. 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

Rory Winston contributed the following instance of howuseful evalcan be.

Example 11-14. Using evalto force variablesubstitution in a Perl script

   1 In the Perl script "test.pl":
   2         ...
   3         my $WEBROOT = <WEBROOT_PATH>;
   4         ...
   5
   6 To force variable substitution try:
   7         $export WEBROOT_PATH=/usr/local/webroot
   8         $sed 's/<WEBROOT_PATH>/$WEBROOT_PATH/' < test.pl > out
   9
  10 But this just gives:
  11         my $WEBROOT = $WEBROOT_PATH;
  12
  13 However:
  14         $export WEBROOT_PATH=/usr/local/webroot
  15         $eval sed 's%\<WEBROOT_PATH\>%$WEBROOT_PATH%' < test.pl > out
  16 #        ====
  17
  18 That works fine, and gives the expected substitution:
  19         my $WEBROOT = /usr/local/webroot;
  20
  21
  22 ### Correction applied to original example by Paulo Marcel Coelho Aragao.
 

The evalcommand can berisky, and normally should be avoided when thereexists a reasonable alternative. An eval$COMMANDSexecutes the contents ofCOMMANDS, which maycontain such unpleasant surprises as rm -rf*. Running an evalonunfamiliar code written by persons unknown is livingdangerously.

set

The setcommand changesthe value of internal script variables. One use forthis is to toggle optionflagswhich help determine the behavior of thescript. Another application for it is to reset the positional parametersthata script sees as the result of a command (set`command`). The script can then parse thefields of the command output.

Example 11-15. Using setwith positionalparameters

   1 #!/bin/bash
   2
   3 # script "set-test"
   4
   5 # Invoke this script with three command line parameters,
   6 # for example, "./set-test 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 $_        # unknown
  19 # Flags set in script.
  20
  21 echo "Positional parameters after  set \`uname -a\` :"
  22 # $1, $2, $3, etc. reinitialized to result of `uname -a`
  23 echo "Field #1 of 'uname -a' = $1"
  24 echo "Field #2 of 'uname -a' = $2"
  25 echo "Field #3 of 'uname -a' = $3"
  26 echo ---
  27 echo $_        # ---
  28 echo
  29
  30 exit 0

Invoking setwithout any options orarguments simply lists all the environmentaland other variablesthat 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 setwith the --option explicitly assigns the contents of a variable tothe positional parameters. When no variable follows the--, it unsetsthe positional parameters.

Example 11-16. 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 remaining_params="$*"
  12
  13 echo
  14 echo "first parameter = $first_param"             # one
  15 echo "second parameter = $second_param"           # two
  16 echo "remaining parameters = $remaining_params"   # three four five
  17
  18 echo; echo
  19
  20 # Again.
  21 set -- $variable
  22 first_param=$1
  23 second_param=$2
  24 echo "first parameter = $first_param"             # one
  25 echo "second parameter = $second_param"           # two
  26
  27 # ======================================================
  28
  29 set --
  30 # Unsets positional parameters if no variable specified.
  31
  32 first_param=$1
  33 second_param=$2
  34 echo "first parameter = $first_param"             # (null value)
  35 echo "second parameter = $second_param"           # (null value)
  36
  37 exit 0

See also Example 10-2and Example 12-51.

unset

The unsetcommand deletes ashell variable, effectively setting it tonull. Note that this command doesnot affect positional parameters.

 
bash$ 


unset PATH

bash$ echo $PATHbash$

Example 11-17. "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                                      # Same effect as:  variable=
   9 echo "(unset) variable = $variable"  # $variable is null.
  10
  11 exit 0
export

The exportcommand makesavailable variables to all child processes of therunning script or shell. Unfortunately, thereis no way toexport variables backto the parent process, to the process that called orinvoked the script or shell.One importantuse of the exportcommand is in startup files, to initializeand make accessible environmentalvariablesto subsequent user processes.

Example 11-18. Using exportto pass a variable to anembedded awkscript

   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=65
  11
  12 if [ $# -ne "$ARGS" ] # Check for proper no. 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
 

It is possible to initialize and exportvariables in the same operation, as in exportvar1=xxx.

However, as Greg Keraunen points out, in certainsituations this may have a different effect thansetting 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

declare, typeset

The declareandtypesetcommands specifyand/or restrict properties of variables.

readonly

Same as declare -r,sets a variable as read-only, or, in effect, as aconstant. Attempts to change the variable fail withan error message. This is the shell analog of theClanguage consttype qualifier.

getopts

This powerful tool parses command-line arguments passedto the script. This is the Bash analog of the getoptexternal command and thegetoptlibrary function familiar toCprogrammers. It permits passingand concatenating multiple options

Example 11-19. Using getoptsto read theoptions/arguments passed to a script

   1 #!/bin/bash
   2 # 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'    - Unexpected result, takes "r" as the argument to option "q"
  15 # 'scriptname -q -r'  - Unexpected result, same as above
  16 # 'scriptname -mnop -mnop'  - Unexpected result
  17 # (OPTIND is unreliable at stating where an option came from).
  18 #
  19 #  If an option expects an argument ("flag:"), then it will grab
  20 #+ whatever is next on the command line.
  21
  22 NO_ARGS=0
  23 E_OPTERROR=65
  24
  25 if [ $# -eq "$NO_ARGS" ]  # Script invoked with no command-line args?
  26 then
  27   echo "Usage: `basename $0` options (-mnopqrs)"
  28   exit $E_OPTERROR        # Exit and explain usage, if no argument(s) given.
  29 fi
  30 # Usage: scriptname -options
  31 # Note: dash (-) necessary
  32
  33
  34 while getopts ":mnopq:rs" Option
  35 do
  36   case $Option in
  37     m     ) echo "Scenario #1: option -m-   [OPTIND=${OPTIND}]";;
  38     n | o ) echo "Scenario #2: option -$Option-   [OPTIND=${OPTIND}]";;
  39     p     ) echo "Scenario #3: option -p-   [OPTIND=${OPTIND}]";;
  40     q     ) echo "Scenario #4: option -q-\
  41  with argument \"$OPTARG\"   [OPTIND=${OPTIND}]";;
  42     #  Note that option 'q' must have an associated argument,
  43     #+ otherwise it falls through to the default.
  44     r | s ) echo "Scenario #5: option -$Option-";;
  45     *     ) echo "Unimplemented option chosen.";;   # DEFAULT
  46   esac
  47 done
  48
  49 shift $(($OPTIND - 1))
  50 #  Decrements the argument pointer so it points to next argument.
  51 #  $1 now references the first non option item supplied on the command line
  52 #+ if one exists.
  53
  54 exit 0
  55
  56 #   As Bill Gradwohl states,
  57 #  "The getopts mechanism allows one to specify:  scriptname -mnop -mnop
  58 #+  but there is no reliable way to differentiate what came from where
  59 #+  by using OPTIND."

Script Behavior

source,.(dotcommand)

This command, when invoked from the command line,executes a script. Within a script, asource file-nameloads thefile file-name. Sourcing a file(dot-command) importscode into the script, appending to the script (same effectas the #includedirective in aCprogram). The net result is thesame as if the "sourced"lines of code werephysically present in the body of the script. This is usefulin situations when multiple scripts use a common data fileor function library.

Example 11-20. "Including"a data file

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

File data-filefor Example 11-20, above. Must be present in samedirectory.

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

If the sourcedfile is itselfan executable script, then it will run, thenreturn control to the script that called it.A sourcedexecutable script may use areturnfor thispurpose.

Arguments may be (optionally) passed to thesourcedfile as positional parameters.

   1 source $filename $arg1 arg2

It is even possible for a script tosourceitself, though this does notseem to have any practical applications.

Example 11-21. 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. Theexitcommand may optionally take aninteger argument, which is returned to the shell asthe exit statusof the script. It is good practice to end all but thesimplest scripts with an exit 0,indicating a successful run.

 

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

exec

This shell builtin replaces the current process witha specified command. Normally, when the shell encountersa command, it forks offachild process to actually execute the command. Using theexecbuiltin, the shell does not fork,and the command exec'ed replaces the shell. When used ina script, therefore, it forces an exit from the script whenthe exec'ed command terminates.

Example 11-22. Effects of exec

   1 #!/bin/bash
   2
   3 exec echo "Exiting \"$0\"."   # Exit from script here.
   4
   5 # ----------------------------------
   6 # The following lines never execute.
   7
   8 echo "This echo will never echo."
   9
  10 exit 99                       #  This script will not exit here.
  11                               #  Check exit value after script terminates
  12                               #+ with an 'echo $?'.
  13                               #  It will *not* be 99.

Example 11-23. A script that exec'sitself

   1 #!/bin/bash
   2 # self-exec.sh
   3
   4 echo
   5
   6 echo "This line appears ONCE in the script, yet it keeps echoing."
   7 echo "The PID of this instance of the script is still $$."
   8 #     Demonstrates that a subshell is not forked off.
   9
  10 echo "==================== Hit Ctl-C to exit ===================="
  11
  12 sleep 1
  13
  14 exec $0   #  Spawns another instance of this same script
  15           #+ that replaces the previous one.
  16
  17 echo "This line will never echo!"  # Why not?
  18
  19 exit 0

An execalso serves to reassignfile descriptors. For example, exec<zzz-filereplaces stdinwith the file zzz-file.

 

The -execoption tofindisnotthe same as theexecshell builtin.

shopt

This command permits changing shell options on the fly (seeExample 24-1and Example 24-2). It oftenappears in the Bash startupfiles, but also has its uses in scripts. Needsversion 2or later of Bash.

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

Putting a callercommandinside a functionechoes to stdoutinformation aboutthe callerof 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 callercommand can also returncallerinformation from a script sourcedwithin anotherscript. Like a function, this is a "subroutinecall."

You may find this command useful in debugging.

Commands

true

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

   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.

   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 whichexternal command,type cmdgives the full path name to"cmd". Unlike which,typeis a Bash builtin. The useful-aoption to typeidentifies keywordsand builtins, and also locatessystem commands with identical names.

 
bash$ 


type '['

 
[ is a shell builtin
 
bash$ 


type -a '['

 
[ is a shell builtin
 [ is /usr/bin/[
 	      
hash [cmds]

Record the path name of specified commands -- in theshell hash table

wait

Stop script execution until all jobs running inbackground have terminated, or until the job number orprocess ID specified as an option terminates. Returns the exit statusof waited-forcommand.

You may use the waitcommandto prevent a script from exiting before a backgroundjob finishes executing (this would create a dreadedorphan process).

Example 11-24. 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, waitcan take a jobidentifier as an argument, for example,wait%1or wait$PPID. See the jobid table.

 

Within a script, running a command in the backgroundwith an ampersand (&) may cause the scriptto hang until ENTERis hit. Thisseems to occur with commands that write tostdout. 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
 _
                

Placing a waitafter the backgroundcommand 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
                

Redirectingtheoutput of the command to a file or even to/dev/nullalso takes care of thisproblem.

suspend

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

logout

Exit a login shell, optionally specifying an exit status.

times

Gives statistics on the system time used in executing commands, in thefollowing form:

 
0m0.020s 0m0.020s

This capability is of very limited value, since it is uncommon toprofile and benchmark shell scripts.

kill

Forcibly terminate a process by sending it anappropriate terminatesignal (see Example 13-6).

Example 11-25. 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
  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
 

kill -llists all thesignals. A kill-9is a "sure kill", which willusually terminate a process that stubbornly refuses todie with a plain kill. Sometimes, akill -15works. A "zombieprocess,"that is, a child process that hasterminated, but that the parentprocess has not (yet) killed, cannot be killed by alogged-on user -- you can't kill something that is alreadydead -- but initwill generally cleanit up sooner or later.

command

The command COMMANDdirectivedisables aliases and functions for the command"COMMAND".

 

This is one of three shell directives thateffect script command processing. The others arebuiltinand enable.

builtin

Invoking builtinBUILTIN_COMMANDruns the command"BUILTIN_COMMAND"as a shell builtin, temporarily disablingboth functions and external system commands with thesame name.

enable

This either enables or disables a shellbuiltin command. As an example, enable -nkilldisables the shell builtin kill, so that when Bashsubsequently encounters kill, it invokes/bin/kill.

The -aoption to enablelists all theshell builtins, indicating whether or not theyare enabled. The -f filenameoption lets enableload a builtinas a shared library(DLL) module from a properly compiled object file.

Table 11-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 inforeground or started in background)
%+"current"job (last job stopped inforeground or started in background)
%-Last job
$!Last background process

Notes

Hashingis a method ofcreating lookup keys for data stored in a table. Thedata items themselvesare"scrambled"to create keys, using one ofa number of simple mathematical algorithms.

An advantage of hashingis that itis fast. A disadvantage is that "collisions"--where a single key maps to more than one data item -- arepossible.

For examples of hashing see Example A-21andExample A-22.

[5]

The readlinelibraryis what Bash uses for reading input in an interactiveshell.

[6]

The C source for a number of loadable builtins istypically found in the /usr/share/doc/bash-?.??/functionsdirectory.

Note that the -foption toenableis not portable to allsystems.

[7]

The same effect asautoloadcan be achieved with typeset -fu.