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 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 by itself a command, but part of a larger command structure. [1]
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.
An echo can be used to feed a sequence of commands down a pipe.
|
An echo, in combination with command substitution can set a variable. a=`echo "HELLO" | tr A-Z a-z` See also Example 12-18, Example 12-3, Example 12-39, and Example 12-40. |
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 362 Nov 7 2000 seconds.au |
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? 37 string1=$"Yet another line of text containing 38 a linefeed (maybe)." 39 40 echo $string1 41 42 # Thanks, Steve Parker, for pointing this out. |
This command is a shell builtin, and not the same as /bin/echo, although its behavior is similar.
|
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.
Older versions of Bash may not support printf. |
Example 11-1. printf in 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, but this is not portable. 38 39 exit 0 |
Formatting error messages is a useful application of printf
1 E_BADDIR=65 2 3 var=nonexistent_directory 4 5 error() 6 { 7 printf "$@" >&2 8 # Formats positional params passed, and sents 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. |
"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 26-6).
Example 11-2. Variable assignment, using read
1 #!/bin/bash 2 3 echo -n "Enter the value of variable 'var1': " 4 # The -n option to echo suppresses newline. 5 6 read var1 7 # Note no '$' in front of var1, since it is being set. 8 9 echo "var1 = $var1" 10 11 12 echo 13 14 # A single 'read' statement can set multiple variables. 15 echo -n "Enter the values of variables 'var2' and 'var3' (separated by a space or tab): " 16 read var2 var3 17 echo "var2 = $var2 var3 = $var3" 18 # If you input only one value, the other variable(s) will remain unset (null). 19 20 exit 0 |
A read without an associated variable assigns its input to the dedicated variable $REPLY.
Example 11-3. 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 26 exit 0 |
Normally, inputting a \ suppresses a newline during input to a read. The -r option causes an inputted \ to be interpreted literally.
Example 11-4. 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 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 11-5. 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 -n option to read will not detect the ENTER (newline) key. |
The -t option to read permits timed input (see Example 9-4).
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 11-6. 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 originial $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 |
Piping output to a read, using echo to set variables will fail. Yet, piping the output of cat seems to work.
However, as Bjön Eriksson shows: Example 11-7. Problems reading from a pipe
The gendiff script, usually found in /usr/bin on many Linux distros, pipes the output of find to a while read construct.
|
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 -) |
The -P (physical) option to cd causes it to ignore symbolic links.
cd - changes to $OLDPWD, the previous working directory.
The cd command does not function as expected when presented with two forward slashes.
|
Print Working Directory. This gives the user's (or script's) current directory (see Example 11-8). The effect is identical to reading the value of the builtin variable $PWD.
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 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 that directory popped from 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 11-8. 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 |
The let command carries out arithmetic operations on variables. In many cases, it functions as a less complex version of expr.
Example 11-9. 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 arg1 [arg2] ... [argN]
Combines the arguments in an expression or list of expressions and evaluates them. Any variables contained within the expression are expanded. The result translates into a command. This can be useful for code 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-10. 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-11. 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 # Must be 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. |
Example 11-12. 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 how useful eval can be.
Example 11-13. Using eval to force variable substitution 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 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. |
The set command changes the value of internal script variables. 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 11-14. Using set with positional parameters
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 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. When no variable follows the --, it unsets the positional parameters.
Example 11-15. 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-2 and Example 12-47.
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$ |
The export command makes available variables to all child processes of the running script or shell. Unfortunately, there is no way to export variables back to the parent process, to the process that called or invoked the script or shell. One important use of export command is in startup files, to initialize and make accessible environmental variables to subsequent user processes.
Example 11-17. 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 7 ARGS=2 8 E_WRONGARGS=65 9 10 if [ $# -ne "$ARGS" ] # Check for proper no. of command line args. 11 then 12 echo "Usage: `basename $0` filename column-number" 13 exit $E_WRONGARGS 14 fi 15 16 filename=$1 17 column_number=$2 18 19 #===== Same as original script, up to this point =====# 20 21 export column_number 22 # Export column number to environment, so it's available for retrieval. 23 24 25 # Begin awk script. 26 # ------------------------------------------------ 27 awk '{ total += $ENVIRON["column_number"] 28 } 29 END { print total }' $filename 30 # ------------------------------------------------ 31 # End awk script. 32 33 34 # Thanks, Stephane Chazelas. 35 36 exit 0 |
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.
|
The declare and typeset commands specify and/or restrict properties of variables.
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.
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 [2] 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 decrements the implicit $OPTIND variable to step to the next.
|
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>. 20 |
Example 11-18. Using getopts to read the options/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." |
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 in the body of the script. This is useful in situations when multiple scripts use a common data file or function library.
Example 11-19. "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-file for Example 11-19, 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 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 } |
It is even possible for a script to source itself, though this does not seem to have any practical applications.
Example 11-20. 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 set $pass_count to 0 if non-initialized. 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. 22 done 23 24 # What occurs here is not actually recursion, 25 #+ since the script effectively "expands" itself 26 #+ (generates a new section of code) 27 #+ with each pass throught 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 do something useful. |
Unconditionally terminates a script. 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.
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 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. For this reason, if an exec appears in a script, it would probably be the final command.
Example 11-21. 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-22. A script that exec's itself
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 exec also serves to reassign file descriptors. exec <zzz-file replaces stdin with the file zzz-file (see Example 16-1).
The -exec option to find is not the same as the exec shell builtin. |
This command permits changing shell options on the fly (see Example 24-1 and Example 24-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 4 cd /hpme # Oops! Mistyped '/home'. 5 pwd # /home 6 # The shell corrected the misspelling. |
A command that returns a successful (zero) exit status, but does nothing 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 |
A command that returns an unsuccessful exit status, but does nothing else.
1 # Null loop 2 while false 3 do 4 # The following code will not execute. 5 operation-1 6 operation-2 7 ... 8 operation-n 9 # Nothing happens! 10 done |
Similar to the which external command, type cmd gives the full pathname to "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/[ |
Record the path name of specified commands (in the shell hash table), 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.
The bind builtin displays or modifies readline [3] key bindings.
Gets a short usage summary of a shell builtin. This is the counterpart to whatis, but for builtins.
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. |
Certain of the following job control commands take a "job identifier" as an argument. See the table at end of the chapter.
Lists the jobs running in the background, giving the job number. Not as useful as ps.
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.
"1" is the job number (jobs are maintained by the current shell), and "1384" is the process number (processes are maintained by the system). To kill this job/process, either a kill %1 or a kill 1384 works. Thanks, S.C. |
Remove job(s) from the shell's table of active jobs.
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.
Stop 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 11-23. 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. See the job id table.
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.
Placing a wait after the background command seems to remedy this.
|
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).
Exit a login shell, optionally specifying an exit status.
Gives statistics on the system time used in executing commands, in the following form:
0m0.020s 0m0.020s |
Forcibly terminate a process by sending it an appropriate terminate signal (see Example 13-5).
Example 11-24. 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 -l lists all the signals. 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 process whose parent has terminated, cannot be killed (you can't kill something that is already dead), but init will generally clean it up sooner or later. |
The command COMMAND directive disables aliases and functions for the command "COMMAND".
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.
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 /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. [4].
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. [5] 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 11-1. Job identifiers
Notation | Meaning |
---|---|
%N | Job number [N] |
%S | Invocation (command line) of job begins with string S |
%?S | Invocation (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 |
[1] | An exception to this is the time command, listed in the official Bash documentation as a keyword. |
[2] | A 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. |
[3] | The readline library is what Bash uses for reading input in an interactive shell. |
[4] | 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. |
[5] | The same effect as autoload can be achieved with typeset -fu. |