| Advanced Bash-Scripting Guide: A complete guide to shell scripting, using Bash | ||
|---|---|---|
| Prev | Chapter 3. Tutorial / Reference | Next |
3.9. Internal Commands and Builtins
A builtin is a command contained within the Bash tool set, literally built in. A builtin may be a synonym to a system command of the same name, but Bash reimplements it internally. [1] For example, the Bash echo command is not the same as /bin/echo, although their behavior is almost identical.
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" and "!" are keywords. Similar to a builtin, a keyword is hard-coded into Bash.
- echo
prints (to stdout) an expression or variable (see Example 3-6).
1 echo Hello 2 echo $a
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.
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 with command substitution can set a variable.
a=`echo "HELLO" | tr A-Z a-z`
See also Example 3-96, Example 3-85, Example 3-107, and Example 3-108.

Be aware that echo `command` deletes any linefeeds that the output of command generates. Since $IFS normally contains \n as one of its set of whitespace characters, Bash segments the output of command at linefeeds into arguments to echo, which then emits these arguments separated by spaces.
bash$ printf '\n\n1\n2\n3\n\n\n\n' 1 2 3 bash $ bash$ echo "`printf '\n\n1\n2\n3\n\n\n\n'`" 1 2 3 bash $

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
- cd
The familiar cd change directory command finds use in scripts where execution of a command requires being in a specified directory.
[from the previously cited example by Alan Cox]1 (cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -)
- printf
The printf, formatted print, command is an enhanced echo. It is a limited variant of the C language printf, and the 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 man page (of the system command) for in-depth coverage.

Older versions of bash may not support printf.
Example 3-69. printf in action
1 #!/bin/bash 2 3 # printf demo 4 5 PI=3.14159265358979 6 DecimalConstant=31373 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 15 # Note correct round off. 16 17 printf "\n" 18 # Prints a line feed, equivalent to 'echo'. 19 20 printf "Constant = \t%d\n" $DecimalConstant 21 # Insert tab (\t) 22 23 printf "%s %s \n" $Message1 $Message2 24 25 echo 26 27 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.- getopts
This powerful tool parses command line arguments passed to the script. This is the bash analog of 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.

The arguments must be passed from the command line to the script preceded by a minus (-) or a plus (+). It is the prefixed - or + that lets getopts recognize command-line arguments as options. In fact, getopts will not process arguments without the prefixed - or +, and will terminate option processing at the first argument encountered lacking them.
The getopts template differs slightly from the standard while loop, in that it lacks condition brackets.
The getopts construct replaces the obsolete getopt 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>. 20
Example 3-70. Using getopts to read the options/arguments passed to a script
1 #!/bin/bash 2 3 # 'getopts' processes command line arguments to script. 4 # The arguments are parsed as "options" (flags) and associated arguments. 5 6 # Usage: scriptname -options 7 # Note: dash (-) necessary 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 # If an option expects an argument ("flag:"), then it will grab 17 # whatever is next on the command line. 18 19 NO_ARGS=0 20 OPTERROR=65 21 22 if [ $# -eq "$NO_ARGS" ] # Script invoked with no command-line args? 23 then 24 echo "Usage: `basename $0` options (-mnopqrs)" 25 exit $OPTERROR # Exit and explain usage, if no argument(s) given. 26 fi 27 28 while getopts ":mnopq:rs" Option 29 do 30 case $Option in 31 m ) echo "Scenario #1: option -m-";; 32 n | o ) echo "Scenario #2: option -$Option-";; 33 p ) echo "Scenario #3: option -p-";; 34 q ) echo "Scenario #4: option -q-, with argument \"$OPTARG\"";; 35 # Note that option 'q' must have an associated argument, 36 # otherwise it falls through to the default. 37 r | s ) echo "Scenario #5: option -$Option-"'';; 38 * ) echo "Unimplemented option chosen.";; # DEFAULT 39 esac 40 done 41 42 shift $(($OPTIND - 1)) 43 # Decrements the argument pointer 44 # so it points to next argument. 45 46 exit 0- exit
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 a 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.
- eval
eval arg1, arg2, ...
Translates into commands the arguments in a list (useful for code generation within a script).
Example 3-71. 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. 5 6 y=`eval df` # Similar to y=`df` 7 echo $y # but linefeeds removed. 8 9 # Note that LF's not preserved, 10 # and this may make it easier to parse output. 11 12 exit 0
Example 3-72. Forcing a log-off
1 #!/bin/bash 2 3 y=`eval ps ax | sed -n '/ppp/p' | awk '{ print $1 }'` 4 # Finding the process number of 'ppp' 5 6 kill -9 $y 7 # Killing it 8 9 # Above lines may be replaced by 10 # kill -9 `ps ax | awk '/ppp/ { print $1 }' 11 12 13 # Restore to previous state... 14 15 chmod 666 /dev/ttyS3 16 # Doing a SIGKILL on ppp changes the permissions 17 # on the serial port. Must be restored. 18 19 rm /var/lock/LCK..ttyS3 20 # Remove the serial port lock file. 21 22 exit 0Example 3-73. A version of "rot13"
1 #!/bin/bash 2 # A version of "rot13" using 'eval' (see also "rot13.sh" example) 3 4 setvar_rot_13() # "rot13" scrambling 5 { 6 local varname=$1 varvalue=$2 7 eval $varname='$(echo "$varvalue" | tr a-z n-za-m)' 8 } 9 10 11 setvar_rot_13 var "foobar" # Run "foobar" through rot13. 12 echo $var # sbbone 13 14 echo $var | tr a-z n-za-m # foobar 15 # Back to original variable. 16 17 # This example by Stephane Chazelas. 18 19 exit 0
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.
- exec
This shell builtin replaces the current process with a specified command. Normally, when the shell encounters a command, it forks off [3] 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.
An exec also serves to reassign file descriptors. exec <zzz-file replaces stdin with the file zzz-file (see Example 3-118).
Example 3-74. Effects of exec
1 #!/bin/bash 2 3 exec echo "Exiting \"$0\"." 4 # Exit from script. 5 6 # The following lines never execute. 7 echo "This will never echo." 8 9 exit 0

