Adv Bash Scripting Guide
Adv Bash Scripting Guide
3.9
15 May 2006
Revision History
Revision 3.7 23 Oct 2005 Revised by: mc
'WHORTLEBERRY' release: Bugfix Update.
Revision 3.8 26 Feb 2006 Revised by: mc
'BLAEBERRY' release: Minor Update.
Revision 3.9 15 May 2006 Revised by: mc
'SPICEBERRY' release: Minor Update.
This tutorial assumes no previous knowledge of scripting or programming, but progresses rapidly toward an
intermediate/advanced level of instruction . . . all the while sneaking in little snippets of UNIX® wisdom and
lore. It serves as a textbook, a manual for self−study, and a reference and source of knowledge on shell
scripting techniques. The exercises and heavily−commented examples invite active reader participation, under
the premise that the only way to really learn scripting is to write scripts.
This book is suitable for classroom use as a general introduction to programming concepts.
The latest update of this document, as an archived, bzip2−ed "tarball" including both the SGML source and
rendered HTML, may be downloaded from the author's home site. A pdf version is also available. See the
change log for a revision history.
Dedication
For Anita, the source of all the magic
Advanced Bash−Scripting Guide
Table of Contents
Chapter 1. Why Shell Programming?...............................................................................................................1
Part 2. Basics.......................................................................................................................................................7
Chapter 5. Quoting...........................................................................................................................................34
5.1. Quoting Variables...........................................................................................................................34
5.2. Escaping..........................................................................................................................................35
Chapter 7. Tests................................................................................................................................................43
7.1. Test Constructs...............................................................................................................................43
7.2. File test operators............................................................................................................................49
7.3. Other Comparison Operators..........................................................................................................52
7.4. Nested if/then Condition Tests.......................................................................................................57
7.5. Testing Your Knowledge of Tests..................................................................................................57
i
Advanced Bash−Scripting Guide
Table of Contents
Chapter 10. Loops and Branches..................................................................................................................116
10.1. Loops..........................................................................................................................................116
10.2. Nested Loops..............................................................................................................................127
10.3. Loop Control...............................................................................................................................127
10.4. Testing and Branching................................................................................................................131
ii
Advanced Bash−Scripting Guide
Table of Contents
Chapter 22. Process Substitution...................................................................................................................326
iii
Advanced Bash−Scripting Guide
Table of Contents
Chapter 35. Endnotes.....................................................................................................................................450
35.1. Author's Note..............................................................................................................................450
35.2. About the Author........................................................................................................................450
35.3. Where to Go For Help.................................................................................................................450
35.4. Tools Used to Produce This Book..............................................................................................451
35.4.1. Hardware...........................................................................................................................451
35.4.2. Software and Printware.....................................................................................................451
35.5. Credits.........................................................................................................................................451
Bibliography....................................................................................................................................................454
Appendix I. Localization................................................................................................................................608
Appendix M. Exercises...................................................................................................................................628
M.1. Analyzing Scripts........................................................................................................................628
M.2. Writing Scripts............................................................................................................................629
iv
Advanced Bash−Scripting Guide
Table of Contents
Appendix P. To Do List..................................................................................................................................640
Appendix Q. Copyright..................................................................................................................................642
Notes....................................................................................................................................................643
v
Chapter 1. Why Shell Programming?
No programming language is perfect. There is not
even a single best language; there are only languages
well suited or perhaps poorly suited for particular
purposes.
Herbert Mayer
A working knowledge of shell scripting is essential to anyone wishing to become reasonably proficient at
system administration, even if they do not anticipate ever having to actually write a script. Consider that as a
Linux machine boots up, it executes the shell scripts in /etc/rc.d to restore the system configuration and
set up services. A detailed understanding of these startup scripts is important for analyzing the behavior of a
system, and possibly modifying it.
Writing shell scripts is not hard to learn, since the scripts can be built in bite−sized sections and there is only a
fairly small set of shell−specific operators and options [1] to learn. The syntax is simple and straightforward,
similar to that of invoking and chaining together utilities at the command line, and there are only a few "rules"
to learn. Most short scripts work right the first time, and debugging even the longer ones is straightforward.
A shell script is a "quick and dirty" method of prototyping a complex application. Getting even a limited
subset of the functionality to work in a shell script is often a useful first stage in project development. This
way, the structure of the application can be tested and played with, and the major pitfalls found before
proceeding to the final coding in C, C++, Java, or Perl.
Shell scripting hearkens back to the classic UNIX philosophy of breaking complex projects into simpler
subtasks, of chaining together components and utilities. Many consider this a better, or at least more
esthetically pleasing approach to problem solving than using one of the new generation of high powered
all−in−one languages, such as Perl, which attempt to be all things to all people, but at the cost of forcing you
to alter your thinking processes to fit the tool.
If any of the above applies, consider a more powerful scripting language −− perhaps Perl, Tcl, Python, Ruby
−− or possibly a high−level compiled language such as C, C++, or Java. Even then, prototyping the
application as a shell script might still be a useful development step.
We will be using Bash, an acronym for "Bourne−Again shell" and a pun on Stephen Bourne's now classic
Bourne shell. Bash has become a de facto standard for shell scripting on all flavors of UNIX. Most of the
principles this book covers apply equally well to scripting with other shells, such as the Korn Shell, from
which Bash derives some of its features, [2] and the C Shell and its variants. (Note that C Shell programming
is not recommended due to certain inherent problems, as pointed out in an October, 1993 Usenet post by Tom
Christiansen.)
What follows is a tutorial on shell scripting. It relies heavily on examples to illustrate various features of the
shell. The example scripts work −− they've been tested, insofar as was possible −− and some of them are even
useful in real life. The reader can play with the actual working code of the examples in the source archive
(scriptname.sh or scriptname.bash), [3] give them execute permission (chmod u+rx
scriptname), then run them to see what happens. Should the source archive not be available, then
cut−and−paste from the HTML, pdf, or text rendered versions. Be aware that some of the scripts presented
here introduce features before they are explained, and this may require the reader to temporarily skip ahead
for enlightenment.
Unless otherwise noted, the author of this book wrote the example scripts that follow.
# Cleanup
# Run as root, of course.
cd /var/log
cat /dev/null > messages
cat /dev/null > wtmp
echo "Logs cleaned up."
There is nothing unusual here, only a set of commands that could just as easily be invoked one by one from
the command line on the console or in an xterm. The advantages of placing the commands in a script go
beyond not having to retype them time and again. The script becomes a tool, and can easily be modified or
customized for a particular application.
#!/bin/bash
# Proper header for a Bash script.
# Cleanup, version 2
LOG_DIR=/var/log
# Variables are better than hard−coded values.
cd $LOG_DIR
#!/bin/bash
# Cleanup, version 3
# Warning:
# −−−−−−−
# This script uses quite a number of features that will be explained
LOG_DIR=/var/log
ROOT_UID=0 # Only users with $UID 0 have root privileges.
LINES=50 # Default number of lines saved.
E_XCD=66 # Can't change directory?
E_NOTROOT=67 # Non−root exit error.
if [ −n "$1" ]
# Test if command line argument present (non−empty).
then
lines=$1
else
lines=$LINES # Default, if not specified on command line.
fi
cd $LOG_DIR
tail −$lines messages > mesg.temp # Saves last section of message log file.
mv mesg.temp messages # Becomes new log directory.
cat /dev/null > wtmp # ': > wtmp' and '> wtmp' have the same effect.
echo "Logs cleaned up."
exit 0
# A zero return value from the script upon exit
#+ indicates success to the shell.
Since you may not wish to wipe out the entire system log, this version of the script keeps the last section of
the message log intact. You will constantly discover ways of refining previously written scripts for increased
effectiveness.
The sha−bang ( #!) at the head of a script tells your system that this file is a set of commands to be fed to the
command interpreter indicated. The #! is actually a two−byte [4] magic number, a special marker that
designates a file type, or in this case an executable shell script (type man magic for more details on this
fascinating topic). Immediately following the sha−bang is a path name. This is the path to the program that
interprets the commands in the script, whether it be a shell, a programming language, or a utility. This
command interpreter then executes the commands in the script, starting at the top (line following the
sha−bang line), ignoring comments. [5]
#!/bin/sh
#!/bin/bash
#!/usr/bin/perl
#!/usr/bin/tcl
#!/bin/sed −f
#!/usr/awk −f
Each of the above script header lines calls a different command interpreter, be it /bin/sh, the default shell
(bash in a Linux system) or otherwise. [6] Using #!/bin/sh, the default Bourne shell in most commercial
variants of UNIX, makes the script portable to non−Linux machines, though you sacrifice Bash−specific
features. The script will, however, conform to the POSIX [7] sh standard.
Note that the path given at the "sha−bang" must be correct, otherwise an error message −− usually "Command
not found" −− will be the only result of running the script.
#! can be omitted if the script consists only of a set of generic system commands, using no internal shell
directives. The second example, above, requires the initial #!, since the variable assignment line, lines=50,
uses a shell−specific construct. [8] Note again that #!/bin/sh invokes the default shell interpreter, which
defaults to /bin/bash on a Linux machine.
This tutorial encourages a modular approach to constructing a script. Make note of and collect
"boilerplate" code snippets that might be useful in future scripts. Eventually you can build quite an
extensive library of nifty routines. As an example, the following script prolog tests whether the script has
been invoked with the correct number of parameters.
E_WRONG_ARGS=65
script_parameters="−a −h −m −z"
# −a = all, −h = help, etc.
if [ $# −ne $Number_of_expected_args ]
then
echo "Usage: `basename $0` $script_parameters"
# `basename $0` is the script's filename.
exit $E_WRONG_ARGS
fi
Many times, you will write a script that carries out one particular task. The first script in this chapter is
an example of this. Later, it might occur to you to generalize the script to do other, similar tasks.
Replacing the literal ("hard−wired") constants by variables is a step in that direction, as is replacing
repetitive code blocks by functions.
Either:
chmod 555 scriptname (gives everyone read/execute permission) [10]
or
chmod +rx scriptname (gives everyone read/execute permission)
chmod u+rx scriptname (gives only the script owner read/execute permission)
Having made the script executable, you may now test it by ./scriptname. [11] If it begins with a
"sha−bang" line, invoking the script calls the correct command interpreter to run it.
As a final step, after testing and debugging, you would likely want to move it to /usr/local/bin (as root,
of course), to make the script available to yourself and all other users as a system−wide executable. The script
could then be invoked by simply typing scriptname [ENTER] from the command line.
Part 2. Basics 7
Chapter 3. Special Characters
Special Characters Found In Scripts and Elsewhere
#
Comments. Lines beginning with a # (with the exception of #!) are comments.
A command may not follow a comment on the same line. There is no method of
terminating the comment, in order for "live code" to begin on the same line. Use a new
line for the next command.
# Thanks, S.C.
The standard quoting and escape characters (" ' \) escape the #.
Certain pattern matching operations also use the #.
;
Command separator [semicolon]. Permits putting two or more commands on the same line.
case "$variable" in
abc) echo "\$variable = abc" ;;
"dot" command [period]. Equivalent to source (see Example 11−21). This is a bash builtin.
.
"dot", as a component of a filename. When working with filenames, a dot is the prefix of a
"hidden" file, a file that an ls will not normally show.
bash$ ls −al
total 14
drwxrwxr−x 2 bozo bozo 1024 Aug 29 20:54 ./
drwx−−−−−− 52 bozo bozo 3072 Aug 29 20:51 ../
−rw−r−−r−− 1 bozo bozo 4034 Jul 18 22:04 data1.addressbook
−rw−r−−r−− 1 bozo bozo 4602 May 25 13:58 data1.addressbook.bak
−rw−r−−r−− 1 bozo bozo 877 Dec 17 2000 employment.addressbook
−rw−rw−r−− 1 bozo bozo 0 Aug 29 20:54 .hidden−file
When considering directory names, a single dot represents the current working directory, and two dots
denote the parent directory.
bash$ pwd
/home/bozo/projects
bash$ cd .
bash$ pwd
/home/bozo/projects
bash$ cd ..
bash$ pwd
/home/bozo/
The dot often appears as the destination (directory) of a file movement command.
bash$ cp /home/bozo/current_work/junk/* .
.
"dot" character match. When matching characters, as part of a regular expression, a "dot" matches a
single character.
"
partial quoting [double quote]. "STRING" preserves (from interpretation) most of the special
characters within STRING. See also Chapter 5.
'
full quoting [single quote]. 'STRING' preserves all special characters within STRING. This is a
stronger form of quoting than using ". See also Chapter 5.
,
comma operator. The comma operator links together a series of arithmetic operations. All are
evaluated, but only the last one is returned.
\X "escapes" the character X. This has the effect of "quoting" X, equivalent to 'X'. The \ may be used
to quote " and ', so they are expressed literally.
null command [colon]. This is the shell equivalent of a "NOP" (no op, a do−nothing operation). It
may be considered a synonym for the shell builtin true. The ":" command is itself a Bash builtin, and
its exit status is "true" (0).
:
echo $? # 0
Endless loop:
while :
do
operation−1
operation−2
...
operation−n
done
# Same as:
# while true
# do
# ...
# done
Placeholder in if/then test:
if condition
then : # Do nothing and branch ahead
else
take−some−action
fi
Provide a placeholder where a binary operation is expected, see Example 8−2 and default parameters.
: ${username=`whoami`}
# ${username=`whoami`} Gives an error without the leading :
# unless "username" is a command or builtin...
Provide a placeholder where a command is expected in a here document. See Example 17−10.
In combination with the > redirection operator, truncates a file to zero length, without changing its
permissions. If the file did not previously exist, creates it.
In combination with the >> redirection operator, has no effect on a pre−existing target file (: >>
target_file). If the file did not previously exist, creates it.
This applies to regular files, not pipes, symlinks, and certain special files.
May be used to begin a comment line, although this is not recommended. Using # for a comment
turns off error checking for the remainder of that line, so almost anything may appear in a comment.
However, this is not the case with :.
reverse (or negate) the sense of a test or exit status [bang]. The ! operator inverts the exit status of
the command to which it is applied (see Example 6−2). It also inverts the meaning of a test operator.
This can, for example, change the sense of "equal" ( = ) to "not−equal" ( != ). The ! operator is a Bash
keyword.
In yet another context, from the command line, the ! invokes the Bash history mechanism (see
Appendix J). Note that within a script, the history mechanism is disabled.
*
wild card [asterisk]. The * character serves as a "wild card" for filename expansion in globbing. By
itself, it matches every filename in a given directory.
bash$ echo *
abs−book.sgml add−drive.sh agram.sh alias.sh
The * also represents any number (or zero) characters in a regular expression.
*
arithmetic operator. In the context of arithmetic operations, the * denotes multiplication.
test operator. Within certain expressions, the ? indicates a test for a condition.
In a double parentheses construct, the ? serves as a C−style trinary operator. See Example 9−31.
In a parameter substitution expression, the ? tests whether a variable has been set.
?
wild card. The ? character serves as a single−character "wild card" for filename expansion in
globbing, as well as representing one character in an extended regular expression.
$
Variable substitution (contents of a variable).
var1=5
var2=23skidoo
echo $var1 # 5
echo $var2 # 23skidoo
A $ prefixing a variable name indicates the value the variable holds.
$
end−of−line. In a regular expression, a "$" addresses the end of a line of text.
${}
Parameter substitution.
$*, $@
positional parameters.
$?
exit status variable. The $? variable holds the exit status of a command, a function, or of the script
itself.
$$
process ID variable. The $$ variable holds the process ID [12] of the script in which it appears.
()
command group.
Variables inside parentheses, within the subshell, are not visible to the rest
of the script. The parent process, the script, cannot read variables created in
the child process, the subshell.
a=123
( a=321; )
cp file22.{txt,backup}
# Copies "file22.txt" to "file22.backup"
A command may act upon a comma−separated list of file specs within braces. [13] Filename
expansion (globbing) applies to the file specs between the braces.
No spaces allowed within the braces unless the spaces are quoted or escaped.
Block of code [curly brackets]. Also referred to as an inline group, this construct, in effect, creates
an anonymous function (a function without a name). However, unlike in a "standard" function, the
variables inside a code block remain visible to the remainder of the script.
bash$ { local a;
a=123; }
bash: local: can only be used in a
function
a=123
{ a=321; }
echo "a = $a" # a = 321 (value inside code block)
# Thanks, S.C.
The code block enclosed in braces may have I/O redirected to and from it.
#!/bin/bash
# Reading lines in /etc/fstab.
File=/etc/fstab
{
read line1
read line2
} < $File
exit 0
#!/bin/bash
# rpm−check.sh
# Queries an rpm file for description, listing, and whether it can be installed.
# Saves output to a file.
#
# This script illustrates using a code block.
SUCCESS=0
E_NOARGS=65
if [ −z "$1" ]
then
echo "Usage: `basename $0` rpm−file"
exit $E_NOARGS
fi
{
echo
echo "Archive Description:"
rpm −qpi $1 # Query description.
echo
echo "Archive Listing:"
rpm −qpl $1 # Query listing.
echo
rpm −i −−test $1 # Query whether rpm file can be installed.
if [ "$?" −eq $SUCCESS ]
then
echo "$1 can be installed."
else
echo "$1 cannot be installed."
fi
echo
} > "$1.test" # Redirects output of everything in block to file.
exit 0
The ";" ends the −exec option of a find command sequence. It needs to be escaped to
protect it from interpretation by the shell.
[]
test.
Test expression between [ ]. Note that [ is part of the shell builtin test (and a synonym for it), not a
link to the external command /usr/bin/test.
[[ ]]
test.
In the context of an array, brackets set off the numbering of each element of that array.
Array[1]=slot_1
echo ${Array[1]}
[]
range of characters.
command &>filename redirects both the stdout and the stderr of command to filename.
[i]<>filename opens file filename for reading and writing, and assigns file descriptor i to it. If
filename does not exist, it is created.
process substitution.
(command)>
<(command)
In a different context, the "<" and ">" characters act as string comparison operators.
In yet another context, the "<" and ">" characters act as integer comparison operators. See also
Example 12−9.
<<
redirection used in a here document.
<<<
redirection used in a here string.
<, >
ASCII comparison.
veg1=carrots
veg2=tomatoes
pipe. Passes the output of previous command to the input of the next one, or to the shell. This is a
method of chaining commands together.
echo ls −l | sh
# Passes the output of "echo ls −l" to the shell,
#+ with the same result as a simple "ls −l".
A pipe, as a classic method of interprocess communication, sends the stdout of one process to the
stdin of another. In a typical case, a command, such as cat or echo, pipes a stream of data to a
"filter" (a command that transforms its input) for processing.
#!/bin/bash
# uppercase.sh : Changes input to uppercase.
tr 'a−z' 'A−Z'
# Letter ranges must be quoted
#+ to prevent filename generation from single−letter filenames.
exit 0
Now, let us pipe the output of ls −l to this script.
bash$ ls −l | ./uppercase.sh
−RW−RW−R−− 1 BOZO BOZO 109 APR 7 19:49 1.TXT
−RW−RW−R−− 1 BOZO BOZO 109 APR 14 16:48 2.TXT
−RW−R−−R−− 1 BOZO BOZO 725 APR 20 20:56 DATA−FILE
The stdout of each process in a pipe must be read as the stdin of the next. If this
is not the case, the data stream will block, and the pipe will not behave as expected.
variable="initial_value"
echo "new_value" | read variable
echo "variable = $variable" # variable = initial_value
If one of the commands in the pipe aborts, this prematurely terminates execution of the
pipe. Called a broken pipe, this condition sends a SIGPIPE signal.
>|
force redirection (even if the noclobber option is set). This will forcibly overwrite an existing file.
||
OR logical operator. In a test construct, the || operator causes a return of 0 (success) if either of the
linked test conditions is true.
&
Run job in background. A command followed by an & will run in the background.
Within a script, commands and even loops may run in the background.
#!/bin/bash
# background−loop.sh
# ======================================================
# Occasionally also:
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
exit 0
A command run in the background within a script may cause the script to hang,
waiting for a keystroke. Fortunately, there is a remedy for this.
&&
AND logical operator. In a test construct, the && operator causes a return of 0 (success) only if both
the linked test conditions are true.
−
option, prefix. Option flag for a command or filter. Prefix for an operator.
COMMAND −[Option1][Option2][...]
ls −al
set −− $variable
bash$ file
Usage: file [−bciknvzL] [−f namefile] [−m magicfiles] file...
Add a "−" for a more useful result. This causes the shell to await user input.
bash$ file −
abc
standard input: ASCII text
bash$ file −
#!/bin/bash
standard input: Bourne−Again shell script text executable
Now the command accepts input from stdin and analyzes it.
The "−" can be used to pipe stdout to other commands. This permits such stunts as prepending
lines to a file.
#!/bin/bash
BACKUPFILE=backup−$(date +%m−%d−%Y)
# Embeds date in backup filename.
# Thanks, Joshua Tschida, for the idea.
archive=${1:−$BACKUPFILE}
# If no backup−archive filename specified on command line,
#+ it will default to "backup−MM−DD−YYYY.tar.gz."
# Stephane Chazelas points out that the above code will fail
#+ if there are too many files found
#+ or if any filenames contain blank characters.
exit 0
Filenames beginning with "−" may cause problems when coupled with the "−"
redirection operator. A script should check for this and add an appropriate prefix to
such filenames, for example ./−FILENAME, $PWD/−FILENAME, or
$PATHNAME/−FILENAME.
If the value of a variable begins with a −, this may likewise create problems.
var="−n"
echo $var
# Has the effect of "echo −n", and outputs nothing.
−
previous working directory. A cd − command changes to the previous working directory. This uses
the $OLDPWD environmental variable.
Do not confuse the "−" used in this sense with the "−" redirection operator just
discussed. The interpretation of the "−" depends on the context in which it appears.
−
Minus. Minus sign in an arithmetic operation.
=
Equals. Assignment operator
a=28
echo $a # 28
In a different context, the "=" is a string comparison operator.
+
Plus. Addition arithmetic operator.
Certain commands and builtins use the + to enable certain options and the − to disable them.
%
modulo. Modulo (remainder of a division) arithmetic operation.
bash$ echo ~
/home/bozo
bash$ echo ~/
/home/bozo/
bash$ echo ~:
/home/bozo:
~+
current working directory. This corresponds to the $PWD internal variable.
~−
previous working directory. This corresponds to the $OLDPWD internal variable.
=~
regular expression match. This operator was introduced with version 3 of Bash.
^
beginning−of−line. In a regular expression, a "^" addresses the beginning of a line of text.
Control Characters
change the behavior of the terminal or text display. A control character is a CONTROL + key
combination (pressed simultaneously). A control character may also be written in octal or
◊ Ctl−B
Backspace (nondestructive).
◊ Ctl−C
When typing text on the console or in an xterm window, Ctl−D erases the character under
the cursor. When there are no characters present, Ctl−D logs out of the session, as expected.
In an xterm window, this has the effect of closing the window.
◊ Ctl−G
"BEL" (beep). On some old−time teletype terminals, this would actually ring a bell.
◊ Ctl−H
"Rubout" (destructive backspace). Erases characters the cursor backs over while backspacing.
#!/bin/bash
# Embedding Ctl−H in a string.
Horizontal tab.
◊ Ctl−J
Newline (line feed). In a script, may also be expressed in octal notation −− '\012' or in
hexadecimal −− '\x0a'.
◊ Ctl−K
Vertical tab.
When typing text on the console or in an xterm window, Ctl−K erases from the character
under the cursor to end of line. Within a script, Ctl−K may behave differently, as in Lee Lee
Maschmeyer's example, below.
Formfeed (clear the terminal screen). In a terminal, this has the same effect as the clear
command. When sent to a printer, a Ctl−L causes an advance to end of the paper sheet.
◊ Ctl−M
Carriage return.
#!/bin/bash
# Thank you, Lee Maschmeyer, for this example.
###
exit 0
◊ Ctl−Q
Resume (XON).
Suspend (XOFF).
Erase a line of input, from the cursor backward to beginning of line. In some settings, Ctl−U
erases the entire line of input, regardless of cursor position.
◊ Ctl−V
When inputting text, Ctl−V permits inserting control characters. For example, the following
two are equivalent:
echo −e '\x0a'
echo <Ctl−V><Ctl−J>
Ctl−V is primarily useful from within a text editor.
◊ Ctl−W
When typing text on the console or in an xterm window, Ctl−W erases from the character
under the cursor backwards to the first instance of whitespace. In some settings, Ctl−W
erases backwards to first non−alphanumeric character.
◊ Ctl−Z
Blank lines have no effect on the action of a script, and are therefore useful for visually separating
functional sections.
$IFS, the special variable separating fields of input to certain commands, defaults to whitespace.
Variables appear in arithmetic operations and manipulation of quantities, and in string parsing.
$
Let us carefully distinguish between the name of a variable and its value. If variable1 is the name
of a variable, then $variable1 is a reference to its value, the data item it contains.
bash$ variable=23
Enclosing a referenced value in double quotes (" ") does not interfere with variable substitution. This
is called partial quoting, sometimes referred to as "weak quoting." Using single quotes (' ') causes the
variable name to be used literally, and no substitution will take place. This is full quoting, sometimes
referred to as "strong quoting." See Chapter 5 for a detailed discussion.
Note that $variable is actually a simplified alternate form of ${variable}. In contexts where
the $variable syntax causes an error, the longer form may work (see Section 9.3, below).
#!/bin/bash
a=375
hello=$a
#−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# No space permitted on either side of = sign when initializing variables.
# What happens if there is a space?
# "VARIABLE= value"
# ^
#% Script tries to run "value" command with
#+ the environmental variable "VARIABLE" set to "".
#−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
echo $hello
echo ${hello} # Identical to above.
echo "$hello"
echo "${hello}"
echo
hello="A B C D"
echo $hello # A B C D
echo "$hello" # A B C D
# As you see, echo $hello and echo "$hello" give different results.
# =======================================
# Quoting a variable preserves whitespace.
# =======================================
echo
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
echo; echo
echo; echo
exit 0
An uninitialized variable has a "null" value − no assigned value at all (not zero!).
Using a variable before assigning a value to it will usually cause problems.
# Conclusion:
# An uninitialized variable has no value,
#+ however it acts as if it were 0 in an arithmetic operation.
# This is undocumented (and probably non−portable) behavior.
See also Example 11−22.
Do not confuse this with = and −eq, which test, rather than assign!
echo
# Assignment
a=879
echo "The value of \"a\" is $a."
echo
echo
echo
echo
exit 0
#!/bin/bash
# From /etc/rc.d/rc.local
R=$(cat /etc/redhat−release)
arch=$(uname −m)
Unlike many other programming languages, Bash does not segregate its variables by "type". Essentially, Bash
variables are character strings, but, depending on context, Bash permits integer operations and comparisons on
variables. The determining factor is whether the value of a variable contains only digits.
#!/bin/bash
# int−or−string.sh: Integer or string?
a=2334 # Integer.
let "a += 1"
echo "a = $a " # a = 2335
echo # Integer, still.
c=BB34
echo "c = $c" # c = BB34
d=${c/BB/23} # Substitute "23" for "BB".
# This makes $d an integer.
echo "d = $d" # d = 2334
let "d += 1" # 2334 + 1 =
echo "d = $d" # d = 2335
echo
exit 0
Untyped variables are both a blessing and a curse. They permit more flexibility in scripting (enough rope to
hang yourself!) and make it easier to grind out lines of code. However, they permit errors to creep in and
encourage sloppy programming habits.
The burden is on the programmer to keep track of what type the script variables are. Bash will not do it for
you.
In a more general context, each process has an "environment", that is, a group of
variables that hold information that the process may reference. In this sense, the shell
behaves like any other process.
Every time a shell starts, it creates shell variables that correspond to its own
environmental variables. Updating or adding new environmental variables causes the
shell to update its environment, and all the shell's child processes (the commands it
executes) inherit this environment.
The space allotted to the environment is limited. Creating too many environmental
variables or ones that use up excessive space may cause problems.
bash$ du
bash: /usr/bin/du: Argument list too long
(Thank you, Stéphane Chazelas for the clarification, and for providing the above
example.)
If a script sets environmental variables, they need to be "exported", that is, reported to the
environment local to the script. This is the function of the export command.
A script can export variables only to child processes, that is, only to commands or
processes which that particular script initiates. A script invoked from the command
line cannot export variables back to the command line environment. Child processes
cannot export variables back to the parent processes that spawned them.
−−−
positional parameters
arguments passed to the script from the command line: $0, $1, $2, $3 . . .
#!/bin/bash
echo
echo
if [ −n "$2" ]
then
echo "Parameter #2 is $2"
fi
if [ −n "$3" ]
then
echo "Parameter #3 is $3"
fi
# ...
echo "−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−"
echo "All the command−line parameters are: "$*""
if [ $# −lt "$MINPARAMS" ]
then
echo
echo "This script needs at least $MINPARAMS command−line arguments!"
fi
echo
exit 0
Bracket notation for positional parameters leads to a fairly simple way of referencing the last
argument passed to a script on the command line. This also requires indirect referencing.
If a script expects a command line parameter but is invoked without one, this may
cause a null variable assignment, generally an undesirable result. One way to prevent
this is to append an extra character to both sides of the assignment statement using the
expected positional parameter.
variable1_=$1_ # Rather than variable1=$1
# This will prevent an error, even if positional parameter is absent.
critical_argument01=$variable1_
#!/bin/bash
# ex18.sh
E_NOARGS=65
if [ −z "$1" ]
then
echo "Usage: `basename $0` [domain−name]"
exit $E_NOARGS
fi
exit $?
−−−
The shift command reassigns the positional parameters, in effect shifting them to the left one notch.
The old $1 disappears, but $0 (the script name) does not change. If you use a large number of
positional parameters to a script, shift lets you access those past 10, although {bracket} notation also
permits this.
#!/bin/bash
# Using 'shift' to step through all the positional parameters.
exit 0
The shift command works in a similar fashion on parameters passed to a function. See
Example 33−15.
Quoting means just that, bracketing a string in quotes. This has the effect of protecting special characters in
the string from reinterpretation or expansion by the shell or shell script. (A character is "special" if it has an
interpretation other than its literal meaning, such as the wild card character −− *.)
bash$ ls −l [Vv]*
−rw−rw−r−− 1 bozo bozo 324 Apr 2 15:05 VIEWDATA.BAT
−rw−rw−r−− 1 bozo bozo 507 May 4 14:25 vartrace.sh
−rw−rw−r−− 1 bozo bozo 539 Apr 14 17:11 viewdata.sh
bash$ ls −l '[Vv]*'
ls: [Vv]*: No such file or directory
In everyday speech or writing, when we "quote" a phrase, we set it apart and give it special meaning. In a
Bash script, when we quote a string, we set it apart and protect its literal meaning.
Certain programs and utilities reinterpret or expand special characters in a quoted string. An important use of
quoting is protecting a command−line parameter from the shell, but still letting the calling program expand it.
Use double quotes to prevent word splitting. [20] An argument enclosed in double quotes presents itself as a
single word, even if it contains whitespace separators.
Chapter 5. Quoting 34
Advanced Bash−Scripting Guide
variable2="" # Empty.
Enclosing the arguments to an echo statement in double quotes is necessary only when word splitting or
preservation of whitespace is an issue.
#!/bin/bash
# weirdvars.sh: Echoing weird variables.
var="'(]\\{}\$\""
echo $var # '(]\{}$"
echo "$var" # '(]\{}$" Doesn't make a difference.
echo
IFS='\'
echo $var # '(] {}$" \ converted to space. Why?
echo "$var" # '(]\{}$"
exit 0
Single quotes (' ') operate similarly to double quotes, but do not permit referencing variables, since the special
meaning of $ is turned off. Within single quotes, every special character except ' gets interpreted literally.
Consider single quotes ("full quoting") to be a stricter method of quoting than double quotes ("partial
quoting").
Since even the escape character (\) gets a literal interpretation within single quotes, trying to enclose
a single quote within single quotes will not yield the expected result.
echo
5.2. Escaping
Escaping is a method of quoting single characters. The escape (\) preceding a character tells the shell to
interpret that character literally.
Chapter 5. Quoting 35
Advanced Bash−Scripting Guide
With certain commands and utilities, such as echo and sed, escaping a character may have the opposite
effect − it can toggle on a special meaning for that character.
#!/bin/bash
# escaped.sh: escaped characters
echo; echo
echo "==============="
echo "QUOTATION MARKS"
# Version 2 and later of Bash permits using the $'\nnn' construct.
# Note that in this case, '\nnn' is an octal value.
echo $'\t \042 \t' # Quote (") framed by tabs.
Chapter 5. Quoting 36
Advanced Bash−Scripting Guide
echo
echo
echo
echo; echo
echo; echo
exit 0
See Example 34−1 for another example of the $' ' string expansion construct.
\"
gives the quote its literal meaning
# Whereas . . .
The behavior of \ depends on whether it is itself escaped, quoted, or appearing within command
substitution or a here document.
Chapter 5. Quoting 37
Advanced Bash−Scripting Guide
# Command substitution
echo `echo \z` # z
echo `echo \\z` # z
echo `echo \\\z` # \z
echo `echo \\\\z` # \z
echo `echo \\\\\\z` # \z
echo `echo \\\\\\\z` # \\z
echo `echo "\z"` # \z
echo `echo "\\z"` # \z
# Here document
cat <<EOF
\z
EOF # \z
cat <<EOF
\\z
EOF # \z
variable=\
echo "$variable"
# Will not work − gives an error message:
# test.sh: : command not found
# A "naked" escape cannot safely be assigned to a variable.
#
# What actually happens here is that the "\" escapes the newline and
#+ the effect is variable=echo "$variable"
#+ invalid variable assignment
variable=\
23skidoo
echo "$variable" # 23skidoo
# This works, since the second line
#+ is a valid variable assignment.
variable=\
# \^ escape followed by space
echo "$variable" # space
variable=\\
echo "$variable" # \
variable=\\\
echo "$variable"
# Will not work − gives an error message:
# test.sh: \: command not found
#
# First escape escapes second one, but the third one is left "naked",
#+ with same result as first instance, above.
variable=\\\\
echo "$variable" # \\
# Second and fourth escapes escaped.
# This is o.k.
Escaping a space can prevent word splitting in a command's argument list.
Chapter 5. Quoting 38
Advanced Bash−Scripting Guide
file_list="/bin/cat /bin/gzip /bin/more /usr/bin/less /usr/bin/emacs−20.7"
# List of files as argument(s) to a command.
echo "−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−"
# As an alternative:
tar cf − −C /source/directory . |
tar xpvf − −C /dest/directory
# See note below.
# (Thanks, Stéphane Chazelas.)
If a script line ends with a |, a pipe character, then a \, an escape, is not strictly necessary. It is, however,
good programming practice to always escape the end of a line of code that continues to the following
line.
echo "foo
bar"
#foo
#bar
echo
echo 'foo
bar' # No difference yet.
#foo
#bar
echo
echo foo\
bar # Newline escaped.
#foobar
echo
echo "foo\
bar" # Same here, as \ still interpreted as escape within weak quotes.
#foobar
echo
echo 'foo\
bar' # Escape character \ taken literally because of strong quoting.
Chapter 5. Quoting 39
Advanced Bash−Scripting Guide
#foo\
#bar
Chapter 5. Quoting 40
Chapter 6. Exit and Exit Status
...there are dark corners in the Bourne shell, and
people use all of them.
Chet Ramey
The exit command may be used to terminate a script, just as in a C program. It can also return a value, which
is available to the script's parent process.
Every command returns an exit status (sometimes referred to as a return status ). A successful command
returns a 0, while an unsuccessful one returns a non−zero value that usually may be interpreted as an error
code. Well−behaved UNIX commands, programs, and utilities return a 0 exit code upon successful
completion, though there are some exceptions.
Likewise, functions within a script and the script itself return an exit status. The last command executed in the
function or script determines the exit status. Within a script, an exit nnn command may be used to deliver
an nnn exit status to the shell (nnn must be a decimal number in the 0 − 255 range).
When a script ends with an exit that has no parameter, the exit status of the script is the exit status of the
last command executed in the script (previous to the exit).
#!/bin/bash
COMMAND_1
. . .
exit
The equivalent of a bare exit is exit $? or even just omitting the exit.
#!/bin/bash
COMMAND_1
. . .
exit $?
#!/bin/bash
COMMAND1
. . .
$? reads the exit status of the last command executed. After a function returns, $? gives the exit status of the
last command executed in the function. This is Bash's way of giving functions a "return value." After a script
terminates, a $? from the command line gives the exit status of the script, that is, the last command executed
in the script, which is, by convention, 0 on success or an integer in the range 1 − 255 on error.
#!/bin/bash
echo hello
echo $? # Exit status 0 returned because command executed successfully.
echo
The !, the logical "not" qualifier, reverses the outcome of a test or command, and this affects its exit
status.
! true
echo "exit status of \"! true\" = $?" # 1
# Note that the "!" needs a space.
# !true leads to a "command not found" error
#
# The '!' operator prefixing a command invokes the Bash history mechanism.
true
!true
# No error this time, but no negation either.
# It just repeats the previous command (true).
Certain exit status codes have reserved meanings and should not be user−specified in a script.
Every reasonably complete programming language can test for a condition, then act according to the result of
the test. Bash has the test command, various bracket and parenthesis operators, and the if/then construct.
• An if/then construct tests whether the exit status of a list of commands is 0 (since 0 means "success"
by UNIX convention), and if so, executes one or more commands.
• There exists a dedicated command called [ (left bracket special character). It is a synonym for test,
and a builtin for efficiency reasons. This command considers its arguments as comparison expressions
or file tests and returns an exit status corresponding to the result of the comparison (0 for true, 1 for
false).
• With version 2.02, Bash introduced the [[ ... ]] extended test command, which performs comparisons
in a manner more familiar to programmers from other languages. Note that [[ is a keyword, not a
command.
The (( ... )) and let ... constructs also return an exit status of 0 if the arithmetic expressions they
evaluate expand to a non−zero value. These arithmetic expansion constructs may therefore be used to
perform arithmetic comparisons.
word=Linux
letter_sequence=inu
if echo "$word" | grep −q "$letter_sequence"
# The "−q" option to grep suppresses output.
then
echo "$letter_sequence found in $word"
else
echo "$letter_sequence not found in $word"
fi
if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED
then echo "Command succeeded."
Chapter 7. Tests 43
Advanced Bash−Scripting Guide
else echo "Command failed."
fi
• An if/then construct can contain nested comparisons and tests.
if echo "Next *if* is part of the comparison for the first *if*."
if [[ $comparison = "integer" ]]
then (( a < b ))
else
[[ $a < $b ]]
fi
then
echo '$a is less than $b'
fi
This detailed "if−test" explanation courtesy of Stéphane Chazelas.
#!/bin/bash
# Tip:
# If you're unsure of how a certain condition would evaluate,
#+ test it in an if−test.
echo
echo
echo
echo
Chapter 7. Tests 44
Advanced Bash−Scripting Guide
echo "NULL is true."
else
echo "NULL is false."
fi # NULL is false.
echo
echo
echo
echo
echo
Chapter 7. Tests 45
Advanced Bash−Scripting Guide
echo
echo
exit 0
Exercise. Explain the behavior of Example 7−1, above.
if [ condition−true ]
then
command 1
command 2
...
else
# Optional (may be left out if not needed).
# Adds default code block executing if original condition tests false.
command 3
command 4
...
fi
When if and then are on same line in a condition test, a semicolon must terminate the if statement. Both if
and then are keywords. Keywords (or commands) begin statements, and before a new statement on the
same line begins, the old one must terminate.
if [ −x "$filename" ]; then
elif
elif is a contraction for else if. The effect is to nest an inner if/then construct within an outer one.
if [ condition1 ]
then
command1
command2
command3
elif [ condition2 ]
# Same as else if
then
command4
command5
else
default−command
fi
Chapter 7. Tests 46
Advanced Bash−Scripting Guide
The test command is a Bash builtin which tests file types and compares strings.
Therefore, in a Bash script, test does not call the external /usr/bin/test binary,
which is part of the sh−utils package. Likewise, [ does not call /usr/bin/[, which is
linked to /usr/bin/test.
#!/bin/bash
echo
if test −z "$1"
then
echo "No command−line arguments."
else
echo "First command−line argument is $1."
fi
echo
echo
echo
Chapter 7. Tests 47
Advanced Bash−Scripting Guide
# # Note:
# This has been fixed in Bash, version 3.x.
then
echo "No command−line arguments."
else
echo "First command−line argument is $1."
fi
echo
exit 0
The [[ ]] construct is the more versatile Bash version of [ ]. This is the extended test command, adopted from
ksh88.
No filename expansion or word splitting takes place between [[ and ]], but there is parameter expansion
and command substitution.
file=/etc/passwd
if [[ −e $file ]]
then
echo "Password file exists."
fi
Using the [[ ... ]] test construct, rather than [ ... ] can prevent many logic errors in scripts. For example,
the &&, ||, <, and > operators work within a [[ ]] test, despite giving an error within a [ ] construct.
Following an if, neither the test command nor the test brackets ( [ ] or [[ ]] ) are strictly
necessary.
dir=/home/bozo
Similarly, a condition within test brackets may stand alone without an if, when used in
combination with a list construct.
var1=20
var2=22
[ "$var1" −ne "$var2" ] && echo "$var1 is not equal to $var2"
home=/home/bozo
[ −d "$home" ] || echo "$home directory does not exist."
The (( )) construct expands and evaluates an arithmetic expression. If the expression evaluates as zero, it
returns an exit status of 1, or "false". A non−zero expression returns an exit status of 0, or "true". This is in
marked contrast to using the test and [ ] constructs previously discussed.
Chapter 7. Tests 48
Advanced Bash−Scripting Guide
#!/bin/bash
# Arithmetic tests.
(( 0 ))
echo "Exit status of \"(( 0 ))\" is $?." # 1
(( 1 ))
echo "Exit status of \"(( 1 ))\" is $?." # 0
(( 5 > 4 )) # true
echo "Exit status of \"(( 5 > 4 ))\" is $?." # 0
(( 5 > 9 )) # false
echo "Exit status of \"(( 5 > 9 ))\" is $?." # 1
(( 5 − 5 )) # 0
echo "Exit status of \"(( 5 − 5 ))\" is $?." # 1
(( 5 / 4 )) # Division o.k.
echo "Exit status of \"(( 5 / 4 ))\" is $?." # 0
exit 0
−e
file exists
−a
file exists
This is identical in effect to −e. It has been "deprecated," and its use is discouraged.
−f
file is a regular file (not a directory or device file)
−s
file is not zero size
−d
file is a directory
−b
file is a block device (floppy, cdrom, etc.)
−c
Chapter 7. Tests 49
Advanced Bash−Scripting Guide
This test option may be used to check whether the stdin ([ −t 0 ]) or stdout ([ −t 1 ]) in
a given script is a terminal.
−r
file has read permission (for the user running the test)
−w
file has write permission (for the user running the test)
−x
file has execute permission (for the user running the test)
−g
set−group−id (sgid) flag set on file or directory
If a directory has the sgid flag set, then a file created within that directory belongs to the group that
owns the directory, not necessarily to the group of the user who created the file. This may be useful
for a directory shared by a workgroup.
−u
set−user−id (suid) flag set on file
A binary owned by root with set−user−id flag set runs with root privileges, even when an
ordinary user invokes it. [21] This is useful for executables (such as pppd and cdrecord) that need to
access system hardware. Lacking the suid flag, these binaries could not be invoked by a non−root
user.
Commonly known as the "sticky bit," the save−text−mode flag is a special type of file permission. If
a file has this flag set, that file will be kept in cache memory, for quicker access. [22] If set on a
directory, it restricts write permission. Setting the sticky bit adds a t to the permissions on the file or
directory listing.
If a user does not own a directory that has the sticky bit set, but has write permission in that directory,
he can only delete files in it that he owns. This keeps users from inadvertently overwriting or deleting
each other's files in a publicly accessible directory, such as /tmp. (The owner of the directory or root
can, of course, delete or rename files there.)
Chapter 7. Tests 50
Advanced Bash−Scripting Guide
−O
you are owner of file
−G
group−id of file same as yours
−N
file modified since it was last read
f1 −nt f2
file f1 is newer than f2
f1 −ot f2
file f1 is older than f2
f1 −ef f2
files f1 and f2 are hard links to the same file
!
"not" −− reverses the sense of the tests above (returns true if condition absent).
#!/bin/bash
# broken−link.sh
# Written by Lee bigelow <[email protected]>
# Used with permission.
#A pure shell script to find dead symlinks and output them quoted
#so they can be fed to xargs and dealt with :)
#eg. broken−link.sh /somedir /someotherdir|xargs rm
#
#This, however, is a better method:
#
#find "somedir" −type l −print0|\
#xargs −r0 file|\
#grep "broken symbolic"|
#sed −e 's/^\|: *broken symbolic.*$/"/g'
#
#but that wouldn't be pure bash, now would it.
#Caution: beware the /proc file system and any circular links!
##############################################################
Chapter 7. Tests 51
Advanced Bash−Scripting Guide
#Send each arg that was passed to the script to the linkchk function
#if it is a valid directoy. If not, then print the error message
#and usage info.
################
for directory in $directorys; do
if [ −d $directory ]
then linkchk $directory
else
echo "$directory is not a directory"
echo "Usage: $0 dir1 dir2 ..."
fi
done
exit 0
Example 28−1, Example 10−7, Example 10−3, Example 28−3, and Example A−1 also illustrate uses of the
file test operators.
integer comparison
−eq
is equal to
Chapter 7. Tests 52
Advanced Bash−Scripting Guide
string comparison
is equal to
if [ "$a" = "$b" ]
==
is equal to
if [ "$a" == "$b" ]
if [ "$a" != "$b" ]
Chapter 7. Tests 53
Advanced Bash−Scripting Guide
>
is greater than, in ASCII alphabetical order
The −n test absolutely requires that the string be quoted within the test brackets. Using
an unquoted string with ! −z, or even just the unquoted string alone within test
brackets (see Example 7−6) normally works, however, this is an unsafe practice.
Always quote a tested string. [23]
#!/bin/bash
a=4
b=5
echo
echo
if [ "$a" != "$b" ]
then
echo "$a is not equal to $b."
echo "(string comparison)"
# "4" != "5"
# ASCII 52 != ASCII 53
fi
echo
Chapter 7. Tests 54
Advanced Bash−Scripting Guide
exit 0
#!/bin/bash
# str−test.sh: Testing null strings and unquoted strings,
#+ but not strings and sealing wax, not to mention cabbages and kings . . .
# Using if [ ... ]
echo
echo
echo
Chapter 7. Tests 55
Advanced Bash−Scripting Guide
string1=initialized
string1="a = b"
exit 0
# Thank you, also, Florian Wisser, for the "heads−up".
#!/bin/bash
# zmore
NOARGS=65
NOTFOUND=66
NOTGZIP=67
filename=$1
if [ ${filename##*.} != "gz" ]
# Using bracket in variable substitution.
then
echo "File $1 is not a gzipped file!"
exit $NOTGZIP
fi
Chapter 7. Tests 56
Advanced Bash−Scripting Guide
zcat $1 | more
compound comparison
−a
logical and
exp1 −a exp2 returns true if both exp1 and exp2 are true.
−o
logical or
These are similar to the Bash comparison operators && and ||, used within double brackets.
if [ condition1 ]
then
if [ condition2 ]
then
do−something # But only if both "condition1" and "condition2" valid.
fi
fi
See Example 34−4 for an example of nested if/then condition tests.
if [ −f $HOME/.Xclients ]; then
exec $HOME/.Xclients
elif [ −f /etc/X11/xinit/Xclients ]; then
exec /etc/X11/xinit/Xclients
else
# failsafe settings. Although we should never get here
Chapter 7. Tests 57
Advanced Bash−Scripting Guide
# (we provide fallbacks in Xclients as well) it can't hurt.
xclock −geometry 100x100−5+5 &
xterm −geometry 80x50−50+150 &
if [ −f /usr/bin/netscape −a −f /usr/share/doc/HTML/index.html ]; then
netscape /usr/share/doc/HTML/index.html &
fi
fi
Explain the "test" constructs in the above excerpt, then examine the entire file,
/etc/X11/xinit/xinitrc, and analyze the if/then test constructs there. You may need to refer ahead to
the discussions of grep, sed, and regular expressions.
Chapter 7. Tests 58
Chapter 8. Operations and Related Topics
8.1. Operators
assignment
variable assignment
Initializing or changing the value of a variable
=
All−purpose assignment operator, which works for both arithmetic and string assignments.
var=27
category=minerals # No spaces allowed after the "=".
Do not confuse the "=" assignment operator with the = test operator.
# = as a test operator
if [ "$string1" = "$string2" ]
# if [ "X$string1" = "X$string2" ] is safer,
# to prevent an error message should one of the variables be empty.
# (The prepended "X" characters cancel out.)
then
command
fi
arithmetic operators
+
plus
−
minus
*
multiplication
/
division
**
exponentiation
let "z=5**3"
echo "z = $z" # z = 125
%
modulo, or mod (returns the remainder of an integer division operation)
bash$ expr 5 % 3
2
#!/bin/bash
# gcd.sh: greatest common divisor
# Uses Euclid's algorithm
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Argument check
ARGS=2
E_BADARGS=65
if [ $# −ne "$ARGS" ]
then
echo "Usage: `basename $0` first−number second−number"
exit $E_BADARGS
fi
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
gcd ()
{
# Exercise :
# −−−−−−−−
# Check command−line arguments to make sure they are integers,
#+ and exit the script with an appropriate error message if not.
exit 0
+=
"plus−equal" (increment variable by a constant)
#!/bin/bash
# Counting to 11 in 10 different ways.
: $((n = $n + 1))
# ":" necessary because otherwise Bash attempts
#+ to interpret "$((n = $n + 1))" as a command.
echo −n "$n "
(( n = n + 1 ))
# A simpler alternative to the method above.
# Thanks, David Lombard, for pointing this out.
echo −n "$n "
n=$(($n + 1))
echo −n "$n "
: $[ n = $n + 1 ]
# ":" necessary because otherwise Bash attempts
#+ to interpret "$[ n = $n + 1 ]" as a command.
# Works even if "n" was initialized as a string.
n=$[ $n + 1 ]
# Works even if "n" was initialized as a string.
#* Avoid this type of construct, since it is obsolete and nonportable.
# Thanks, Stephane Chazelas.
echo −n "$n "
echo
exit 0
Integer variables in Bash are actually signed long (32−bit) integers, in the range of
−2147483648 to 2147483647. An operation that takes a variable outside these limits
will give an erroneous result.
a=2147483646
echo "a = $a" # a = 2147483646
let "a+=1" # Increment "a".
echo "a = $a" # a = 2147483647
let "a+=1" # increment "a" again, past the limit.
echo "a = $a" # a = −2147483648
# ERROR (out of range)
As of version 2.05b, Bash supports 64−bit integers.
Bash does not understand floating point arithmetic. It treats numbers containing a decimal point as
strings.
a=1.5
bitwise operators
<<
logical operators
&&
and (logical)
&& may also, depending on context, be used in an and list to concatenate commands.
||
or (logical)
if [ $condition1 ] || [ $condition2 ]
# Same as: if [ $condition1 −o $condition2 ]
# Returns true if either condition1 or condition2 holds true...
Bash tests the exit status of each statement linked with a logical operator.
#!/bin/bash
a=24
b=47
a=rhino
b=crocodile
if [ "$a" = rhino ] && [ "$b" = crocodile ]
then
echo "Test #5 succeeds."
else
echo "Test #5 fails."
fi
exit 0
The && and || operators also find use in an arithmetic context.
bash$ echo $(( 1 && 2 )) $((3 && 0)) $((4 || 0)) $((0 || 0))
1 0 1 0
miscellaneous operators
,
comma operator
The comma operator chains together two or more arithmetic operations. All the operations are
evaluated (with possible side effects), but only the last operation is returned.
#!/bin/bash
# numbers.sh: Representation of numbers in different bases.
echo
# Important note:
# −−−−−−−−−−−−−−
# Using a digit out of range of the specified base notation
#+ gives an error message.
for n in 0 1 2 3 4 5
do
echo "BASH_VERSINFO[$n] = ${BASH_VERSINFO[$n]}"
done
Checking $BASH_VERSION is a good method of determining which shell is running. $SHELL does
not necessarily give the correct answer.
$DIRSTACK
the top value in the directory stack (affected by pushd and popd)
This builtin variable corresponds to the dirs command, however dirs shows the entire contents of the
directory stack.
$EDITOR
the default editor invoked by a script, usually vi or emacs.
$EUID
"effective" user ID number
Identification number of whatever identity the current user has assumed, perhaps by means of su.
xyz23 ()
{
echo "$FUNCNAME now executing." # xyz23 now executing.
}
xyz23
This is a listing (array) of the group id numbers for current user, as recorded in /etc/passwd.
$HOME
home directory of the user, usually /home/username (see Example 9−15)
$HOSTNAME
The hostname command assigns the system name at bootup in an init script. However, the
gethostname() function sets the Bash internal variable $HOSTNAME. See also Example 9−15.
$HOSTTYPE
host type
$IFS defaults to whitespace (space, tab, and newline), but may be changed, for example, to parse a
comma−separated data file. Note that $* uses the first character held in $IFS. See Example 5−1.
$IFS does not handle whitespace the same as it does other characters.
#!/bin/bash
# $IFS treats whitespace differently than other characters.
output_args_one_per_line()
{
for arg
do echo "[$arg]"
done
}
IFS=" "
var=" a b c "
output_args_one_per_line $var # output_args_one_per_line `echo " a b c "`
#
# [a]
# [b]
# [c]
IFS=:
var=":a::b:c:::" # Same as above, but substitute ":" for " ".
output_args_one_per_line $var
#
# []
# [a]
# []
# [b]
# [c]
# []
# []
# []
# The same thing happens with the "FS" field separator in awk.
echo
exit 0
(Thanks, S. C., for clarification and examples.)
See also Example 12−37, Example 10−7, and Example 17−14 for instructive examples of using
$IFS.
$IGNOREEOF
ignore EOF: how many end−of−files (control−D) the shell will ignore before logging out.
$LC_COLLATE
Often set in the .bashrc or /etc/profile files, this variable controls collation order in filename
expansion and pattern matching. If mishandled, LC_COLLATE can cause unexpected results in
filename globbing.
When given a command, the shell automatically does a hash table search on the directories listed in
the path for the executable. The path is stored in the environmental variable, $PATH, a list of
directories, separated by colons. Normally, the system stores the $PATH definition in
/etc/profile and/or ~/.bashrc (see Appendix G).
The current "working directory", ./, is usually omitted from the $PATH as a security
measure.
$PIPESTATUS
Array variable holding exit status(es) of last executed foreground pipe. Interestingly enough, this does
not necessarily give the same result as the exit status of the last executed command.
The members of the $PIPESTATUS array hold the exit status of each respective command executed
in a pipe. $PIPESTATUS[0] holds the exit status of the first command in the pipe,
$PIPESTATUS[1] the exit status of the second command, and so on.
The $PIPESTATUS variable may contain an erroneous 0 value in a login shell (in
releases prior to 3.0 of Bash).
tcsh% bash
The above lines contained in a script would produce the expected 0 1 0 output.
Thank you, Wayne Pollock for pointing this out and supplying the above example.
bash$ $ ls | bogus_command | wc
Chet Ramey attributes the above output to the behavior of ls. If ls writes to a pipe
whose output is not read, then SIGPIPE kills it, and its exit status is 141. Otherwise its
exit status is 0, as expected. This likewise is the case for tr.
bash$ $ ls | bogus_command | wc
bash: bogus_command: command not found
0 0 0
$PPID
The $PPID of a process is the process ID (pid) of its parent process. [24]
The secondary prompt, seen when additional input is expected. It displays as ">".
$PS3
The tertiary prompt, displayed in a select loop (see Example 10−29).
$PS4
The quartenary prompt, shown at the beginning of each line of output when invoking a script with the
−x option. It displays as "+".
$PWD
working directory (directory you are in at the time)
#!/bin/bash
E_WRONG_DIRECTORY=73
TargetDirectory=/home/bozo/projects/GreatAmericanNovel
if [ "$PWD" != "$TargetDirectory" ]
then # Keep from wiping out wrong directory by accident.
echo "Wrong directory!"
echo "In $PWD, rather than $TargetDirectory!"
echo "Bailing out!"
exit $E_WRONG_DIRECTORY
fi
rm −rf *
rm .[A−Za−z0−9]* # Delete dotfiles.
# rm −f .[^.]* ..?* to remove filenames beginning with multiple dots.
# (shopt −s dotglob; rm −f *) will also work.
# Thanks, S.C. for pointing this out.
# Filenames may contain all characters in the 0 − 255 range, except "/".
# Deleting files beginning with weird characters is left as an exercise.
echo
echo "Done."
echo "Old files deleted in $TargetDirectory."
echo
exit 0
$REPLY
The default value when a variable is not supplied to read. Also applicable to select menus, but only
supplies the item number of the variable chosen, not the value of the variable itself.
#!/bin/bash
# reply.sh
echo
echo −n "What is your favorite vegetable? "
read
echo
echo −n "What is your favorite fruit? "
read fruit
echo "Your favorite fruit is $fruit."
echo "but..."
echo "Value of \$REPLY is still $REPLY."
# $REPLY is still set to its previous value because
#+ the variable $fruit absorbed the new "read" value.
echo
exit 0
$SECONDS
The number of seconds the script has been running.
TIME_LIMIT=10
INTERVAL=1
echo
echo "Hit Control−C to exit before $TIME_LIMIT seconds."
echo
exit 0
$SHELLOPTS
the list of enabled shell options, a readonly variable
$SHLVL
Shell level, how deeply Bash is nested. If, at the command line, $SHLVL is 1, then in a script it will
increment to 2.
$TMOUT
If the $TMOUT environmental variable is set to a non−zero value time, then the shell prompt will time
out after time seconds. This will cause a logout.
As of version 2.05b of Bash, it is now possible to use $TMOUT in a script in combination with read.
if [ −z "$song" ]
then
song="(no answer)"
# Default response.
fi
#!/bin/bash
# timed−input.sh
PrintAnswer()
{
if [ "$answer" = TIMEOUT ]
then
echo $answer
else # Don't want to mix up the two instances.
echo "Your favorite veggie is $answer"
kill $! # Kills no longer needed TimerOn function running in background.
# $! is PID of last job running in background.
fi
TimerOn()
{
sleep $TIMELIMIT && kill −s 14 $$ &
# Waits 3 seconds, then sends sigalarm to script.
}
Int14Vector()
{
answer="TIMEOUT"
PrintAnswer
exit 14
}
exit 0
#!/bin/bash
# timeout.sh
timedout_read() {
timeout=$1
varname=$2
old_tty_settings=`stty −g`
stty −icanon min 0 time ${timeout}0
eval read $varname # or just read $varname
stty "$old_tty_settings"
# See man page for "stty".
}
echo
echo
exit 0
Perhaps the simplest method is using the −t option to read.
#!/bin/bash
# t−out.sh
# Inspired by a suggestion from "syngin seven" (thanks).
TIMELIMIT=4 # 4 seconds
echo
if [ −z "$variable" ] # Is null?
then
echo "Timed out, variable still unset."
else
echo "variable = $variable"
fi
exit 0
$UID
user ID number
This is the current user's real id, even if she has temporarily assumed another identity through su.
$UID is a readonly variable, not subject to change from the command line or within a script, and is
the counterpart to the id builtin.
#!/bin/bash
# am−i−root.sh: Am I root or not?
if [ "$UID" −eq "$ROOT_UID" ] # Will the real "root" please stand up?
then
echo "You are root."
else
echo "You are just an ordinary user (but mom loves you just the same)."
fi
exit 0
# ============================================================= #
# Code below will not execute, because the script already exited.
ROOTUSER_NAME=root
The variables $ENV, $LOGNAME, $MAIL, $TERM, $USER, and $USERNAME are not
Bash builtins. These are, however, often set as environmental variables in one of the
Bash startup files. $SHELL, the name of the user's login shell, may be set from
Positional Parameters
#!/bin/bash
# arglist.sh
# Invoke this script with several arguments, such as "one two three".
E_BADARGS=65
if [ ! −n "$1" ]
then
echo "Usage: `basename $0` argument1 argument2 etc."
exit $E_BADARGS
fi
echo
echo
echo
exit 0
Following a shift, the $@ holds the remaining command−line parameters, lacking the previous $1,
which was lost.
#!/bin/bash
# Invoke with ./scriptname 1 2 3 4 5
echo "$@" # 1 2 3 4 5
shift
echo "$@" # 2 3 4 5
shift
echo "$@" # 3 4 5
#!/bin/bash
echo
IFS=:
echo 'IFS=":", using "$*"'
c=0
for i in "$*"
do echo "$((c+=1)): [$i]"
done
echo −−−
var=$*
echo 'IFS=":", using "$var" (var=$*)'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo −−−
var="$*"
echo 'IFS=":", using $var (var="$*")'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo −−−
var=$@
echo 'IFS=":", using $var (var=$@)'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo −−−
var="$@"
echo 'IFS=":", using "$var" (var="$@")'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo −−−
echo
exit 0
#!/bin/bash
mecho $@ # a,b,c
mecho "$@" # a,b,c
exit 0
$−
Flags passed to script (using set). See Example 11−15.
This was originally a ksh construct adopted into Bash, and unfortunately it does not
seem to work reliably in Bash scripts. One possible use for it is to have a script
self−test whether it is interactive.
$!
PID (process ID) of last job run in background
LOG=$0.log
COMMAND1="sleep 100"
echo "Logging PIDs background commands for script: $0" >> "$LOG"
# So they can be monitored, and killed as necessary.
echo >> "$LOG"
# Logging commands.
# Thank you, Sylvain Fourmanoit, for this creative use of the "!" variable.
$_
Special variable set to last argument of previous command executed.
#!/bin/bash
echo $_ # /bin/bash
# Just called /bin/bash to run the script.
:
echo $_ # :
$?
Exit status of a command, function, or the script itself (see Example 23−7)
$$
Process ID of the script itself. The $$ variable often finds use in scripts to construct "unique" temp
file names (see Example A−13, Example 29−6, Example 12−28, and Example 11−26). This is usually
simpler than invoking mktemp.
Bash supports a surprising number of string manipulation operations. Unfortunately, these tools lack a unified
focus. Some are a subset of parameter substitution, and others fall under the functionality of the UNIX expr
command. This results in inconsistent command syntax and overlap of functionality, not to mention
confusion.
String Length
${#string}
expr length $string
expr "$string" : '.*'
stringZ=abcABC123ABCabc
echo ${#stringZ} # 15
echo `expr length $stringZ` # 15
echo `expr "$stringZ" : '.*'` # 15
#!/bin/bash
# paragraph−space.sh
while read line # For as many lines as the input file has...
do
echo "$line" # Output the line itself.
len=${#line}
if [ "$len" −lt "$MINLEN" ]
then echo # Add a blank line after short line.
fi
done
exit 0
stringZ=abcABC123ABCabc
# |−−−−−−|
Index
stringZ=abcABC123ABCabc
echo `expr index "$stringZ" C12` # 6
# C position.
Substring Extraction
${string:position}
Extracts substring from $string at $position.
If the $string parameter is "*" or "@", then this extracts the positional parameters, [26] starting at
$position.
${string:position:length}
Extracts $length characters of substring from $string at $position.
stringZ=abcABC123ABCabc
# 0123456789.....
# 0−based indexing.
stringZ=abcABC123ABCabc
# 123456789......
# 1−based indexing.
stringZ=abcABC123ABCabc
# =======
stringZ=abcABC123ABCabc
# ======
Substring Removal
${string#substring}
Strips shortest match of $substring from front of $string.
${string##substring}
Strips longest match of $substring from front of $string.
stringZ=abcABC123ABCabc
# |−−−−|
# |−−−−−−−−−−|
stringZ=abcABC123ABCabc
# ||
# |−−−−−−−−−−−−|
echo ${stringZ%%b*c} # a
# Strip out longest match between 'b' and 'c', from back of $stringZ.
This operator is useful for generating filenames.
#!/bin/bash
# cvt.sh:
# Converts all the MacPaint image files in a directory to "pbm" format.
OPERATION=macptopbm
SUFFIX=pbm # New filename suffix.
if [ −n "$1" ]
then
directory=$1 # If directory name given as a script argument...
else
directory=$PWD # Otherwise use current working directory.
fi
# Assumes all files in the target directory are MacPaint image files,
#+ with a ".mac" filename suffix.
exit 0
# Exercise:
# −−−−−−−−
# As it stands, this script converts *all* the files in the current
#+ working directory.
# Modify it to work *only* on files with a ".mac" suffix.
#!/bin/bash
# ra2ogg.sh: Convert streaming audio files (*.ra) to ogg.
##########################################################################
mplayer "$1" −ao pcm:file=$OUTFILE
oggenc "$OUTFILE" # Correct file extension automatically added by oggenc.
##########################################################################
exit $?
# Note:
# −−−−
# On a Website, simply clicking on a *.ram streaming audio file
#+ usually only downloads the URL of the actual audio file, the *.ra file.
# You can then use "wget" or something similar
#+ to download the *.ra file itself.
# Exercises:
# −−−−−−−−−
# As is, this script converts only *.ra filenames.
# Add flexibility by permitting use of *.ram and other filenames.
#
# If you're really ambitious, expand the script
#+ to do automatic downloads and conversions of streaming audio files.
# Given a URL, batch download streaming audio files (using "wget")
#+ and convert them.
A simple emulation of getopt using substring extraction constructs.
#!/bin/bash
# getopt−simple.sh
# Author: Chris Morgan
# Used in the ABS Guide with permission.
getopt_simple()
{
echo "getopt_simple()"
echo "Parameters are '$*'"
until [ −z "$1" ]
do
echo "Processing parameter of: '$1'"
if [ ${1:0:1} = '/' ]
then
tmp=${1:1} # Strip off leading '/' . . .
parameter=${tmp%%=*} # Extract name.
value=${tmp##*=} # Extract value.
echo "Parameter: '$parameter', value: '$value'"
eval $parameter=$value
fi
shift
done
}
exit 0
−−−
Substring Replacement
${string/substring/replacement}
Replace first match of $substring with $replacement.
${string//substring/replacement}
Replace all matches of $substring with $replacement.
stringZ=abcABC123ABCabc
stringZ=abcABC123ABCabc
String=23skidoo1
# 012345678 Bash
# 123456789 awk
# Note different string indexing system:
# Bash numbers first character of string as '0'.
# Awk numbers first character of string as '1'.
exit 0
1. Example 12−9
2. Example 9−17
3. Example 9−18
4. Example 9−19
5. Example 9−21
${parameter}
Same as $parameter, i.e., value of the variable parameter. In certain contexts, only the less
ambiguous ${parameter} form works.
your_id=${USER}−on−${HOSTNAME}
echo "$your_id"
#
echo "Old \$PATH = $PATH"
PATH=${PATH}:/opt/bin #Add /opt/bin to $PATH for duration of script.
echo "New \$PATH = $PATH"
${parameter−default}, ${parameter:−default}
If parameter not set, use default.
#!/bin/bash
# param−sub.sh
username0=
echo "username0 has been declared, but is set to null."
echo "username0 = ${username0−`whoami`}"
# Will not echo.
echo
username2=
echo "username2 has been declared, but is set to null."
echo "username2 = ${username2:−`whoami`}"
# ^
# Will echo because of :− rather than just − in condition test.
# Compare to first instance, above.
# Once again:
variable=
# variable has been declared, but is set to null.
unset variable
echo "${variable−2}" # 2
echo "${variable:−3}" # 3
exit 0
The default parameter construct finds use in providing "missing" command−line arguments in scripts.
DEFAULT_FILENAME=generic.data
filename=${1:−$DEFAULT_FILENAME}
# If not otherwise specified, the following command block operates
#+ on the file "generic.data".
#
# Commands follow.
See also Example 3−4, Example 28−2, and Example A−6.
Compare this method with using an and list to supply a default command−line argument.
${parameter=default}, ${parameter:=default}
Both forms nearly equivalent. The : makes a difference only when $parameter has been declared and
is null, [27] as above.
echo ${username=`whoami`}
# Variable "username" is now set to `whoami`.
${parameter+alt_value}, ${parameter:+alt_value}
If parameter set, use alt_value, else use null string.
Both forms nearly equivalent. The : makes a difference only when parameter has been declared and is
null, see below.
a=${param1+xyz}
echo "a = $a" # a =
param2=
a=${param2+xyz}
echo "a = $a" # a = xyz
param3=123
a=${param3+xyz}
echo "a = $a" # a = xyz
echo
echo "###### \${parameter:+alt_value} ########"
echo
a=${param4:+xyz}
echo "a = $a" # a =
param5=
a=${param5:+xyz}
echo "a = $a" # a =
# Different result from a=${param5+xyz}
param6=123
a=${param6:+xyz}
echo "a = $a" # a = xyz
${parameter?err_msg}, ${parameter:?err_msg}
If parameter set, use it, else print err_msg.
Both forms nearly equivalent. The : makes a difference only when parameter has been declared and is
null, as above.
#!/bin/bash
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
ThisVariable=Value−of−ThisVariable
# Note, by the way, that string variables may be set
#+ to characters disallowed in their names.
: ${ThisVariable?}
echo "Value of ThisVariable is $ThisVariable".
echo
echo
echo "You will not see this message, because script already terminated."
HERE=0
exit $HERE # Will NOT exit here.
#!/bin/bash
# usage−message.sh
# Check the exit status, both with and without command−line parameter.
# If command−line parameter present, then "$?" is 0.
# If not, then "$?" is 1.
Parameter substitution and/or expansion. The following expressions are the complement to the match in
expr string operations (see Example 12−9). These particular ones are used mostly in parsing file path names.
${#var}
String length (number of characters in $var). For an array, ${#array} is the length of the first
element in the array.
Exceptions:
#!/bin/bash
# length.sh
E_NO_ARGS=65
var01=abcdEFGH28ij
echo "var01 = ${var01}"
echo "Length of var01 = ${#var01}"
# Now, let's try embedding a space.
var02="abcd EFGH28ij"
echo "var02 = ${var02}"
echo "Length of var02 = ${#var02}"
exit 0
${var#Pattern}, ${var##Pattern}
Remove from $var the shortest/longest part of $Pattern that matches the front end of $var.
#!/bin/bash
# patt−matching.sh
var1=abcd12345abc6789
pattern1=a*c # * (wild card) matches everything between a − c.
echo
echo "var1 = $var1" # abcd12345abc6789
echo "var1 = ${var1}" # abcd12345abc6789
# (alternate form)
echo "Number of characters in ${var1} = ${#var1}"
echo
echo
exit 0
#!/bin/bash
# rfe.sh: Renaming file extensions.
#
# rfe old_extension new_extension
#
# Example:
# To rename all *.gif files in working directory to *.jpg,
# rfe gif jpg
E_BADARGS=65
case $# in
0|1) # The vertical bar means "or" in this context.
echo "Usage: `basename $0` old_file_suffix new_file_suffix"
exit $E_BADARGS # If 0 or 1 arg, then bail out.
;;
esac
exit 0
If Replacement is omitted, then the first match of Pattern is replaced by nothing, that is,
deleted.
${var//Pattern/Replacement}
Global replacement. All matches of Pattern, within var replaced with Replacement.
As above, if Replacement is omitted, then all occurrences of Pattern are replaced by nothing,
that is, deleted.
#!/bin/bash
var1=abcd−1234−defg
echo "var1 = $var1"
t=${var1#*−*}
echo "var1 (with everything, up to and including first − stripped out) = $t"
# t=${var1#*−} works just the same,
#+ since # matches the shortest string,
#+ and * matches everything preceding, including an empty string.
# (Thanks, Stephane Chazelas, for pointing this out.)
t=${var1##*−*}
echo "If var1 contains a \"−\", returns empty string... var1 = $t"
t=${var1%*−*}
echo "var1 (with everything from the last − on stripped out) = $t"
echo
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
path_name=/home/bozo/ideas/thoughts.for.today
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
echo "path_name = $path_name"
t=${path_name##/*/}
echo "path_name, stripped of prefixes = $t"
# Same effect as t=`basename $path_name` in this particular case.
# t=${path_name%/}; t=${t##*/} is a more general solution,
#+ but still fails sometimes.
# If $path_name ends with a newline, then `basename $path_name` will not work,
#+ but the above expression will.
# (Thanks, S.C.)
t=${path_name%/*.*}
# Same effect as t=`dirname $path_name`
echo "path_name, stripped of suffixes = $t"
# These will fail in some cases, such as "../", "/foo////", # "foo/", "/".
# Removing suffixes, especially when the basename has no suffix,
#+ but the dirname does, also complicates matters.
# (Thanks, S.C.)
t=${path_name:11}
echo "$path_name, with first 11 chars stripped off = $t"
t=${path_name:11:5}
echo "$path_name, with first 11 chars stripped off, length 5 = $t"
echo
t=${path_name/bozo/clown}
echo "$path_name with \"bozo\" replaced by \"clown\" = $t"
t=${path_name/today/}
echo "$path_name with \"today\" deleted = $t"
t=${path_name//o/O}
echo "$path_name with all o's capitalized = $t"
t=${path_name//o/}
echo "$path_name with all o's deleted = $t"
exit 0
${var/#Pattern/Replacement}
If prefix of var matches Pattern, then substitute Replacement for Pattern.
${var/%Pattern/Replacement}
If suffix of var matches Pattern, then substitute Replacement for Pattern.
#!/bin/bash
# var−match.sh:
# Demo of pattern replacement at prefix / suffix of string.
echo
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Must match at beginning / end of string,
#+ otherwise no replacement results.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
v3=${v0/#123/000} # Matches, but not at beginning.
echo "v3 = $v3" # abc1234zip1234abc
# NO REPLACEMENT.
v4=${v0/%123/000} # Matches, but not at end.
echo "v4 = $v4" # abc1234zip1234abc
# NO REPLACEMENT.
xyz23=whatever
xyz24=
declare/typeset options
−r readonly
declare −r var1
(declare −r var1 works the same as readonly var1)
This is the rough equivalent of the C const type qualifier. An attempt to change the value of a
readonly variable fails with an error message.
−i integer
declare −i number
# The script will treat subsequent occurrences of "number" as an integer.
number=3
echo "Number = $number" # Number = 3
number=three
echo "Number = $number" # Number = 0
# Tries to evaluate the string "three" as an integer.
Certain arithmetic operations are permitted for declared integer variables without the need for expr or
let.
n=6/3
echo "n = $n" # n = 6/3
declare −i n
n=6/3
echo "n = $n" # n = 2
−a array
declare −a indices
The variable indices will be treated as an array.
−f functions
declare −f function_name
A declare −f function_name in a script lists just the function named.
−x export
declare −x var3
This declares a variable as available for exporting outside the environment of the script itself.
−x var=$value
declare −x var3=373
The declare command permits assigning a value to a variable in the same statement as setting its
properties.
#!/bin/bash
func1 ()
{
echo This is a function.
}
echo
echo
foo ()
{
FOO="bar"
bar ()
{
foo
echo $FOO
}
foo (){
declare FOO="bar"
}
bar ()
{
foo
echo $FOO
}
Assume that the value of a variable is the name of a second variable. Is it somehow possible to retrieve the
value of this second variable from the first one? For example, if a=letter_of_alphabet and
letter_of_alphabet=z, can a reference to a return z? This can indeed be done, and it is called an
indirect reference. It uses the unusual eval var1=\$$var2 notation.
#!/bin/bash
# ind−ref.sh: Indirect variable referencing.
# Accessing the contents of the contents of a variable.
echo
# Direct reference.
echo "a = $a" # a = letter_of_alphabet
# Indirect reference.
eval a=\$$a
echo "Now a = $a" # Now a = z
echo
t=table_cell_3
table_cell_3=24
echo "\"table_cell_3\" = $table_cell_3" # "table_cell_3" = 24
echo −n "dereferenced \"t\" = "; eval echo \$$t # dereferenced "t" = 24
# In this simple case, the following also works (why?).
# eval t=\$$t; echo "\"t\" = $t"
echo
t=table_cell_3
NEW_VAL=387
table_cell_3=$NEW_VAL
echo "Changing value of \"table_cell_3\" to $NEW_VAL."
echo "\"table_cell_3\" now $table_cell_3"
echo −n "dereferenced \"t\" now "; eval echo \$$t
# "eval" takes the two arguments "echo" and "\$$t" (set equal to $table_cell_3)
echo
# Another method is the ${!t} notation, discussed in "Bash, version 2" section.
# See also ex78.sh.
exit 0
Of what practical use is indirect referencing of variables? It gives Bash a little of the functionality of pointers
in C, for instance, in table lookup. And, it also has some other very interesting applications. . . .
Nils Radtke shows how to build "dynamic" variable names and evaluate their contents. This can be useful
when sourcing configuration files.
#!/bin/bash
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# This could be "sourced" from a separate file.
isdnMyProviderRemoteNet=172.16.0.100
isdnYourProviderRemoteNet=10.0.0.10
isdnOnlineService="MyProvider"
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# ================================================================
chkMirrorArchs () {
arch="$1";
getSparc="true"
unset getIa64
chkMirrorArchs sparc
echo $? # 0
# True
chkMirrorArchs Ia64
echo $? # 1
# False
# Notes:
# −−−−−
# Even the to−be−substituted variable name part is built explicitly.
# The parameters to the chkMirrorArchs calls are all lower case.
# The variable name is composed of two parts: "get" and "Sparc" . . .
#!/bin/bash
ARGS=2
E_WRONGARGS=65
filename=$1
column_number=$2
" "$filename"
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# End awk script.
exit 0
This method of indirect referencing is a bit tricky. If the second order variable changes its value, then the
first order variable must be properly dereferenced (as in the above example). Fortunately, the
${!variable} notation introduced with version 2 of Bash (see Example 34−2) makes indirect
referencing more intuitive.
Bash does not support pointer arithmetic, and this severely limits the usefulness of indirect referencing. In
fact, indirect referencing in a scripting language is an ugly kludge.
#!/bin/bash
MAXCOUNT=10
count=1
echo
echo "$MAXCOUNT random numbers:"
echo "−−−−−−−−−−−−−−−−−"
while [ "$count" −le $MAXCOUNT ] # Generate 10 ($MAXCOUNT) random integers.
do
number=$RANDOM
echo $number
let "count += 1" # Increment count.
done
echo "−−−−−−−−−−−−−−−−−"
# If you need a random int within a certain range, use the 'modulo' operator.
# This returns the remainder of a division operation.
RANGE=500
echo
number=$RANDOM
let "number %= $RANGE"
echo
FLOOR=200
number=0 #initialize
while [ "$number" −le $FLOOR ]
do
number=$RANDOM
done
echo "Random number greater than $FLOOR −−− $number"
echo
# Combine above two techniques to retrieve random number between two limits.
number=0 #initialize
while [ "$number" −le $FLOOR ]
do
number=$RANDOM
let "number %= $RANGE" # Scales $number down within $RANGE.
done
echo "Random number between $FLOOR and $RANGE −−− $number"
echo
echo
exit 0
#!/bin/bash
# pick−card.sh
Suites="Clubs
Diamonds
Hearts
Spades"
Denominations="2
3
4
5
6
7
8
9
10
Jack
Queen
King
Ace"
# $bozo sh pick−cards.sh
rnumber=$(((RANDOM%(max−min+divisibleBy))/divisibleBy*divisibleBy+min))
Here Bill presents a versatile function that returns a random number between two specified values.
#!/bin/bash
# random−between.sh
# Random number between two specified values.
# Script by Bill Gradwohl, with minor modifications by the document author.
# Used with permission.
randomBetween() {
# Generates a positive or negative random number
#+ between $min and $max
#+ and divisible by $divisibleBy.
# Gives a "reasonably random" distribution of return values.
#
# Bill Gradwohl − Oct 1, 2003
syntax() {
# Function embedded within function.
echo
echo "Syntax: randomBetween [min] [max] [multiple]"
echo
echo "Expects up to 3 passed parameters, but all are completely optional."
echo "min is the minimum value"
echo "max is the maximum value"
echo "multiple specifies that the answer must be a multiple of this value."
echo " i.e. answer must be evenly divisible by this number."
echo
echo "If any value is missing, defaults area supplied as: 0 32767 1"
echo "Successful completion returns 0, unsuccessful completion returns"
echo "function syntax and 1."
echo "The answer is returned in the global variable randomBetweenAnswer"
echo "Negative values for any passed parameter are handled correctly."
}
local min=${1:−0}
local max=${2:−32767}
local divisibleBy=${3:−1}
# Default values assigned, in case parameters not passed to function.
local x
local spread
# Sanity check.
if [ $# −gt 3 −o ${divisibleBy} −eq 0 −o ${min} −eq ${max} ]; then
syntax
return 1
fi
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Now, to do the real work.
# The slight increase will produce the proper distribution for the
#+ end points.
return 0
declare −a answer
minimum=${min}
maximum=${max}
if [ $((minimum/divisibleBy*divisibleBy)) −ne ${minimum} ]; then
if [ ${minimum} −lt 0 ]; then
minimum=$((minimum/divisibleBy*divisibleBy))
else
minimum=$((((minimum/divisibleBy)+1)*divisibleBy))
fi
fi
displacement=$((0−minimum))
for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
answer[i+displacement]=0
done
# Note that we are specifying min and max in reversed order here to
#+ make the function correct for this case.
exit 0
Just how random is $RANDOM? The best way to test this is to write a script that tracks the distribution of
"random" numbers generated by $RANDOM. Let's roll a $RANDOM die a few times . . .
#!/bin/bash
# How random is RANDOM?
RANDOM=$$ # Reseed the random number generator using script process ID.
print_result ()
{
echo
echo "ones = $ones"
echo "twos = $twos"
echo "threes = $threes"
echo "fours = $fours"
echo "fives = $fives"
echo "sixes = $sixes"
echo
}
echo
print_result
exit 0
# The scores should distribute fairly evenly, assuming RANDOM is fairly random.
# With $MAXTHROWS at 600, all should cluster around 100, plus−or−minus 20 or so.
#
# Keep in mind that RANDOM is a pseudorandom generator,
#+ and not a spectacularly good one at that.
# Exercise (easy):
# −−−−−−−−−−−−−−−
# Rewrite this script to flip a coin 1000 times.
# Choices are "HEADS" and "TAILS".
As we have seen in the last example, it is best to "reseed" the RANDOM generator each time it is invoked.
Using the same seed for RANDOM repeats the same series of numbers. [29] (This mirrors the behavior of the
random() function in C.)
#!/bin/bash
# seeding−random.sh: Seeding the RANDOM variable.
random_numbers ()
{
count=0
while [ "$count" −lt "$MAXCOUNT" ]
do
number=$RANDOM
echo −n "$number "
let "count += 1"
echo; echo
echo; echo
echo; echo
echo; echo
# Getting fancy...
SEED=$(head −1 /dev/urandom | od −N 1 | awk '{ print $2 }')
# Pseudo−random output fetched
#+ from /dev/urandom (system pseudo−random device−file),
#+ then converted to line of printable (octal) numbers by "od",
#+ finally "awk" retrieves just one number for SEED.
RANDOM=$SEED
random_numbers
echo; echo
exit 0
The /dev/urandom device−file provides a method of generating much more "random" pseudorandom
numbers than the $RANDOM variable. dd if=/dev/urandom of=targetfile bs=1
count=XX creates a file of well−scattered pseudorandom numbers. However, assigning these numbers
to a variable in a script requires a workaround, such as filtering through od (as in above example and
Example 12−13), or using dd (see Example 12−55), or even piping to md5sum (see Example 33−14).
There are also other ways to generate pseudorandom numbers in a script. Awk provides a convenient
means of doing this.
#!/bin/bash
# random2.sh: Returns a pseudorandom number in the range 0 − 1.
# Uses the awk rand() function.
exit 0
# Exercises:
# −−−−−−−−−
# 3) Same as exercise #2, above, but generate random integers this time.
The date command also lends itself to generating pseudorandom integer sequences.
#!/bin/bash
# Manipulating a variable, C−style, using the ((...)) construct.
echo
echo
########################################################
# Note that, as in C, pre− and post−decrement operators
echo
echo
# −−−−−−−−−−−−−−−−−
# Easter Egg alert!
# −−−−−−−−−−−−−−−−−
# Chet Ramey apparently snuck a bunch of undocumented C−style constructs
#+ into Bash (actually adapted from ksh, pretty much).
# In the Bash docs, Ramey calls ((...)) shell arithmetic,
#+ but it goes far beyond that.
# Sorry, Chet, the secret is now out.
# See also "for" and "while" loops using the ((...)) construct.
exit 0
See also Example 10−12.
10.1. Loops
A loop is a block of code that iterates (repeats) a list of commands as long as the loop control condition is
true.
for loops
During each pass through the loop, arg takes on the value of each successive variable
in the list.
#!/bin/bash
# Listing the planets.
for planet in Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto
do
echo $planet # Each planet on a separate line.
done
echo
for planet in "Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto"
exit 0
Each [list] element may contain multiple parameters. This is useful when
processing parameters in groups. In such cases, use the set command (see Example
11−15) to force parsing of each [list] element and assignment of each component
to the positional parameters.
Example 10−2. for loop with two parameters in each [list] element
#!/bin/bash
# Planets revisited.
# Associate the name of each planet with its distance from the sun.
for planet in "Mercury 36" "Venus 67" "Earth 93" "Mars 142" "Jupiter 483"
do
set −− $planet # Parses variable "planet" and sets positional parameters.
# the "−−" prevents nasty surprises if $planet is null or begins with a dash.
# May need to save original positional parameters, since they get overwritten.
# One way of doing this is to use an array,
# original_params=("$@")
exit 0
A variable may supply the [list] in a for loop.
#!/bin/bash
# fileinfo.sh
FILES="/usr/sbin/accept
/usr/sbin/pwck
/usr/sbin/chroot
/usr/bin/fakefile
/sbin/badblocks
/sbin/ypbind" # List of files you are curious about.
# Threw in a dummy file, /usr/bin/fakefile.
echo
ls −l $file | awk '{ print $9 " file size: " $5 }' # Print 2 fields.
whatis `basename $file` # File info.
# Note that the whatis database needs to have been set up for this to work.
# To do this, as root run /usr/bin/makewhatis.
echo
done
exit 0
If the [list] in a for loop contains wildcards (* and ?) used in filename expansion, then globbing
takes place.
#!/bin/bash
# list−glob.sh: Generating [list] in a for−loop, using "globbing"
echo
for file in *
# ^ Bash performs filename expansion
#+ on expressions that globbing recognizes.
do
ls −l "$file" # Lists all files in $PWD (current directory).
# Recall that the wild card character "*" matches every filename,
#+ however, in "globbing," it doesn't match dot−files.
echo; echo
echo
exit 0
Omitting the in [list] part of a for loop causes the loop to operate on $@ −− the positional
parameters. A particularly clever illustration of this is Example A−16. See also Example 11−16.
#!/bin/bash
for a
do
echo −n "$a "
done
echo
exit 0
It is possible to use command substitution to generate the [list] in a for loop. See also Example
12−49, Example 10−10 and Example 12−43.
Example 10−6. Generating the [list] in a for loop with command substitution
#!/bin/bash
# for−loopcmd.sh: for−loop with [list]
#+ generated by command substitution.
NUMBERS="9 7 3 8 37.53"
echo
exit 0
Here is a somewhat more complex example of using command substitution to create the [list].
#!/bin/bash
# bin−grep.sh: Locates matching strings in a binary file.
E_BADARGS=65
E_NOFILE=66
if [ $# −ne 2 ]
then
echo "Usage: `basename $0` search_string filename"
exit $E_BADARGS
fi
if [ ! −f "$2" ]
then
echo "File \"$2\" does not exist."
exit $E_NOFILE
fi
exit 0
More of the same.
#!/bin/bash
# userlist.sh
PASSWORD_FILE=/etc/passwd
n=1 # User number
# USER #1 = root
# USER #2 = bin
# USER #3 = daemon
# ...
# USER #30 = bozo
exit 0
# Exercise:
# −−−−−−−−
# How is it that an ordinary user (or a script run by same)
#+ can read /etc/passwd?
# Isn't this a security hole? Why or why not?
A final example of the [list] resulting from command substitution.
#!/bin/bash
# findstring.sh:
# Find a particular string in binaries in a specified directory.
directory=/usr/bin/
exit 0
# Exercise (easy):
# −−−−−−−−−−−−−−−
# Convert this script to taking command−line parameters
#+ for $directory and $fstring.
The output of a for loop may be piped to a command or commands.
#!/bin/bash
# symlinks.sh: Lists symbolic links in a directory.
directory=${1−`pwd`}
# Defaults to current working directory,
#+ if not otherwise specified.
# Equivalent to code block below.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# ARGS=1 # Expect one command−line argument.
#
# if [ $# −ne "$ARGS" ] # If not 1 arg...
# then
# directory=`pwd` # current working directory
# else
# directory=$1
# fi
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
for file in "$( find $directory −type l )" # −type l = symbolic links
do
echo "$file"
done | sort # Otherwise file list is unsorted.
# Strictly speaking, a loop isn't really necessary here,
#+ since the output of the "find" command is expanded into a single word.
# However, it's easy to understand and illustrative this way.
exit 0
#!/bin/bash
# symlinks.sh: Lists symbolic links in a directory.
directory=${1−`pwd`}
# Defaults to current working directory,
#+ if not otherwise specified.
for file in "$( find $directory −type l )" # −type l = symbolic links
do
echo "$file"
done | sort >> "$OUTFILE" # stdout of loop
# ^^^^^^^^^^^^^ redirected to save file.
exit 0
There is an alternative syntax to a for loop that will look very familiar to C programmers. This
requires double parentheses.
#!/bin/bash
# Two ways to count up to 10.
echo
# Standard syntax.
for a in 1 2 3 4 5 6 7 8 9 10
do
echo −n "$a "
done
echo; echo
# +==========================================+
LIMIT=10
for ((a=1; a <= LIMIT ; a++)) # Double parentheses, and "LIMIT" with no "$".
do
echo −n "$a "
done # A construct borrowed from 'ksh93'.
echo; echo
# +=========================================================================+
for ((a=1, b=1; a <= LIMIT ; a++, b++)) # The comma chains together operations.
do
echo −n "$a−$b "
done
echo; echo
exit 0
See also Example 26−15, Example 26−16, and Example A−6.
−−−
#!/bin/bash
# Faxing (must have 'fax' installed).
EXPECTED_ARGS=2
E_BADARGS=65
if [ $# −ne $EXPECTED_ARGS ]
# Check for proper no. of command line args.
then
echo "Usage: `basename $0` phone# text−file"
exit $E_BADARGS
fi
if [ ! −f "$2" ]
then
echo "File $2 is not a text file"
exit $E_BADARGS
fi
exit 0
while
This construct tests for a condition at the top of a loop, and keeps looping as long as that condition is
true (returns a 0 exit status). In contrast to a for loop, a while loop finds use in situations where the
number of loop repetitions is not known beforehand.
while [condition]
do
command...
done
As is the case with for loops, placing the do on the same line as the condition test requires a
semicolon.
while [condition] ; do
Note that certain specialized while loops, as, for example, a getopts construct, deviate somewhat from
the standard template given here.
#!/bin/bash
var0=0
LIMIT=10
echo
exit 0
#!/bin/bash
echo
# Equivalent to:
while [ "$var1" != "end" ] # while test "$var1" != "end"
do
echo "Input variable #1 (end to exit) "
read var1 # Not 'read $var1' (why?).
exit 0
A while loop may have multiple conditions. Only the final condition determines when the loop
terminates. This necessitates a slightly different loop syntax, however.
#!/bin/bash
var1=unset
previous=$var1
exit 0
As with a for loop, a while loop may employ C−like syntax by using the double parentheses construct
(see also Example 9−31).
#!/bin/bash
# wh−loopc.sh: Count to 10 in a "while" loop.
LIMIT=10
a=1
echo; echo
# +=================================================================+
echo
exit 0
A while loop may have its stdin redirected to a file by a < at its end.
until [condition−is−true]
do
command...
done
Note that an until loop tests for the terminating condition at the top of the loop, differing from a
similar construct in some programming languages.
As is the case with for loops, placing the do on the same line as the condition test requires a
semicolon.
until [condition−is−true] ; do
#!/bin/bash
END_CONDITION=end
exit 0
#!/bin/bash
# nested−loop.sh: Nested "for" loops.
# ===============================================
# Beginning of inner loop.
for b in 1 2 3 4 5
do
echo "Pass $inner in inner loop."
let "inner+=1" # Increment inner loop counter.
done
# End of inner loop.
# ===============================================
exit 0
See Example 26−11 for an illustration of nested while loops, and Example 26−13 to see a while loop nested
inside an until loop.
break, continue
The break and continue loop control commands [30] correspond exactly to their counterparts in other
programming languages. The break command terminates the loop (breaks out of it), while continue
causes a jump to the next iteration (repetition) of the loop, skipping all the remaining commands in
that particular loop cycle.
echo
echo "Printing Numbers 1 through 20 (but not 3 and 11)."
a=0
echo −n "$a " # This will not execute for 3 and 11.
done
# Exercise:
# Why does loop print up to 20?
echo; echo
##################################################################
a=0
if [ "$a" −gt 2 ]
then
break # Skip entire rest of loop.
fi
exit 0
The break command may optionally take a parameter. A plain break terminates only the innermost
loop in which it is embedded, but a break N breaks out of N levels of loop.
#!/bin/bash
# break−levels.sh: Breaking out of loops.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
for innerloop in 1 2 3 4 5
do
echo −n "$innerloop "
if [ "$innerloop" −eq 3 ]
then
break # Try break 2 to see what happens.
# ("Breaks" out of both inner and outer loops.)
fi
done
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
echo
done
echo
exit 0
The continue command, similar to break, optionally takes a parameter. A plain continue cuts short
the current iteration within its loop and begins the next. A continue N terminates all remaining
iterations at its loop level and continues with the next iteration at the loop, N levels above.
#!/bin/bash
# The "continue N" command, continuing at the Nth level loop.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
for inner in 1 2 3 4 5 6 7 8 9 10 # inner loop
do
if [ "$inner" −eq 7 ]
then
continue 2 # Continue at loop on 2nd level, that is "outer loop".
# Replace above line with a simple "continue"
# to see normal loop behavior.
fi
done
echo; echo
# Exercise:
# Come up with a meaningful use for "continue N" in a script.
exit 0
while true
do
for n in .iso.*
do
[ "$n" = ".iso.opts" ] && continue
beta=${n#.iso.}
[ −r .Iso.$beta ] && continue
[ −r .lock.$beta ] && sleep 10 && continue
lockfile −r0 .lock.$beta || continue
echo −n "$beta: " `date`
run−isotherm $beta
date
ls −alF .Iso.$beta
[ −r .Iso.$beta ] && rm −f .lock.$beta
continue 2
done
break
done
while true
do
for job in {pattern}
do
{job already done or running} && continue
{mark job as running, do job, mark job as done}
continue 2
done
break # Or something like `sleep 600' to avoid termination.
done
# This way the script will stop only when there are no more jobs to do
#+ (including jobs that were added