The -exec option to find is not the same as the exec shell builtin.
- set
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 3-75. 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 echo 15 16 set `uname -a` 17 # Sets the positional parameters to the output 18 # of the command `uname -a` 19 20 echo "Positional parameters after set \`uname -a\` :" 21 # $1, $2, $3, etc. reinitialized to result of `uname -a` 22 echo "Field #1 of 'uname -a' = $1" 23 echo "Field #2 of 'uname -a' = $2" 24 echo "Field #3 of 'uname -a' = $3" 25 echo 26 27 exit 0
See also Example 3-44.
- unset
The unset command deletes a shell variable. Note that this command does not affect positional parameters.
bash$ unset PATH bash$ echo $PATH bash$
- shopt
This command permits changing shell options on the fly (see Example 3-144 and Example 3-145). 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 directory names with 'cd' 3 command.
- export
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 3-77. 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 WRONGARGS=65 9 10 if [ $# -ne "$ARGS" ] 11 # Check for proper no. of command line args. 12 then 13 echo "Usage: `basename $0` filename column-number" 14 exit $WRONGARGS 15 fi 16 17 filename=$1 18 column_number=$2 19 20 #===== Same as original script, up to this point =====# 21 22 export column_number 23 # Export column number to environment, so it's available for retrieval. 24 25 26 # Begin awk script. 27 # ------------------------------------------------ 28 awk '{ total += $ENVIRON["column_number"] 29 } 30 END { print total }' $filename 31 # ------------------------------------------------ 32 # End awk script. 33 34 35 # Thanks, Stephane Chazelas. 36 37 exit 0
It is possible to initialize and export variables in the same operation, as in export var1=xxx.
- 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.
- 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 3-150).
Example 3-78. Variable assignment, using read
1 #!/bin/bash 2 3 echo -n "Enter the value of variable 'var1': " 4 # -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 # Note that a single 'read' statement can set multiple variables. 13 14 echo 15 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
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 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 3-79. Using read with file redirection
1 #!/bin/bash 2 3 4 read var1 <data-file 5 echo "var1 = $var1" 6 # var1 set to the entire first line of the input file "data-file" 7 8 read var2 var3 <data-file 9 echo "var2 = $var2 var3 = $var3" 10 # Note non-intuitive behavior of "read" here. 11 # 1) Rewinds back to the beginning of input file. 12 # 2) Each variable is now set to a corresponding string, separated by whitespace, 13 # rather than to an entire line of text. 14 # 3) The final variable gets the remainder of the line. 15 # 4) If there are more variables to be set than whitespace-terminated strings 16 # on the first line of the file, then the excess variables remain empty. 17 18 echo "------------------------------------------------" 19 20 # How to resolve the above problem with a loop: 21 while read line 22 do 23 echo "$line" 24 done <data-file 25 # Thanks, Heiner Steven for pointing this out. 26 27 echo "------------------------------------------------" 28 29 # Use $IFS (Internal File Separator variable) to split a line of input to 30 # "read", if you do not want the default to be whitespace. 31 32 echo "List of all users:" 33 OIFS=$IFS; IFS=: # /etc/passwd uses ":" for field separator. 34 while read name passwd uid gid fullname ignore 35 do 36 echo "$name ($fullname)" 37 done </etc/passwd # I/O redirection. 38 IFS=$OIFS # Restore originial $IFS. 39 # This code snippet also by Heiner Steven. 40 41 exit 0
- let
The let command carries out arithmetic operations on variables. In many cases, it functions as a less complex version of expr.
Example 3-80. Letting let do some arithmetic.
1 #!/bin/bash 2 3 echo 4 5 let a=11 6 # Same as 'a=11' 7 let a=a+5 8 # Equivalent to let "a = a + 5" 9 # (double quotes makes it more readable) 10 echo "a = $a" 11 let "a <<= 3" 12 # Equivalent of let "a = a << 3" 13 echo "a left-shifted 3 places = $a" 14 15 let "a /= 4" 16 # Equivalent to let "a = a / 4" 17 echo $a 18 let "a -= 5" 19 # Equivalent to let "a = a - 5" 20 echo $a 21 let "a = a * 10" 22 echo $a 23 let "a %= 8" 24 echo $a 25 26 exit 0
- true
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. 9 done
- false
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
- hash [cmds]
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.
- type [cmd]
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 accesses 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/[
- pwd
Print Working Directory. This gives the user's (or script's) current directory (see Example 3-81). 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 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 (counterpart to $DIRSTACK) 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 3-81. 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' 8 # (list directory stack to stdout). 9 echo "Now in directory `pwd`." 10 # Uses back-quoted 'pwd'. 11 # Now, do some stuff in directory 'dir1'. 12 pushd $dir2 13 echo "Now in directory `pwd`." 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 # Now, do some more stuff in directory 'dir1'. 19 popd 20 echo "Now back in original working directory `pwd`." 21 22 exit 0
- 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. This is the shell scripting equivalent of a C/C++ #include directive. It is useful in situations when multiple scripts use a common data file or function library.
Example 3-82. "Including" a data file
1 #!/bin/bash 2 3 # Load a data file. 4 . data-file 5 # Same effect as "source data-file", but more portable. 6 7 # Note that the file "data-file", given below 8 # must be present in working directory. 9 10 # Now, reference some data from that file. 11 12 echo "variable1 (from data-file) = $variable1" 13 echo "variable3 (from data-file) = $variable3" 14 15 let "sum = $variable2 + $variable4" 16 echo "Sum of variable2 + variable4 (from data-file) = $sum" 17 echo "message1 (from data-file) is \"$message1\"" 18 # Note: escaped quotes 19 20 print_message This is the message-print function in the data-file. 21 22 23 exit 0
File data-file for Example 3-82, 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 }- help
help COMMAND looks up a short usage summary of the shell builtin COMMAND. 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.
3.9.1. Job Control Commands
- ps
Process Statistics: lists currently executing processes by owner and PID (process id). This is usually invoked with ax options, and may be piped to grep or sed to search for a specific process (see Example 3-72 and Example 3-156).
bash$ ps ax | grep sendmail 295 ? S 0:00 sendmail: accepting connections on port 25
- pstree
Lists currently executing processes in "tree" format. The -p option shows the PIDs, as well as the process names.
- wait
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 3-83. Waiting for a process to finish before proceeding
1 #!/bin/bash 2 3 if [ -z "$1" ] 4 then 5 echo "Usage: `basename $0` find-string" 6 exit 65 7 fi 8 9 echo "Updating 'locate' database..." 10 echo "This may take a while." 11 updatedb /usr & 12 # Must be run as root. 13 14 wait 15 # Don't run the rest of the script until 'updatedb' finished. 16 # You want the the database updated before looking up the file name. 17 18 locate $1 19 20 # Lacking the wait command, in the worse case scenario, 21 # the script would exit while 'updatedb' was still running, 22 # leaving it as an orphan process. 23 24 exit 0
- 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).
- disown
Remove job(s) from the shell's table of active jobs.
- jobs
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.
bash$ sleep 100 & [1] 1384 bash $ jobs [1]+ Running sleep 100 &
"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.
- times
Gives statistics on the system time used in executing commands, in the following form:
This capability is of very limited value, since it is uncommon to profile and benchmark shell scripts.0m0.020s 0m0.020s
- kill
Forcibly terminate a process by sending it an appropriate terminate signal (see Example 3-113).

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 usually clean it up sooner or later.
- command
The command COMMAND directive disables aliases and functions for the command "COMMAND".
- 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 /bin/kill. The -a option lists all the shell builtins, indicating whether or not they are enabled.
Notes
| [1] | This is either for performance reasons (builtins execute much faster than external commands, which usually require forking off a process) or because a particular builtin needs direct access to the shell internals. |
| [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] | When a command or the shell itself initiates (or spawns) a new subprocess to carry out a task, this is called forking. This new process is the "child", and the process that forked it off is the "parent". While the child process is doing its work, the parent process is still running. |
