Adv Bash Scripting Guide
Adv Bash Scripting Guide
<thegrendel@theriver.com>
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 <ligelowbee@yahoo.com>
# 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 during runtime). Through the use
#+ of appropriate lockfiles it can be run on several machines
#+ concurrently without duplication of calculations [which run a couple
#+ of hours in my case, so I really want to avoid this]. Also, as search
#+ always starts again from the beginning, one can encode priorities in
#+ the file names. Of course, one could also do this without `continue 2',
#+ but then one would have to actually check whether or not some job
#+ was done (so that we should immediately look for the next job) or not
#+ (in which case we terminate or sleep for a long time before checking
#+ for a new job).
case "$variable" in
"$condition1" )
command...
;;
"$condition2" )
command...
;;
esac
◊ Quoting the variables is not mandatory, since word splitting does not take
place.
◊ Each test line ends with a right paren ).
◊ Each condition block ends with a double semicolon ;;.
◊ The entire case block terminates with an esac (case spelled backwards).
#!/bin/bash
# Testing ranges of characters.
case "$Keypress" in
[[:lower:]] ) echo "Lowercase letter";;
[[:upper:]] ) echo "Uppercase letter";;
[0−9] ) echo "Digit";;
* ) echo "Punctuation, whitespace, or other";;
esac # Allows ranges of characters in [square brackets],
#+ or POSIX ranges in [[double square brackets.
# Exercise:
# −−−−−−−−
# As the script stands, it accepts a single keystroke, then terminates.
# Change the script so it accepts repeated input,
#+ reports on each keystroke, and terminates only when "X" is hit.
# Hint: enclose everything in a "while" loop.
exit 0
#!/bin/bash
read person
case "$person" in
# Note variable is quoted.
"E" | "e" )
# Accept upper or lowercase input.
echo
echo "Roland Evans"
echo "4321 Floppy Dr."
echo "Hardscrabble, CO 80753"
echo "(303) 734−9874"
echo "(303) 734−9892 fax"
echo "revans@zzy.net"
echo "Business partner & old friend"
;;
# Note double semicolon to terminate each option.
"J" | "j" )
echo
echo "Mildred Jones"
echo "249 E. 7th St., Apt. 19"
echo "New York, NY 10009"
echo "(212) 533−2814"
echo "(212) 533−9972 fax"
echo "milliej@loisaida.com"
echo "Ex−girlfriend"
echo "Birthday: Feb. 11"
;;
* )
esac
echo
# Exercise:
# −−−−−−−−
# Change the script so it accepts multiple inputs,
#+ instead of terminating after displaying just one address.
exit 0
An exceptionally clever use of case involves testing for command−line parameters.
#! /bin/bash
case "$1" in
"") echo "Usage: ${0##*/} <filename>"; exit $E_PARAM;; # No command−line parameters,
# or first parameter empty.
# Note that ${0##*/} is ${var##pattern} param substitution. Net result is $0.
#! /bin/bash
exit 0
A case construct can filter strings for globbing patterns.
#!/bin/bash
# match−string.sh: simple string matching
match_string ()
{
MATCH=0
NOMATCH=90
PARAMS=2 # Function requires 2 arguments.
BAD_PARAMS=91
case "$1" in
"$2") return $MATCH;;
* ) return $NOMATCH;;
esac
a=one
b=two
c=three
d=two
match_string $a $b # no match
echo $? # 90
match_string $b $d # match
echo $? # 0
exit 0
#!/bin/bash
# isalpha.sh: Using a "case" structure to filter a string.
SUCCESS=0
FAILURE=−1
case "$1" in
[a−zA−Z]*) return $SUCCESS;; # Begins with a letter?
* ) return $FAILURE;;
esac
} # Compare this with "isalpha ()" function in C.
case $1 in
*[!a−zA−Z]*|"") return $FAILURE;;
*) return $SUCCESS;;
esac
}
case $1 in
*[!0−9]*|"") return $FAILURE;;
*) return $SUCCESS;;
esac
}
echo
echo
a=23skidoo
b=H3llo
c=−What?
d=What?
e=`echo $b` # Command substitution.
f=AbcDef
g=27234
h=27a34
i=27.34
check_var $a
check_var $b
check_var $c
check_var $d
check_var $e
check_var $f
check_var # No argument passed, so what happens?
#
digit_check $g
digit_check $h
digit_check $i
# Exercise:
# −−−−−−−−
# Write an 'isfloat ()' function that tests for floating point numbers.
# Hint: The function duplicates 'isdigit ()',
#+ but adds a test for a mandatory decimal point.
select
The select construct, adopted from the Korn Shell, is yet another tool for building menus.
This prompts the user to enter one of the choices presented in the variable list. Note that select uses
the PS3 prompt (#? ) by default, but that this may be changed.
#!/bin/bash
echo
exit 0
If in list is omitted, then select uses the list of command line arguments ($@) passed to the script
or to the function in which the select construct is embedded.
#!/bin/bash
echo
choice_of()
{
select vegetable
# [in list] omitted, so 'select' uses arguments passed to function.
do
echo
echo "Your favorite veggie is $vegetable."
echo "Yuck!"
echo
break
done
}
exit 0
See also Example 34−3.
When a command or the shell itself initiates (or spawns) a new subprocess to carry out a task, this is called
forking. This new process is the child, and the process that forked it off is the parent. While the child
process is doing its work, the parent process is still executing.
Note that while a parent process gets the process ID of the child process, and can thus pass arguments to it,
the reverse is not true. This can create problems that are subtle and hard to track down.
#!/bin/bash
# spawn.sh
sleep 1 # Wait.
sh $0 # Play it again, Sam.
# Note:
# −−−−
# Be careful not to run this script too long.
# It will eventually eat up too many system resources.
I/O
echo
prints (to stdout) an expression or variable (see Example 4−1).
echo Hello
echo $a
An echo requires the −e option to print escaped characters. See Example 5−2.
Normally, each echo command prints a terminal newline, but the −n option suppresses this.
See also Example 12−19, Example 12−3, Example 12−42, and Example 12−43.
Be aware that echo `command` deletes any linefeeds that the output of command generates.
The $IFS (internal field separator) variable normally contains \n (linefeed) as one of its set of
whitespace characters. Bash therefore splits the output of command at linefeeds into arguments to
echo. Then echo outputs these arguments, separated by spaces.
bash$ ls −l /usr/share/apps/kjezz/sounds
−rw−r−−r−− 1 root root 1407 Nov 7 2000 reflect.au
−rw−r−−r−− 1 root root 362 Nov 7 2000 seconds.au
# Embedding a linefeed?
echo "Why doesn't this string \n split on two lines?"
# Doesn't split.
echo
echo
echo
echo "−−−−−−−−−−−−−−−"
echo
echo
echo
echo "−−−−−−−−−−−−−−−"
echo
echo
echo $string1
# Yet another line of text containing a linefeed (maybe).
# ^
# Linefeed becomes a space.
This command is a shell builtin, and not the same as /bin/echo, although its
behavior is similar.
printf
The printf, formatted print, command is an enhanced echo. It is a limited variant of the C language
printf() library function, and its syntax is somewhat different.
This is the Bash builtin version of the /bin/printf or /usr/bin/printf command. See the
printf manpage (of the system command) for in−depth coverage.
#!/bin/bash
# printf demo
PI=3.14159265358979
DecimalConstant=31373
Message1="Greetings,"
Message2="Earthling."
echo
echo
# ==========================================#
# Simulation of C function, sprintf().
# Loading a variable with a formatted string.
echo
exit 0
Formatting error messages is a useful application of printf
E_BADDIR=65
var=nonexistent_directory
error()
{
printf "$@" >&2
# Formats positional params passed, and sends them to stderr.
echo
exit $E_BADDIR
}
#!/bin/bash
# "Reading" variables.
read var1
# Note no '$' in front of var1, since it is being set.
echo
exit 0
A read without an associated variable assigns its input to the dedicated variable $REPLY.
#!/bin/bash
# read−novar.sh
echo
# −−−−−−−−−−−−−−−−−−−−−−−−−− #
echo −n "Enter a value: "
read var
echo "\"var\" = "$var""
# Everything as expected here.
# −−−−−−−−−−−−−−−−−−−−−−−−−− #
echo
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− #
echo −n "Enter another value: "
read # No variable supplied for 'read', therefore...
#+ Input to 'read' assigned to default variable, $REPLY.
var="$REPLY"
echo "\"var\" = "$var""
# This is equivalent to the first code block.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− #
echo
#!/bin/bash
echo
echo; echo
echo
exit 0
The read command has some interesting options that permit echoing a prompt and even reading
keystrokes without hitting ENTER.
# Using these options is tricky, since they need to be in the correct order.
The −n option to read also allows detection of the arrow keys and certain of the other unusual keys.
#!/bin/bash
# arrow−detect.sh: Detects the arrow keys, and a few more.
# Thank you, Sandro Magi, for showing me how.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Character codes generated by the keypresses.
arrowup='\[A'
arrowdown='\[B'
arrowrt='\[C'
arrowleft='\[D'
insert='\[2'
delete='\[3'
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
SUCCESS=0
OTHER=65
exit $OTHER
# Exercises:
# −−−−−−−−−
# 1) Simplify this script by rewriting the multiple "if" tests
#+ as a 'case' construct.
# 2) Add detection of the "Home," "End," "PgUp," and "PgDn" keys.
The −n option to read will not detect the ENTER (newline) key.
The −t option to read permits timed input (see Example 9−4).
The read command may also "read" its variable value from a file redirected to stdin. If the file
contains more than one line, only the first line is assigned to the variable. If read has more than one
parameter, then each of these variables gets assigned a successive whitespace−delineated string.
Caution!
#!/bin/bash
echo "−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−"
echo "−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−"
echo
echo "\$IFS still $IFS"
exit 0
#!/bin/sh
# readpipe.sh
# This example contributed by Bjon Eriksson.
last="(null)"
cat $0 |
while read line
do
echo "{$line}"
last=$line
done
printf "\nAll done, last:$last\n"
#############################################
./readpipe.sh
{#!/bin/sh}
{last="(null)"}
{cat $0 |}
{while read line}
{do}
{echo "{$line}"}
{last=$line}
{done}
{printf "nAll done, last:$lastn"}
The variable (last) is set within the subshell but unset outside.
The gendiff script, usually found in /usr/bin on many Linux distros, pipes the
output of find to a while read construct.
Filesystem
cd
The familiar cd change directory command finds use in scripts where execution of a command
requires being in a specified directory.
The cd command does not function as expected when presented with two forward
slashes.
bash$ cd //
bash$ pwd
//
The output should, of course, be /. This is a problem both from the command line and
in a script.
pwd
Print Working Directory. This gives the user's (or script's) current directory (see Example 11−9). The
effect is identical to reading the value of the builtin variable $PWD.
pushd, popd, dirs
This command set is a mechanism for bookmarking working directories, a means of moving back and
forth through directories in an orderly manner. A pushdown stack is used to keep track of directory
names. Options allow various manipulations of the directory stack.
pushd dir−name pushes the path dir−name onto the directory stack and simultaneously
changes the current working directory to dir−name
popd removes (pops) the top directory path name off the directory stack and simultaneously changes
the current working directory to that directory popped from the stack.
dirs lists the contents of the directory stack (compare this with the $DIRSTACK variable). A
successful pushd or popd will automatically invoke dirs.
Scripts that require various changes to the current working directory without hard−coding the
directory name changes can make good use of these commands. Note that the implicit $DIRSTACK
array variable, accessible from within a script, holds the contents of the directory stack.
#!/bin/bash
dir1=/usr/local
dir2=/var/spool
pushd $dir1
# Will do an automatic 'dirs' (list directory stack to stdout).
echo "Now in directory `pwd`." # Uses back−quoted 'pwd'.
exit 0
Variables
let
The let command carries out arithmetic operations on variables. In many cases, it functions as a less
complex version of expr.
#!/bin/bash
echo
echo
exit 0
eval
eval arg1 [arg2] ... [argN]
Combines the arguments in an expression or list of expressions and evaluates them. Any variables
contained within the expression are expanded. The result translates into a command. This can be
useful for code generation from the command line or within a script.
bash$ process=xterm
bash$ show_process="eval ps ax | grep $process"
bash$ $show_process
1867 tty1 S 0:02 xterm
2779 tty1 S 0:00 xterm
2886 pts/1 S 0:00 grep xterm
#!/bin/bash
echo; echo
echo
echo "==========================================================="
echo
for i in 1 2 3 4 5; do
eval value=$i
# value=$i has same effect. The "eval" is not necessary here.
echo
echo "−−−"
echo
for i in ls df; do
value=eval $i
# value=$i has an entirely different effect here.
# The "eval" evaluates the commands "ls" and "df" . . .
# The terms "ls" and "df" have a meta−meaning,
#+ since they are interpreted as commands,
#+ rather than just character strings.
echo $value
done
exit 0
#!/bin/bash
# Killing ppp to force a log−off.
exit 0
# Exercises:
# −−−−−−−−−
# 1) Have script check whether root user is invoking it.
# 2) Do a check on whether the process to be killed
#+ is actually running before attempting to kill it.
# 3) Write an alternate version of this script based on 'fuser':
#+ if [ fuser −s /dev/modem ]; then . . .
#!/bin/bash
# A version of "rot13" using 'eval'.
# Compare to "rot13.sh" example.
exit 0
Rory Winston contributed the following instance of how useful eval can be.
However:
$export WEBROOT_PATH=/usr/local/webroot
$eval sed 's%\<WEBROOT_PATH\>%$WEBROOT_PATH%' < test.pl > out
# ====
The eval command can be risky, and normally should be avoided when there exists a
reasonable alternative. An eval $COMMANDS executes the contents of COMMANDS,
which may contain such unpleasant surprises as rm −rf *. Running an eval on
unfamiliar code written by persons unknown is living dangerously.
set
The set command changes the value of internal script variables. One use for this is to toggle option
flags which help determine the behavior of the script. Another application for it is to reset the
positional parameters that a script sees as the result of a command (set `command`). The script
can then parse the fields of the command output.
# script "set−test"
echo
echo "Positional parameters before set \`uname −a\` :"
echo "Command−line argument #1 = $1"
echo "Command−line argument #2 = $2"
echo "Command−line argument #3 = $3"
echo $_ # unknown
# Flags set in script.
exit 0
More fun with positional parameters.
#!/bin/bash
# revposparams.sh: Reverse positional parameters.
# Script by Dan Jacobson, with stylistic revisions by document author.
set a\ b c d\ e;
# ^ ^ Spaces escaped
# ^ ^ Spaces not escaped
OIFS=$IFS; IFS=:;
# ^ Saving old IFS and setting new one.
echo
until [ $# −eq 0 ]
do # Step through positional parameters.
echo "### k0 = "$k"" # Before
k=$1:$k; # Append each pos param to loop variable.
# ^
echo "### k = "$k"" # After
echo
shift;
done
# Question:
# Is it necessary to set an new IFS, internal field separator,
#+ in order for this script to work properly?
# What happens if you don't? Try it.
# And, why use the new IFS −− a colon −− in line 17,
#+ to append to the loop variable?
# What is the purpose of this?
exit 0
$ ./revposparams.sh
### k0 =
### k = a b
### k0 = a b
### k = c a b
### k0 = c a b
### k = d e c a b
−
3
−
d e
c
a b
Invoking set without any options or arguments simply lists all the environmental and other variables
that have been initialized.
bash$ set
AUTHORCOPY=/home/bozo/posts
BASH=/bin/bash
BASH_VERSION=$'2.05.8(1)−release'
...
XAUTHORITY=/home/bozo/.Xauthority
_=/etc/bashrc
variable22=abc
variable23=xzy
Using set with the −− option explicitly assigns the contents of a variable to the positional parameters.
When no variable follows the −−, it unsets the positional parameters.
#!/bin/bash
set −− $variable
# Sets positional parameters to the contents of "$variable".
first_param=$1
second_param=$2
shift; shift # Shift past first two positional params.
remaining_params="$*"
echo
echo "first parameter = $first_param" # one
echo "second parameter = $second_param" # two
echo "remaining parameters = $remaining_params" # three four five
echo; echo
# Again.
set −− $variable
first_param=$1
second_param=$2
echo "first parameter = $first_param" # one
echo "second parameter = $second_param" # two
# ======================================================
set −−
# Unsets positional parameters if no variable specified.
first_param=$1
second_param=$2
echo "first parameter = $first_param" # (null value)
echo "second parameter = $second_param" # (null value)
exit 0
See also Example 10−2 and Example 12−51.
unset
The unset command deletes a shell variable, effectively setting it to null. Note that this command
does not affect positional parameters.
bash$
#!/bin/bash
# unset.sh: Unsetting a variable.
variable=hello # Initialized.
echo "variable = $variable"
exit 0
#!/bin/bash
ARGS=2
E_WRONGARGS=65
filename=$1
column_number=$2
export column_number
# Export column number to environment, so it's available for retrieval.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
awkscript='{ total += $ENVIRON["column_number"] }
END { print total }'
# Yes, a variable can hold an awk script.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
exit 0
However, as Greg Keraunen points out, in certain situations this may have a different
effect than setting a variable, then exporting it.
declare, typeset
The declare and typeset commands specify and/or restrict properties of variables.
readonly
Same as declare −r, sets a variable as read−only, or, in effect, as a constant. Attempts to change the
variable fail with an error message. This is the shell analog of the C language const type qualifier.
getopts
This powerful tool parses command−line arguments passed to the script. This is the Bash analog of
the getopt external command and the getopt library function familiar to C programmers. It permits
passing and concatenating multiple options [32] and associated arguments to a script (for example
scriptname −abc −e /usr/local).
The getopts construct uses two implicit variables. $OPTIND is the argument pointer (OPTion INDex)
and $OPTARG (OPTion ARGument) the (optional) argument attached to an option. A colon following
the option name in the declaration tags that option as having an associated argument.
A getopts construct usually comes packaged in a while loop, which processes the options and
arguments one at a time, then increments the implicit $OPTIND variable to step to the next.
1. The arguments passed from the command line to the script must be preceded
by a minus (−). It is the prefixed − that lets getopts recognize command−line
arguments as options. In fact, getopts will not process arguments without the
prefixed −, and will terminate option processing at the first argument
encountered lacking them.
2. The getopts template differs slightly from the standard while loop, in that it
lacks condition brackets.
3. The getopts construct replaces the deprecated getopt external command.
NO_ARGS=0
E_OPTERROR=65
exit 0
Script Behavior
#!/bin/bash
exit 0
File data−file for Example 11−21, above. Must be present in same directory.
variable1=22
variable2=474
variable3=5
variable4=97
print_message ()
{
# Echoes any message passed to it.
if [ −z "$1" ]
then
return 1
# Error, if argument missing.
fi
echo
until [ −z "$1" ]
do
# Step through arguments passed to function.
echo −n "$1"
# Echo args one at a time, suppressing line feeds.
echo −n " "
# Insert spaces between words.
shift
# Next one.
done
echo
return 0
}
If the sourced file is itself an executable script, then it will run, then return control to the script that
called it. A sourced executable script may use a return for this purpose.
#!/bin/bash
# self−source.sh: a script sourcing itself "recursively."
# From "Stupid Script Tricks," Volume II.
echo
# Exercise:
# −−−−−−−−
# Write a script that uses this trick to actually do something useful.
exit
Unconditionally terminates a script. The exit command may optionally take an integer argument,
which is returned to the shell as the exit status of the script. It is good practice to end all but the
simplest scripts with an exit 0, indicating a successful run.
If a script terminates with an exit lacking an argument, the exit status of the script is
the exit status of the last command executed in the script, not counting the exit. This is
equivalent to an exit $?.
exec
This shell builtin replaces the current process with a specified command. Normally, when the shell
encounters a command, it forks off a child process to actually execute the command. Using the exec
builtin, the shell does not fork, and the command exec'ed replaces the shell. When used in a script,
therefore, it forces an exit from the script when the exec'ed command terminates. [33]
#!/bin/bash
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# The following lines never execute.
#!/bin/bash
# self−exec.sh
echo
echo "This line appears ONCE in the script, yet it keeps echoing."
echo "The PID of this instance of the script is still $$."
# Demonstrates that a subshell is not forked off.
sleep 1
exit 0
An exec also serves to reassign file descriptors. For example, exec <zzz−file replaces stdin
with the file zzz−file.
The −exec option to find is not the same as the exec shell builtin.
shopt
This command permits changing shell options on the fly (see Example 24−1 and Example 24−2). It
often appears in the Bash startup files, but also has its uses in scripts. Needs version 2 or later of Bash.
shopt −s cdspell
# Allows minor misspelling of directory names with 'cd'
#!/bin/bash
function1 ()
{
# Inside function1 ().
caller 0 # Tell me about it.
}
# 9 main test.sh
# ^ Line number that the function was called from.
# ^^^^ Invoked from "main" part of script.
# ^^^^^^^ Name of calling script.
Commands
true
A command that returns a successful (zero) exit status, but does nothing else.
# Endless loop
while true # alias for ":"
do
operation−1
operation−2
...
operation−n
# Need a way to break out of loop or script will hang.
# Testing "false"
if false
then
echo "false evaluates \"true\""
else
echo "false evaluates \"false\""
fi
# false evaluates "false"
hash [cmds]
Record the path name of specified commands −− in the shell hash table [34] −− so the shell or script
will not need to search the $PATH on subsequent calls to those commands. When hash is called with
no arguments, it simply lists the commands that have been hashed. The −r option resets the hash
table.
bind
The bind builtin displays or modifies readline [35] key bindings.
help
Gets a short usage summary of a shell builtin. This is the counterpart to whatis, but for builtins.
jobs
Lists the jobs running in the background, giving the job number. Not as useful as ps.
It is all too easy to confuse jobs and processes. Certain builtins, such as kill, disown,
and wait accept either a job number or a process number as an argument. The fg, bg
and jobs commands accept only a job number.
bash $ jobs
[1]+ Running sleep 100 &
"1" is the job number (jobs are maintained by the current shell), and "1384" is the
process number (processes are maintained by the system). To kill this job/process,
either a kill %1 or a kill 1384 works.
Thanks, S.C.
disown
Remove job(s) from the shell's table of active jobs.
fg, bg
The fg command switches a job running in the background into the foreground. The bg command
restarts a suspended job, and runs it in the background. If no job number is specified, then the fg or bg
command acts upon the currently running job.
wait
Stop script execution until all jobs running in background have terminated, or until the job number or
process ID specified as an option terminates. Returns the exit status of waited−for command.
You may use the wait command to prevent a script from exiting before a background job finishes
executing (this would create a dreaded orphan process).
#!/bin/bash
if [ −z "$1" ]
then
echo "Usage: `basename $0` find−string"
exit $E_NOPARAMS
fi
wait
# Don't run the rest of the script until 'updatedb' finished.
# You want the the database updated before looking up the file name.
locate $1
exit 0
Optionally, wait can take a job identifier as an argument, for example, wait%1 or wait $PPID. See
the job id table.
Within a script, running a command in the background with an ampersand (&) may
cause the script to hang until ENTER is hit. This seems to occur with commands that
write to stdout. It can be a major annoyance.
#!/bin/bash
# test.sh
ls −l &
echo "Done."
bash$ ./test.sh
Done.
[bozo@localhost test−scripts]$ total 1
−rwxr−xr−x 1 bozo bozo 34 Oct 11 15:09 test.sh
_
#!/bin/bash
# test.sh
ls −l &
echo "Done."
wait
bash$ ./test.sh
Done.
[bozo@localhost test−scripts]$ total 1
−rwxr−xr−x 1 bozo bozo 34 Oct 11 15:09 test.sh
Redirecting the output of the command to a file or even to /dev/null also takes
care of this problem.
suspend
This has a similar effect to Control−Z, but it suspends the shell (the shell's parent process should
resume it at an appropriate time).
logout
Exit a login shell, optionally specifying an exit status.
times
Gives statistics on the system time used in executing commands, in the following form:
0m0.020s 0m0.020s
This capability is of very limited value, since it is uncommon to profile and benchmark shell scripts.
kill
Forcibly terminate a process by sending it an appropriate terminate signal (see Example 13−6).
#!/bin/bash
# self−destruct.sh
exit 0
kill −l lists all the signals. A kill −9 is a "sure kill", which will usually
terminate a process that stubbornly refuses to die with a plain kill. Sometimes, a kill
−15 works. A "zombie process," that is, a child process that has terminated, but that
the parent process has not (yet) killed, cannot be killed by a logged−on user −− you
can't kill something that is already dead −− but init will generally clean it up sooner or
later.
command
The command COMMAND directive disables aliases and functions for the command
"COMMAND".
This is one of three shell directives that effect script command processing. The others
are builtin and enable.
builtin
Invoking builtin BUILTIN_COMMAND runs the command "BUILTIN_COMMAND" as a shell
builtin, temporarily disabling both functions and external system commands with the same name.
enable
This either enables or disables a shell builtin command. As an example, enable −n kill disables the
shell builtin kill, so that when Bash subsequently encounters kill, it invokes /bin/kill.
The −a option to enable lists all the shell builtins, indicating whether or not they are enabled. The −f
filename option lets enable load a builtin as a shared library (DLL) module from a properly
compiled object file. [36].
autoload
This is a port to Bash of the ksh autoloader. With autoload in place, a function with an "autoload"
declaration will load from an external file at its first invocation. [37] This saves system resources.
Notation Meaning
%N Job number [N]
%S Invocation (command line) of job begins with string S
%?S Invocation (command line) of job contains within it string S
%% "current" job (last job stopped in foreground or started in background)
%+ "current" job (last job stopped in foreground or started in background)
%− Last job
$! Last background process
Standard UNIX commands make shell scripts more versatile. The power of scripts comes from coupling
system commands and shell directives with simple programming constructs.
ls
The basic file "list" command. It is all too easy to underestimate the power of this humble command.
For example, using the −R, recursive option, ls provides a tree−like listing of a directory structure.
Other useful options are −S, sort listing by file size, −t, sort by file modification time, and −i, show
file inodes (see Example 12−4).
Example 12−1. Using ls to create a table of contents for burning a CDR disk
#!/bin/bash
# ex40.sh (burn−cd.sh)
# Script to automate burning a CDR.
if [ −z "$1" ]
then
IMAGE_DIRECTORY=$DEFAULTDIR
# Default directory, if not specified on command line.
else
IMAGE_DIRECTORY=$1
fi
exit $?
cat, tac
cat, an acronym for concatenate, lists a file to stdout. When combined with redirection (> or >>), it
is commonly used to concatenate files.
# Uses of 'cat'
cat filename # Lists the file.
cat file.1 file.2 file.3 > file.123 # Combines three files into one.
The −n option to cat inserts consecutive numbers before all lines of the target file(s). The −b option
numbers only the non−blank lines. The −v option echoes nonprintable characters, using ^ notation.
The −s option squeezes multiple consecutive blank lines into a single blank line.
In a pipe, it may be more efficient to redirect the stdin to a file, rather than to cat the
file.
tr a−z A−Z < filename # Same effect, but starts one less process,
#+ and also dispenses with the pipe.
tac, is the inverse of cat, listing a file backwards from its end.
rev
reverses each line of a file, and outputs to stdout. This does not have the same effect as tac, as it
preserves the order of the lines, but flips each one around.
cp
This is the file copy command. cp file1 file2 copies file1 to file2, overwriting file2 if
it already exists (see Example 12−6).
Particularly useful are the −a archive flag (for copying an entire directory tree), the
−u update flag, and the −r and −R recursive flags.
cp −u source_dir/* dest_dir
# "Synchronize" dest_dir to source_dir
#+ by copying over all newer and not previously existing files.
mv
This is the file move command. It is equivalent to a combination of cp and rm. It may be used to
move multiple files to a directory, or even to rename a directory. For some examples of using mv in a
script, see Example 9−19 and Example A−2.
When used in a non−interactive script, mv takes the −f (force) option to bypass user
input.
rm
Delete (remove) a file or files. The −f option forces removal of even readonly files, and is useful for
bypassing user input in a script.
The rm command will, by itself, fail to remove filenames beginning with a dash.
bash$ rm −badname
rm: invalid option −− b
Try `rm −−help' for more information.
One way to accomplish this is to preface the filename to be removed with a
dot−slash .
bash$ rm ./−badname
Another method is to precede the filename with a " −− ".
bash$ rm −− −badname
When used with the recursive flag −r, this command removes files all the way down
the directory tree from the current directory. A careless rm −rf * can wipe out a big
chunk of a directory structure.
rmdir
Remove directory. The directory must be empty of all files −− including "invisible" dotfiles [38] −−
for this command to succeed.
mkdir
Make directory, creates a new directory. For example, mkdir −p
project/programs/December creates the named directory. The −p option automatically
creates any necessary parent directories.
chmod
Changes the attributes of an existing file (see Example 11−12).
One particularly interesting chattr option is i. A chattr +i filename marks the file as immutable.
The file cannot be modified, linked to, or deleted , not even by root. This file attribute can be set or
removed only by root. In a similar fashion, the a option marks the file as append only.
root# rm file1.txt
If a file has the s (secure) attribute set, then when it is deleted its block is zeroed out on the disk.
If a file has the u (undelete) attribute set, then when it is deleted, its contents can still be retrieved
(undeleted).
If a file has the c (compress) attribute set, then it will automatically be compressed on writes to disk,
and uncompressed on reads.
The file attributes set with chattr do not show in a file listing (ls −l).
ln
Creates links to pre−existings files. A "link" is a reference to a file, an alternate name for it. The ln
command permits referencing the linked file by more than one name and is a superior alternative to
aliasing (see Example 4−6).
The ln creates only a reference, a pointer to the file only a few bytes in size.
The ln command is most often used with the −s, symbolic or "soft" link flag. Advantages of using the
−s flag are that it permits linking across file systems or to directories.
The syntax of the command is a bit tricky. For example: ln −s oldfile newfile links the
previously existing oldfile to the newly created link, newfile.
If a file named newfile has previously existed, an error message will result.
Both of these [types of links] provide a certain measure of dual reference −− if you edit the contents
of the file using any name, your changes will affect both the original name and either a hard or soft
new name. The differences between them occurs when you work at a higher level. The advantage of
a hard link is that the new name is totally independent of the old name −− if you remove or rename
the old name, that does not affect the hard link, which continues to point to the data while it would
leave a soft link hanging pointing to the old name which is no longer there. The advantage of a soft
link is that it can refer to a different file system (since it is just a reference to a file name, not to
actual data). And, unlike a hard link, a symbolic link can refer to a directory.
Links give the ability to invoke a script (or any other type of executable) with multiple names, and
having that script behave according to how it was invoked.
#!/bin/bash
# hello.sh: Saying "hello" or "goodbye"
#+ depending on how script is invoked.
HELLO_CALL=65
GOODBYE_CALL=66
if [ $0 = "./goodbye" ]
then
echo "Good−bye!"
# Some other goodbye−type commands, as appropriate.
exit $GOODBYE_CALL
fi
echo "Hello!"
# Some other hello−type commands, as appropriate.
exit $HELLO_CALL
man, info
These commands access the manual and information pages on system commands and installed
utilities. When available, the info pages usually contain a more detailed description than do the man
pages.
find
−exec COMMAND \;
Carries out COMMAND on each file that find matches. The command sequence terminates with ; (the
";" is escaped to make certain the shell passes it to find literally, without interpreting it as a special
character).
If COMMAND contains {}, then find substitutes the full path name of the selected file for "{}".
DIR=/home/bozo/junk_files
find "$DIR" −type f −atime +5 −exec rm {} \;
# ^^
# Curly brackets are placeholder for the path name output by "find."
#
# Deletes all files in "/home/bozo/junk_files"
#+ that have not been accessed in at least 5 days.
#
# "−type filetype", where
# f = regular file
# d = directory, etc.
# (The 'find' manpage has a complete listing.)
# Perhaps by:
The −exec option to find should not be confused with the exec shell builtin.
Example 12−3. Badname, eliminate file names in current directory containing bad characters
and whitespace.
#!/bin/bash
# badname.sh
# Delete filenames in current directory containing bad characters.
for filename in *
do
badname=`echo "$filename" | sed −n /[\+\{\;\"\\\=\?~\(\)\<\>\&\*\|\$]/p`
# badname=`echo "$filename" | sed −n '/[+{;"\=?~()<>&*|$]/p'` also works.
# Deletes files containing these nasties: + { ; " \ = ? ~ ( ) < > & * | $
#
rm $badname 2>/dev/null
# ^^^^^^^^^^^ Error messages deep−sixed.
done
exit 0
#−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Commands below this line will not execute because of "exit" command.
#!/bin/bash
# idelete.sh: Deleting a file by its inode number.
if [ $# −ne "$ARGCOUNT" ]
then
echo "Usage: `basename $0` filename"
exit $E_WRONGARGS
fi
if [ ! −e "$1" ]
then
echo "File \""$1"\" does not exist."
exit $E_FILE_NOT_EXIST
fi
echo; echo −n "Are you absolutely sure you want to delete \"$1\" (y/n)? "
# The '−v' option to 'rm' also asks this.
read answer
case "$answer" in
[nN]) echo "Changed your mind, huh?"
exit $E_CHANGED_MIND
;;
*) echo "Deleting file \"$1\".";;
esac
exit 0
See Example 12−27, Example 3−4, and Example 10−9 for scripts using find. Its manpage provides
more detail on this complex and powerful command.
xargs
A filter for feeding arguments to a command, and also a tool for assembling the commands
themselves. It breaks a data stream into small enough chunks for filters and commands to process.
Consider it as a powerful replacement for backquotes. In situations where command substitution fails
with a too many arguments error, substituting xargs often works. [39] Normally, xargs reads from
stdin or from a pipe, but it can also be given the output of a file.
The default command for xargs is echo. This means that input piped to xargs may have linefeeds and
other whitespace characters stripped out.
bash$ ls −l
total 0
−rw−rw−r−− 1 bozo bozo 0 Jan 29 23:58 file1
−rw−rw−r−− 1 bozo bozo 0 Jan 29 23:58 file2
bash$ ls −l | xargs
total 0 −rw−rw−r−− 1 bozo bozo 0 Jan 29 23:58 file1 −rw−rw−r−− 1 bozo bozo 0 Jan 29 23:58
ls | xargs −p −l gzip gzips every file in current directory, one at a time, prompting before
each operation.
Another useful option is −0, in combination with find −print0 or grep −lZ. This
allows handling arguments containing whitespace or quotes.
Either of the above will remove any file containing "GUI". (Thanks, S.C.)
#!/bin/bash
LINES=5
exit 0
# Note:
# −−−−
# As Frank Wang points out,
#+ unmatched quotes (either single or double quotes) in the source file
#+ may give xargs indigestion.
#
# He suggests the following substitution for line 15:
# tail −$LINES /var/log/messages | tr −d "\"'" | xargs | fmt −s >>logfile
# Exercise:
# −−−−−−−−
# Modify this script to track changes in /var/log/messages at intervals
#+ of 20 minutes.
# Hint: Use the "watch" command.
As in find, a curly bracket pair serves as a placeholder for replacement text.
E_NOARGS=65
ls . | xargs −i −t cp ./{} $1
# ^^ ^^ ^^
# −t is "verbose" (output command line to stderr) option.
# −i is "replace strings" option.
# {} is a placeholder for output text.
# This is similar to the use of a curly bracket pair in "find."
#
# List the files in current directory (ls .),
#+ pass the output of "ls" as arguments to "xargs" (−i −t options),
#+ then copy (cp) these arguments ({}) to new directory ($1).
#
# The net result is the exact equivalent of
#+ cp * $1
#+ unless any of the filenames has embedded "whitespace" characters.
exit 0
#!/bin/bash
# kill−byname.sh: Killing processes by name.
# Compare this script with kill−process.sh.
# For instance,
#+ try "./kill−byname.sh xterm" −−
#+ and watch all the xterms on your desktop disappear.
# Warning:
# −−−−−−−
# This is a fairly dangerous script.
# Running it carelessly (especially as root)
#+ can cause data loss and other undesirable effects.
E_BADARGS=66
PROCESS_NAME="$1"
ps ax | grep "$PROCESS_NAME" | awk '{print $1}' | xargs −i kill {} 2&>/dev/null
# ^^ ^^
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
exit $?
#!/bin/bash
# wf2.sh: Crude word frequency analysis on a text file.
if [ $# −ne "$ARGS" ]
# Correct number of arguments passed to script?
then
echo "Usage: `basename $0` filename"
exit $E_BADARGS
fi
########################################################
cat "$1" | xargs −n1 | \
# List the file, one word per line.
tr A−Z a−z | \
# Shift characters to lowercase.
sed −e 's/\.//g' −e 's/\,//g' −e 's/ /\
/g' | \
# Filter out periods and commas, and
#+ change space between words to linefeed,
sort | uniq −c | sort −nr
# Finally prefix occurrence count and sort numerically.
########################################################
exit 0
expr
All−purpose expression evaluator: Concatenates and evaluates the arguments according to the
operation given (arguments must be separated by spaces). Operations may be arithmetic, comparison,
string, or logical.
expr 3 + 5
The multiplication operator must be escaped when used in an arithmetic expression with
expr.
y=`expr $y + 1`
Increment a variable, with the same effect as let y=y+1 and y=$(($y+1)). This is an
example of arithmetic expansion.
z=`expr substr $string $position $length`
Extract substring of $length characters, starting at $position.
#!/bin/bash
echo
# Arithmetic Operators
# −−−−−−−−−− −−−−−−−−−
a=`expr $a + 1`
echo
echo "a + 1 = $a"
echo "(incrementing a variable)"
a=`expr 5 % 3`
# modulo
echo
echo "5 mod 3 = $a"
echo
echo
# Logical Operators
# −−−−−−− −−−−−−−−−
x=24
a=3
b=`expr $a \> 10`
echo 'b=`expr $a \> 10`, therefore...'
echo "If a > 10, b = 0 (false)"
echo "b = $b" # 0 ( 3 ! −gt 10 )
echo
b=`expr $a \<= 3`
echo "If a <= 3, b = 1 (true)"
echo "b = $b" # 1 ( 3 −le 3 )
# There is also a "\>=" operator (greater than or equal to).
echo
echo
# String Operators
# −−−−−− −−−−−−−−−
a=1234zipper43231
echo "The string being operated upon is \"$a\"."
echo
exit 0
The : operator can substitute for match. For example, b=`expr $a : [0−9]*` is the
exact equivalent of b=`expr match $a [0−9]*` in the above listing.
#!/bin/bash
echo
echo "String operations using \"expr \$string : \" construct"
echo "==================================================="
echo
a=1234zipper5FLIPPER43231
# ***************************
#+ Escaped parentheses
#+ match a substring
# ***************************
# If no escaped parentheses...
#+ then 'expr' converts the string operand to an integer.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− #
echo
echo "The digits at the beginning of \"$a\" are `expr "$a" : '\([0−9]*\)'`."
# == ==
echo "The first 7 characters of \"$a\" are `expr "$a" : '\(.......\)'`."
# ===== == ==
# Again, escaped parentheses force a substring match.
#
echo "The last 7 characters of \"$a\" are `expr "$a" : '.*\(.......\)'`."
# ==== end of string operator ^^
# (actually means skip over one or more of any characters until specified
#+ substring)
echo
exit 0
The above script illustrates how expr uses the escaped parentheses −− \( ... \) −− grouping operator in
tandem with regular expression parsing to match a substring. Here is a another example, this time from "real
life."
date
Simply invoked, date prints the date and time to stdout. Where this command gets interesting is in
its formatting and parsing options.
#!/bin/bash
# Exercising the 'date' command
echo "The number of days since the year's beginning is `date +%j`."
# Needs a leading '+' to invoke formatting.
# %j gives day of year.
prefix=temp
suffix=$(date +%s) # The "+%s" option to 'date' is GNU−specific.
filename=$prefix.$suffix
echo $filename
# It's great for creating "unique" temp filenames,
#+ even better than using $$.
exit 0
The −u option gives the UTC (Universal Coordinated Time).
bash$ date
Fri Mar 29 21:07:39 MST 2002
bash$ date −u
Sat Mar 30 04:07:42 UTC 2002
The date command has quite a number of output options. For example %N gives the nanosecond
portion of the current time. One interesting use for this is to generate six−digit random integers.
date +%j
# Echoes day of the year (days elapsed since January 1).
date +%k%M
# Echoes hour and minute in 24−hour format, as a single digit string.
time
Outputs very verbose timing statistics for executing a command.
As of version 2.0 of Bash, time became a shell reserved word, with slightly altered
behavior in a pipeline.
touch
Utility for updating access/modification times of a file to current system time or other specified time,
but also useful for creating a new file. The command touch zzz will create a new file of zero
length, named zzz, assuming that zzz did not previously exist. Time−stamping empty files in this
way is useful for storing date information, for example in keeping track of modification times on a
project.
at 2pm January 15 prompts for a set of commands to execute at that time. These commands
should be shell−script compatible, since, for all practical purposes, the user is typing in an executable
Using either the −f option or input redirection (<), at reads a command list from a file. This file is an
executable shell script, though it should, of course, be noninteractive. Particularly clever is including
the run−parts command in the file to execute a different set of scripts.
batch
The batch job control command is similar to at, but it runs a command list when the system load
drops below .8. Like at, it can read commands from a file with the −f option.
cal
Prints a neatly formatted monthly calendar to stdout. Will do current year or a large range of past
and future years.
sleep
This is the shell equivalent of a wait loop. It pauses for a specified number of seconds, doing nothing.
It can be useful for timing or in processes running in the background, checking for a specific event
every so often (polling), as in Example 29−6.
The watch command may be a better choice than sleep for running commands at
timed intervals.
usleep
Microsleep (the "u" may be read as the Greek "mu", or micro− prefix). This is the same as sleep,
above, but "sleeps" in microsecond intervals. It can be used for fine−grain timing, or for polling an
ongoing process at very frequent intervals.
The usleep command does not provide particularly accurate timing, and is therefore
unsuitable for critical timing loops.
hwclock, clock
The hwclock command accesses or adjusts the machine's hardware clock. Some options require root
privileges. The /etc/rc.d/rc.sysinit startup file uses hwclock to set the system time from
the hardware clock at bootup.
sort
File sorter, often used as a filter in a pipe. This command sorts a text stream or file forwards or
backwards, or according to various keys or character positions. Using the −m option, it merges
presorted input files. The info page lists its many capabilities and options. See Example 10−9,
Example 10−10, and Example A−8.
tsort
Topological sort, reading in pairs of whitespace−separated strings and sorting according to input
patterns.
uniq
This filter removes duplicate lines from a sorted file. It is often seen in a pipe coupled with sort.
The sort INPUTFILE | uniq −c | sort −nr command string produces a frequency of
occurrence listing on the INPUTFILE file (the −nr options to sort cause a reverse numerical sort).
This template finds use in analysis of log files and dictionary lists, and wherever the lexical structure
of a document needs to be examined.
#!/bin/bash
# wf.sh: Crude word frequency analysis on a text file.
# This is a more efficient version of the "wf2.sh" script.
########################################################
# main ()
sed −e 's/\.//g' −e 's/\,//g' −e 's/ /\
/g' "$1" | tr 'A−Z' 'a−z' | sort | uniq −c | sort −nr
# =========================
# Frequency of occurrence
exit 0
# Exercises:
# −−−−−−−−−
# 1) Add 'sed' commands to filter out other punctuation,
#+ such as semicolons.
# 2) Modify the script to also filter out multiple spaces and
# other whitespace.
expand, unexpand
The expand filter converts tabs to spaces. It is often used in a pipe.
The unexpand filter converts spaces to tabs. This reverses the effect of expand.
cut
A tool for extracting fields from files. It is similar to the print $N command set in awk, but more
limited. It may be simpler to use cut in a script than awk. Particularly important are the −d (delimiter)
and −f (field specifier) options.
FILENAME=/etc/passwd
The join command operates on exactly two files, but pastes together only those lines with a common
tagged field (usually a numerical label), and writes the result to stdout. The files to be joined
should be sorted according to the tagged field for the matchups to work properly.
File: 1.data
100 Shoes
200 Laces
300 Socks
File: 2.data
100 $40.00
200 $1.00
300 $2.00
#!/bin/bash
# script−detector.sh: Detects scripts within a directory.
exit 0
# Exercises:
# −−−−−−−−−
# 1) Modify this script to take as an optional argument
#+ the directory to scan for scripts
#+ (rather than just the current working directory).
#
# 2) As it stands, this script gives "false positives" for
#+ Perl, awk, and other scripting language scripts.
# Correct this.
#!/bin/bash
# rnd.sh: Outputs a 10−digit random number
# =================================================================== #
# Analysis
# −−−−−−−−
# head:
# −c4 option takes first 4 bytes.
# od:
# −N4 option limits output to 4 bytes.
# −tu4 option selects unsigned decimal format for output.
# sed:
# −n option, in combination with "p" flag to the "s" command,
# outputs only matched lines.
# sed is now ready to continue reading its input. (Note that before
#+ continuing, if −n option had not been passed, sed would have printed
#+ the line once again).
# Now, sed reads the remainder of the characters, and finds the end of the file.
# It is now ready to process its 2nd line (which is also numbered '$' as
# it's the last one).
# It sees it is not matched by any <range>, so its job is done.
# range action
# nothing (matches line) s/.* //
# nothing (matches line) q (quit)
# =================================================================== #
exit 0
See also Example 12−35.
tail
lists the end of a file to stdout (the default is 10 lines). Commonly used to keep track of changes to
a system logfile, using the −f option, which outputs lines appended to the file.
#!/bin/bash
filename=sys.log
exit 0
To list a specific line of a text file, pipe the output of head to tail −1. For example
head −8 database.txt | tail −1 lists the 8th line of the file
database.txt.
Search the target file(s) for occurrences of pattern, where pattern may be literal text or a
Regular Expression.
The −l option lists only the files in which matches were found, but not the matching lines.
The −r (recursive) option searches files in the current working directory and all subdirectories below
it.
The −n option lists the matching lines, together with line numbers.
# grep −cz .
# ^ dot
# means count (−c) zero−separated (−z) items matching "."
# that is, non−empty ones (containing at least 1 character).
#
printf 'a b\nc d\n\n\n\n\n\000\n\000e\000\000\nf' | grep −cz . # 3
printf 'a b\nc d\n\n\n\n\n\000\n\000e\000\000\nf' | grep −cz '$' # 5
printf 'a b\nc d\n\n\n\n\n\000\n\000e\000\000\nf' | grep −cz '^' # 5
#
printf 'a b\nc d\n\n\n\n\n\000\n\000e\000\000\nf' | grep −c '$' # 9
# By default, newline chars (\n) separate items to match.
# Thanks, S.C.
When invoked with more than one target file given, grep specifies which file contains matches.
To force grep to show the filename when searching only one target file, simply give
/dev/null as the second file.
If there is a successful match, grep returns an exit status of 0, which makes it useful in a condition test
in a script, especially in combination with the −q option to suppress output.
grep −q "$word" "$filename" # The "−q" option causes nothing to echo to stdout.
if [ $? −eq $SUCCESS ]
# if grep −q "$word" "$filename" can replace lines 5 − 7.
then
echo "$word found in $filename"
else
echo "$word not found in $filename"
fi
Example 29−6 demonstrates how to use grep to search for a word pattern in a system logfile.
#!/bin/bash
# grp.sh: Very crude reimplementation of 'grep'.
E_BADARGS=65
echo
echo
done
echo
exit 0
# Exercises:
# −−−−−−−−−
# 1) Add newlines to output, if more than one match in any given file.
# 2) Add features.
How can grep search for two (or more) separate patterns? What if you want grep to display all lines
in a file or files that contain both "pattern1" and "pattern2"?
# Filename: tstfile
egrep − extended grep − is the same as grep −E. This uses a somewhat different, extended set of
Regular Expressions, which can make the search a bit more flexible. It also allows the boolean | (or)
operator.
fgrep − fast grep − is the same as grep −F. It does a literal string search (no Regular Expressions),
which usually speeds things up a bit.
On some Linux distros, egrep and fgrep are symbolic links to, or aliases for
grep, but invoked with the −E and −F options, respectively.
#!/bin/bash
# dict−lookup.sh
E_BADARGS=65
MAXCONTEXTLINES=50 # Maximum number of lines to show.
DEFAULT_DICTFILE="/usr/share/dict/webster1913−dict.txt"
# Default dictionary file pathname.
# Change this as necessary.
# Note:
# −−−−
# This particular edition of the 1913 Webster's
#+ begins each entry with an uppercase letter
#+ (lowercase for the remaining characters).
# Only the *very first line* of an entry begins this way,
#+ and that's why the search algorithm below works.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
Definition=$(fgrep −A $MAXCONTEXTLINES "$1 \\" "$dictfile")
# Definitions in form "Word \..."
#
# And, yes, "fgrep" is fast enough
#+ to search even a very large text file.
echo "$Definition" |
sed −n '1,/^[A−Z]/p' |
# Print from first line of output
#+ to the first line of the next entry.
sed '$d' | sed '$d'
# Delete last two lines of output
#+ (blank line and first line of next entry).
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
exit 0
# Exercises:
# −−−−−−−−−
# 1) Modify the script to accept any type of alphabetic input
# + (uppercase, lowercase, mixed case), and convert it
# + to an acceptable format for processing.
#
# 2) Convert the script to a GUI application,
# + using something like "gdialog" . . .
# The script will then no longer take its argument(s)
# + from the command line.
#
# 3) Modify the script to parse one of the other available
# + Public Domain Dictionaries, such as the U.S. Census Bureau Gazetteer.
agrep (approximate grep) extends the capabilities of grep to approximate matching. The search string
may differ by a specified number of characters from the resulting matches. This utility is not part of
the core Linux distribution.
To search compressed files, use zgrep, zegrep, or zfgrep. These also work on
non−compressed files, though slower than plain grep, egrep, fgrep. They are handy
for searching through a mixed set of files, some compressed, some not.
#!/bin/bash
# lookup: Does a dictionary lookup on each word in a data file.
echo
if [ "$lookup" −eq 0 ]
then
echo "\"$word\" is valid."
else
echo "\"$word\" is invalid."
fi
echo
exit 0
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Code below line will not execute because of "exit" command above.
exit 0
sed, awk
Scripting languages especially suited for parsing text files and command output. May be embedded
singly or in combination in pipes and shell scripts.
sed
Non−interactive "stream editor", permits using many ex commands in batch mode. It finds many uses
in shell scripts.
awk
Programmable file extractor and formatter, good for manipulating and/or extracting fields (columns)
in structured text files. Its syntax is similar to C.
wc
wc gives a "word count" on a file or I/O stream:
Using wc to count how many .txt files are in current working directory:
$ ls *.txt | wc −l
# Will work as long as none of the "*.txt" files have a linefeed in their name.
# Thanks, S.C.
Using wc to total up the size of all the files whose names begin with letters in the range d − h
Using wc to count the instances of the word "Linux" in the main source file for this book.
# Thanks, S.C.
tr
character translation filter.
Must use quoting and/or brackets, as appropriate. Quotes prevent the shell from
reinterpreting the special characters in tr command sequences. Brackets should be
quoted to prevent expansion by the shell.
Either tr "A−Z" "*" <filename or tr A−Z \* <filename changes all the uppercase
letters in filename to asterisks (writes to stdout). On some systems this may not work, but tr
A−Z '[**]' will.
tr −d 0−9 <filename
# Deletes all digits from the file "filename".
The −−squeeze−repeats (or −s) option deletes all but the first instance of a string of
consecutive characters. This option is useful for removing excess whitespace.
#!/bin/bash
# Changes a file to all uppercase.
E_BADARGS=65
exit 0
# Exercise:
# Rewrite this script to give the option of changing a file
#+ to *either* upper or lowercase.
#!/bin/bash
#
# Changes every filename in working directory to all lowercase.
#
# Inspired by a script of John Dubois,
#+ which was translated into Bash by Chet Ramey,
exit $?
# The above script will not work on filenames containing blanks or newlines.
# Stephane Chazelas therefore suggests the following alternative:
exit $?
#!/bin/bash
# Du.sh: DOS to UNIX text file converter.
E_WRONGARGS=65
if [ −z "$1" ]
then
echo "Usage: `basename $0` filename−to−convert"
exit $E_WRONGARGS
fi
NEWFILENAME=$1.unx
exit 0
# Exercise:
# −−−−−−−−
# Change the above script to convert from UNIX to DOS.
#!/bin/bash
# rot13.sh: Classic rot13 algorithm,
# encryption that might fool a 3−year old.
cat "$@" | tr 'a−zA−Z' 'n−za−mN−ZA−M' # "a" goes to "n", "b" to "o", etc.
# The 'cat "$@"' construction
#+ permits getting input either from stdin or from files.
exit 0
#!/bin/bash
# crypto−quote.sh: Encrypt quotes
key=ETAOINSHRDLUBCFGJMQPVWZYXK
# The "key" is nothing more than a scrambled alphabet.
# Changing the "key" changes the encryption.
# The 'cat "$@"' construction gets input either from stdin or from files.
# If using stdin, terminate input with a Control−D.
# Otherwise, specify filename as command−line parameter.
exit 0
# Exercise:
# −−−−−−−−
# Modify the script so that it will either encrypt or decrypt,
#+ depending on command−line argument(s).
tr variants
The tr utility has two historic variants. The BSD version does not use brackets (tr a−z A−Z), but
the SysV one does (tr '[a−z]' '[A−Z]'). The GNU version of tr resembles the BSD one, so
quoting letter ranges within brackets is mandatory.
fold
A filter that wraps lines of input to a specified width. This is especially useful with the −s option,
which breaks lines at word spaces (see Example 12−23 and Example A−1).
fmt
Simple−minded file formatter, used as a filter in a pipe to "wrap" long lines of text output.
#!/bin/bash
exit 0
See also Example 12−5.
#!/bin/bash
# This is a slight modification of the example file in the "column" man page.
(printf "PERMISSIONS LINKS OWNER GROUP SIZE MONTH DAY HH:MM PROG−NAME\n" \
; ls −l | sed 1d) | column −t
# The "sed 1d" in the pipe deletes the first line of output,
#+ which would be "total N",
#+ where "N" is the total number of files found by "ls −l".
exit 0
colrm
Column removal filter. This removes columns (characters) from a file and writes the file, lacking the
range of specified columns, back to stdout. colrm 2 4 <filename removes the second
through fourth characters from each line of the text file filename.
If the file contains tabs or nonprintable characters, this may cause unpredictable
behavior. In such cases, consider using expand and unexpand in a pipe preceding
colrm.
nl
Line numbering filter. nl filename lists filename to stdout, but inserts consecutive numbers
at the beginning of each non−blank line. If filename omitted, operates on stdin.
The output of nl is very similar to cat −n, however, by default nl does not list blank lines.
#!/bin/bash
# line−number.sh
# This script echoes itself twice to stdout with its lines numbered.
# 'nl' sees this as line 4 since it does not number blank lines.
# 'cat −n' sees the above line as number 6.
nl `basename $0`
exit 0
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
pr
Print formatting filter. This will paginate files (or stdout) into sections suitable for hard copy
printing or viewing on screen. Various options permit row and column manipulation, joining lines,
setting margins, numbering lines, adding page headers, and merging files, among other things. The pr
command combines much of the functionality of nl, paste, fold, column, and expand.
A particularly useful option is −d, forcing double−spacing (same effect as sed −G).
gettext
The GNU gettext package is a set of utilities for localizing and translating the text output of programs
into foreign languages. While originally intended for C programs, it now supports quite a number of
programming and scripting languages.
The gettext program works on shell scripts. See the info page.
msgfmt
A program for generating binary message catalogs. It is used for localization.
iconv
A utility for converting file(s) to a different encoding (character set). Its chief use is for localization.
TeX is Donald Knuth's elaborate typsetting system. It is often convenient to write a shell script
encapsulating all the options and arguments passed to one of these markup languages.
For example, enscript filename.txt −p filename.ps produces the PostScript output file
filename.ps.
groff, tbl, eqn
Yet another text markup and display formatting language is groff. This is the enhanced GNU version
of the venerable UNIX roff/troff display and typesetting package. Manpages use groff.
The tbl table processing utility is considered part of groff, as its function is to convert table markup
into groff commands.
The eqn equation processing utility is likewise part of groff, and its function is to convert equation
markup into groff commands.
#!/bin/bash
# manview.sh: Formats the source of a man page for viewing.
E_WRONGARGS=65
if [ −z "$1" ]
then
echo "Usage: `basename $0` filename"
exit $E_WRONGARGS
fi
# −−−−−−−−−−−−−−−−−−−−−−−−−−−
groff −Tascii −man $1 | less
# From the man page for groff.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−
exit 0
lex, yacc
The lex lexical analyzer produces programs for pattern matching. This has been replaced by the
nonproprietary flex on Linux systems.
The yacc utility creates a parser based on a set of specifications. This has been replaced by the
nonproprietary bison on Linux systems.
tar
The standard UNIX archiving utility. [41] Originally a Tape ARchiving program, it has developed into
a general purpose package that can handle all manner of archiving with all types of destination
devices, ranging from tape drives to regular files to even stdout (see Example 3−4). GNU tar has
been patched to accept various compression filters, such as tar czvf archive_name.tar.gz *, which
recursively archives and gzips all files in a directory tree except dotfiles in the current working
directory ($PWD). [42]
It may be difficult to recover data from a corrupted gzipped tar archive. When
archiving important files, make multiple backups.
shar
Shell archiving utility. The files in a shell archive are concatenated without compression, and the
resultant archive is essentially a shell script, complete with #!/bin/sh header, and containing all the
necessary unarchiving commands. Shar archives still show up in Internet newsgroups, but otherwise
shar has been pretty well replaced by tar/gzip. The unshar command unpacks shar archives.
ar
Creation and manipulation utility for archives, mainly used for binary object file libraries.
rpm
The Red Hat Package Manager, or rpm utility provides a wrapper for source or binary archives. It
includes commands for installing and checking the integrity of packages, among other things.
A simple rpm −i package_name.rpm usually suffices to install a package, though there are many
more options available.
rpm −qa gives a complete list of all installed rpm packages on a given system. An
rpm −qa package_name lists only the package(s) corresponding to
package_name.
docbook−style−dsssl−1.64−3
docbook−dtd30−sgml−1.0−10
docbook−dtd40−sgml−1.0−11
docbook−utils−pdf−0.6.9−2
docbook−dtd41−sgml−1.0−10
docbook−utils−0.6.9−2
cpio
This specialized archiving copy command (copy input and output) is rarely seen any more, having
been supplanted by tar/gzip. It still has its uses, such as moving a directory tree. With an appropriate
block size (for copying) specified, it can be appreciably faster than tar.
#!/bin/bash
ARGS=2
E_BADARGS=65
if [ $# −ne "$ARGS" ]
then
echo "Usage: `basename $0` source destination"
exit $E_BADARGS
fi
source=$1
destination=$2
# Exercise:
# −−−−−−−−
# Add code to check the exit status ($?) of the 'find | cpio' pipe
#+ and output appropriate error messages if anything went wrong.
exit 0
rpm2cpio
This command extracts a cpio archive from an rpm one.
#!/bin/bash
# de−rpm.sh: Unpack an 'rpm' archive
rpm2cpio < $1 > $TEMPFILE # Converts rpm archive into cpio archive.
cpio −−make−directories −F $TEMPFILE −i # Unpacks cpio archive.
rm −f $TEMPFILE # Deletes cpio archive.
exit 0
# Exercise:
# Add check for whether 1) "target−file" exists and
#+ 2) it is really an rpm archive.
# Hint: parse output of 'file' command.
Compression
gzip
The standard GNU/UNIX compression utility, replacing the inferior and proprietary compress. The
corresponding decompression command is gunzip, which is the equivalent of gzip −d.
The −c option sends the output of gzip to stdout. This is useful when piping to
other commands.
The zcat filter decompresses a gzipped file to stdout, as possible input to a pipe or redirection. This
is, in effect, a cat command that works on compressed files (including files processed with the older
compress utility). The zcat command is equivalent to gzip −dc.
On some commercial UNIX systems, zcat is a synonym for uncompress −c, and will
not work on gzipped files.
See also Example 7−7.
bzip2
An alternate compression utility, usually more efficient (but slower) than gzip, especially on large
files. The corresponding decompression command is bunzip2.
Cross−platform file archiving and compression utility compatible with DOS pkzip.exe. "Zipped"
archives seem to be a more acceptable medium of exchange on the Internet than "tarballs".
unarc, unarj, unrar
These Linux utilities permit unpacking archives compressed with the DOS arc.exe, arj.exe, and
rar.exe programs.
File Information
file
A utility for identifying file types. The command file file−name will return a file specification
for file−name, such as ascii text or data. It references the magic numbers found in
/usr/share/magic, /etc/magic, or /usr/lib/magic, depending on the Linux/UNIX
distribution.
The −f option causes file to run in batch mode, to read from a designated file a list of filenames to
analyze. The −z option, when used on a compressed target file, forces an attempt to analyze the
uncompressed file type.
DIRECTORY=/usr/local/bin
KEYWORD=Bourne
# Bourne and Bourne−Again shell scripts
# Output:
#!/bin/bash
# strip−comment.sh: Strips out the comments (/* COMMENT */) in a C program.
E_NOARGS=0
E_ARGERROR=66
E_WRONG_FILE_TYPE=67
if [ $# −eq "$E_NOARGS" ]
then
echo "Usage: `basename $0` C−program−file" >&2 # Error message to stderr.
exit $E_ARGERROR
fi
if [ "$type" != "$correct_type" ]
then
echo
echo "This script works on C program files only."
echo
exit $E_WRONG_FILE_TYPE
fi
# Need to add one more line to the sed script to deal with
#+ case where line of code has a comment following it on same line.
# This is left as a non−trivial exercise.
exit 0
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Code below this line will not execute because of 'exit 0' above.
usage() {
echo "Usage: `basename $0` C−program−file" >&2
exit 1
}
exit 0
which
which command−xxx gives the full path to "command−xxx". This is useful for finding out whether a
particular command or utility is installed on the system.
$bash which rm
/usr/bin/rm
whereis
Similar to which, above, whereis command−xxx gives the full path to "command−xxx", but also to
its manpage.
$bash whereis rm
#!/bin/bash
DIRECTORY="/usr/X11R6/bin"
# Try also "/bin", "/usr/bin", "/usr/local/bin", etc.
exit 0
bash$ vdir
total 10
−rw−r−−r−− 1 bozo bozo 4034 Jul 18 22:04 data1.xrolo
−rw−r−−r−− 1 bozo bozo 4602 May 25 13:58 data1.xrolo.bak
bash ls −l
total 10
−rw−r−−r−− 1 bozo bozo 4034 Jul 18 22:04 data1.xrolo
−rw−r−−r−− 1 bozo bozo 4602 May 25 13:58 data1.xrolo.bak
−rw−r−−r−− 1 bozo bozo 877 Dec 17 2000 employment.xrolo
locate, slocate
The locate command searches for files using a database stored for just that purpose. The slocate
command is the secure version of locate (which may be aliased to slocate).
/usr/lib/xephem/catalogs/hickson.edb
readlink
Disclose the file that a symbolic link points to.
strings
Use the strings command to find printable strings in a binary or data file. It will list sequences of
printable characters found in the target file. This might be handy for a quick 'n dirty examination of a
core dump or for looking at an unknown graphic image file (strings image−file | more
might show something like JFIF, which would identify the file as a jpeg graphic). In a script, you
would probably parse the output of strings with grep or sed. See Example 10−7 and Example 10−9.
#!/bin/bash
# wstrings.sh: "word−strings" (enhanced "strings" command)
#
# This script filters the output of "strings" by checking it
#+ against a standard word list file.
# This effectively eliminates gibberish and noise,
#+ and outputs only recognized words.
# ===========================================================
# Standard Check for Script Argument(s)
ARGS=1
E_BADARGS=65
E_NOFILE=66
if [ $# −ne $ARGS ]
then
echo "Usage: `basename $0` filename"
exit $E_BADARGS
fi
# ****************************************************************
# Note the technique of feeding the output of 'tr' back to itself,
#+ but with different arguments and/or options on each pass.
# ****************************************************************
done
exit $?
Comparison
diff, patch
diff: flexible file comparison utility. It compares the target files line−by−line sequentially. In some
applications, such as comparing word dictionaries, it may be helpful to filter the files through sort and
uniq before piping them to diff. diff file−1 file−2 outputs the lines in the files that differ,
with carets showing which file each particular line belongs to.
The −−side−by−side option to diff outputs each compared file, line by line, in separate columns,
with non−matching lines marked. The −c and −u options likewise make the output of the command
easier to interpret.
There are available various fancy frontends for diff, such as sdiff, wdiff, xdiff, and mgdiff.
The diff command returns an exit status of 0 if the compared files are identical, and 1
if they differ. This permits use of diff in a test construct within a shell script (see
below).
A common use for diff is generating difference files to be used with patch The −e option outputs
files suitable for ed or ex scripts.
patch: flexible versioning utility. Given a difference file generated by diff, patch can upgrade a
previous version of a package to a newer version. It is much more convenient to distribute a relatively
small "diff" file than the entire body of a newly revised package. Kernel "patches" have become the
preferred method of distributing the frequent releases of the Linux kernel.
cd /usr/src
gzip −cd patchXX.gz | patch −p0
# Upgrading kernel source using 'patch'.
# From the Linux kernel docs "README",
# by anonymous author (Alan Cox?).
The diff command can also recursively compare directories (for the filenames
present).
Like diff, cmp returns an exit status of 0 if the compared files are identical, and 1 if
they differ. This permits use in a test construct within a shell script.
#!/bin/bash
if [ $# −ne "$ARGS" ]
then
echo "Usage: `basename $0` file1 file2"
exit $E_BADARGS
fi
if [[ ! −r "$1" || ! −r "$2" ]]
then
echo "Both files to be compared must exist and be readable."
exit $E_UNREADABLE
fi
cmp $1 $2 &> /dev/null # /dev/null buries the output of the "cmp" command.
# cmp −s $1 $2 has same result ("−s" silent flag to "cmp")
# Thank you Anders Gustavsson for pointing this out.
#
# Also works with 'diff', i.e., diff $1 $2 &> /dev/null
exit 0
◊ −1 suppresses column 1
◊ −2 suppresses column 2
◊ −3 suppresses column 3
◊ −12 suppresses both columns 1 and 2, etc.
Utilities
basename
Strips the path information from a file name, printing only the file name. The construction
basename $0 lets the script know its name, that is, the name it was invoked by. This can be used
for "usage" messages if, for example a script is called with missing arguments:
basename and dirname can operate on any arbitrary string. The argument does not
need to refer to an existing file, or even be a filename for that matter (see Example
A−7).
#!/bin/bash
a=/home/bozo/daily−journal.txt
exit 0
split, csplit
These are utilities for splitting a file into smaller chunks. They are usually used for splitting up large
files in order to back them up on floppies or preparatory to e−mailing or uploading them.
The csplit command splits a file according to context, the split occuring where patterns are matched.
sum, cksum, md5sum, sha1sum
These are utilities for generating checksums. A checksum is a number mathematically calculated from
the contents of a file, for the purpose of checking its integrity. A script might refer to a list of
checksums for security purposes, such as ensuring that the contents of key system files have not been
altered or corrupted. For security applications, use the md5sum (message digest 5 checksum)
command, or better yet, the newer sha1sum (Secure Hash Algorithm).
The cksum command shows the size, in bytes, of its target, whether file or stdout.
The md5sum and sha1sum commands display a dash when they receive their input
from stdout.
#!/bin/bash
# file−integrity.sh: Checking whether files in a given directory
# have been tampered with.
E_DIR_NOMATCH=70
E_BAD_DBFILE=71
dbfile=File_record.md5
# Filename for storing records (database file).
set_up_database ()
{
echo ""$directory"" > "$dbfile"
# Write directory name to first line of file.
md5sum "$directory"/* >> "$dbfile"
# Append md5 checksums and filenames.
}
check_database ()
{
local n=0
local filename
local checksum
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− #
# This file check should be unnecessary,
#+ but better safe than sorry.
if [ ! −r "$dbfile" ]
then
echo "Unable to read checksum database file!"
exit $E_BAD_DBFILE
fi
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− #
directory_checked="${record[0]}"
if [ "$directory_checked" != "$directory" ]
then
echo "Directories do not match up!"
# Tried to use file for a different directory.
if [ "${record[n]}" = "${checksum[n]}" ]
then
echo "${filename[n]} unchanged."
fi
let "n+=1"
done <"$dbfile" # Read from checksum database file.
# =================================================== #
# main ()
if [ −z "$1" ]
then
directory="$PWD" # If not specified,
else #+ use current working directory.
directory="$1"
fi
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− #
if [ ! −r "$dbfile" ] # Need to create database file?
then
echo "Setting up database file, \""$directory"/"$dbfile"\"."; echo
set_up_database
fi
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− #
echo
exit 0
There have been reports that the 128−bit md5sum can be cracked, so the more secure
160−bit sha1sum is a welcome new addition to the checksum toolkit.
Some security consultants think that even sha1sum can be compromised. So, what's
next −− a 512−bit checksum utility?
shred
Securely erase a file by overwriting it multiple times with random bit patterns before deleting it. This
command has the same effect as Example 12−55, but does it in a more thorough and elegant manner.
Advanced forensic technology may still be able to recover the contents of a file, even
after application of shred.
uuencode
This utility encodes binary files into ASCII characters, making them suitable for transmission in the
body of an e−mail message or in a newsgroup posting.
uudecode
This reverses the encoding, decoding uuencoded files back into the original binaries.
#!/bin/bash
# Uudecodes all uuencoded files in current working directory.
# Exercise:
# −−−−−−−−
# Modify this script to check each file for a newsgroup header,
#+ and skip to next if not found.
exit 0
The fold −s command may be useful (possibly in a pipe) to process long uudecoded
text messages downloaded from Usenet newsgroups.
mimencode, mmencode
The mimencode and mmencode commands process multimedia−encoded e−mail attachments.
Although mail user agents (such as pine or kmail) normally handle this automatically, these
particular utilities permit manipulating such attachments manually from the command line or in a
batch by means of a shell script.
crypt
At one time, this was the standard UNIX file encryption utility. [43] Politically motivated government
regulations prohibiting the export of encryption software resulted in the disappearance of crypt from
much of the UNIX world, and it is still missing from most Linux distributions. Fortunately,
programmers have come up with a number of decent alternatives to it, among them the author's very
own cruft (see Example A−4).
Miscellaneous
mktemp
Create a temporary file [44] with a "unique" filename. When invoked from the command line without
additional arguments, it creates a zero−length file in the /tmp directory.
bash$ mktemp
/tmp/tmp.zzsvql3154
PREFIX=filename
tempfile=`mktemp $PREFIX.XXXXXX`
# ^^^^^^ Need at least 6 placeholders
#+ in the filename template.
# If no filename template supplied,
#+ "tmp.XXXXXXXXXX" is the default.
The make command checks a Makefile, a list of file dependencies and operations to be carried out.
install
Special purpose file copying command, similar to cp, but capable of setting permissions and attributes
of the copied files. This command seems tailormade for installing software packages, and as such it
shows up frequently in Makefiles (in the make install : section). It could likewise find use
in installation scripts.
dos2unix
This utility, written by Benjamin Lin and collaborators, converts DOS−formatted text files (lines
terminated by CR−LF) to UNIX format (lines terminated by LF only), and vice−versa.
ptx
The ptx [targetfile] command outputs a permuted index (cross−reference list) of the targetfile. This
may be further filtered and formatted in a pipe, if necessary.
more, less
Pagers that display a text file or stream to stdout, one screenful at a time. These may be used to
filter the output of stdout . . . or of a script.
host
Searches for information about an Internet host by name or IP address, using DNS.
ipcalc
Displays IP information for a host. With the −h option, ipcalc does a reverse DNS lookup, finding the
name of the host (server) from the IP address.
nslookup
Do an Internet "name server lookup" on a host by IP address. This is essentially equivalent to ipcalc
−h or dig −x . The command may be run either interactively or noninteractively, i.e., from within a
script.
The nslookup command has allegedly been "deprecated," but it still has its uses.
Non−authoritative answer:
Name: kuhleersparnis.ch
dig
Domain Information Groper. Similar to nslookup, dig does an Internet "name server lookup" on a
host. May be run either interactively or noninteractively, i.e., from within a script.
Some interesting options to dig are +time=N for setting a query timeout to N seconds, +nofail for
continuing to query servers until a reply is received, and −x for doing a reverse address lookup.
;; QUESTION SECTION:
;2.6.9.81.in−addr.arpa. IN PTR
;; AUTHORITY SECTION:
6.9.81.in−addr.arpa. 3600 IN SOA ns.eltel.net. noc.eltel.net.
2002031705 900 600 86400 3600
#!/bin/bash
# spam−lookup.sh: Look up abuse contact to report a spammer.
# Thanks, Michael Zick.
# Exercise:
# −−−−−−−−
# Expand the functionality of this script
#+ so that it automatically e−mails a notification
#+ to the responsible ISP's contact address(es).
# Hint: use the "mail" command.
exit $?
# spam−lookup.sh chinatietong.com
# A known spam domain.
# "crnet_mgr@chinatietong.com"
# "crnet_tec@chinatietong.com"
# "postmaster@chinatietong.com"
#! /bin/bash
# is−spammer.sh: Identifying spam domains
# is−spammer <domain.name>
# Uses functions.
# Uses IFS to parse strings by assignment into arrays.
# And even does something useful: checks e−mail blacklists.
server=${1}${2}
reply=$( dig +short ${server} )
# See: http://cbl.abuseat.org.
echo −n ' abuseat.org says: '
echo $(chk_adr ${rev_dns} 'cbl.abuseat.org')
else
echo
echo 'Could not use that address.'
fi
exit 0
# Exercises:
# −−−−−−−−
traceroute
Trace the route taken by packets sent to a remote host. This command works within a LAN, WAN, or
over the Internet. The remote host may be specified by an IP address. The output of this command
may be filtered by grep or sed in a pipe.
ping
Broadcast an "ICMP ECHO_REQUEST" packet to another machine, either on a local or remote
network. This is a diagnostic tool for testing network connections, and it should be used with caution.
A successful ping returns an exit status of 0. This can be tested for in a script.
whois
Perform a DNS (Domain Name System) lookup. The −h option permits specifying which particular
whois server to query. See Example 4−6 and Example 12−36.
finger
Retrieve information about users on a network. Optionally, this command can display a user's
~/.plan, ~/.project, and ~/.forward files, if present.
bash$ finger
Login Name Tty Idle Login Time Office Office Phone
bozo Bozo Bozeman tty1 8 Jun 25 16:59
bozo Bozo Bozeman ttyp0 Jun 25 16:59
bozo Bozo Bozeman ttyp1 Jun 25 17:07
Out of security considerations, many networks disable finger and its associated daemon. [45]
chfn
Change information disclosed by the finger command.
vrfy
Verify an Internet e−mail address.
sx, rx
The sx and rx command set serves to transfer files to and from a remote host using the xmodem
protocol. These are generally part of a communications package, such as minicom.
sz, rz
The sz and rz command set serves to transfer files to and from a remote host using the zmodem
protocol. Zmodem has certain advantages over xmodem, such as faster transmission rate and
resumption of interrupted file transfers. Like sx and rx, these are generally part of a communications
package.
ftp
Utility and protocol for uploading / downloading files to or from a remote host. An ftp session can be
automated in a script (see Example 17−6, Example A−4, and Example A−13).
uucp, uux, cu
uucp: UNIX to UNIX copy. This is a communications package for transferring files between UNIX
servers. A shell script is an effective way to handle a uucp command sequence.
Since the advent of the Internet and e−mail, uucp seems to have faded into obscurity, but it still exists
and remains perfectly workable in situations where an Internet connection is not available or
appropriate. The advantage of uucp is that it is fault−tolerant, so even if there is a service interruption
the copy operation will resume where it left off when the connection is restored.
−−−
uux: UNIX to UNIX execute. Execute a command on a remote system. This command is part of the
uucp package.
−−−
cu: Call Up a remote system and connect as a simple terminal. It is a sort of dumbed−down version of
telnet. This command is part of the uucp package.
telnet
Utility and protocol for connecting to a remote host.
The telnet protocol contains security holes and should therefore probably be avoided.
wget
The wget utility non−interactively retrieves or downloads files from a Web or ftp site. It works well
in a script.
wget −p http://www.xyz23.com/file01.html
# The −p or −−page−requisite option causes wget to fetch all files
#+ required to display the specified page.
E_NOPARAMS=66
stock_symbol=$1
file_suffix=.html
# Fetches an HTML file, so name it appropriately.
URL='http://finance.yahoo.com/q?s='
# Yahoo finance board, with stock query suffix.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
wget −O ${stock_symbol}${file_suffix} "${URL}${stock_symbol}"
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
exit $?
# Exercises:
# −−−−−−−−−
#
# 1) Add a test to ensure the user running the script is on−line.
# (Hint: parse the output of 'ps −ax' for "ppp" or "connect."
#
# 2) Modify this script to fetch the local weather report,
#+ taking the user's zip code as an argument.
See also Example A−29 and Example A−30.
lynx
The lynx Web and file browser can be used inside a script (with the −dump option) to retrieve a file
from a Web or ftp site non−interactively.
Remote synchronize, updates (synchronizes) files between two different networked machines.
#!/bin/bash
# fc4upd.sh
URL=rsync://distro.ibiblio.org/fedora−linux−core/updates/
# URL=rsync://ftp.kddilabs.jp/fedora/core/updates/
# URL=rsync://rsync.planetmirror.com/fedora−linux−core/updates/
DEST=${1:−/var/www/html/fedora/updates/}
LOG=/tmp/repo−update−$(/bin/date +%Y−%m−%d).txt
PID_FILE=/var/run/${0##*/}.pid
init () {
# Let pipe command return possible rsync error, e.g., stalled network.
set −o pipefail
check_pid () {
# Check if process exists.
if [ −s "$PID_FILE" ]; then
echo "PID file exists. Checking ..."
PID=$(/bin/egrep −o "^[[:digit:]]+" $PID_FILE)
if /bin/ps −−pid $PID &>/dev/null; then
echo "Process $PID found. ${0##*/} seems to be running!"
/usr/bin/logger −t ${0##*/} \
"Process $PID found. ${0##*/} seems to be running!"
exit $E_RETURN
fi
echo "Process $PID not found. Start new process . . ."
fi
}
for p in "${EXCLUDE[@]}"; do
exclude="$exclude −−exclude \"$p\""
done
}
echo "Downloading..."
/bin/nice /usr/bin/rsync \
$OPTS \
RET=$?
echo "Done"
rm −f $PID_FILE 2>/dev/null
return $RET
}
# −−−−−−−
# Main
init
check_pid
set_range
get_list
get_file
RET=$?
# −−−−−−−
exit $RET
Using rcp, rsync, and similar utilities with security implications in a shell script may not be
advisable. Consider, instead, using ssh, scp, or an expect script.
ssh
Secure shell, logs onto a remote host and executes commands there. This secure replacement for
telnet, rlogin, rcp, and rsh uses identity authentication and encryption. See its manpage for details.
#!/bin/bash
# remote.bash: Using ssh.
# Presumptions:
# −−−−−−−−−−−−
# fd−2 isn't being captured ( '2>/dev/null' ).
# ssh/sshd presumes stderr ('2') will display to user.
#
# sshd is running on your machine.
# For any 'standard' distribution, it probably is,
#+ and without any funky ssh−keygen having been done.
ls −l
exit 0
Within a loop, ssh may cause unexpected behavior. According to a Usenet post in the
comp.unix shell archives, ssh inherits the loop's stdin. To remedy this, pass ssh
either the −n or −f option.
scp
Secure copy, similar in function to rcp, copies files between two different networked machines,
but does so using authentication, and with a security level similar to ssh.
Local Network
write
This is a utility for terminal−to−terminal communication. It allows sending lines from your terminal
(console or xterm) to that of another user. The mesg command may, of course, be used to disable
write access to a terminal
mail
Send or read e−mail messages.
This stripped−down command−line mail client works fine as a command embedded in a script.
#!/bin/sh
# self−mailer.sh: Self−mailing script
# ============================================================================
cat $0 | mail −s "Script \"`basename $0`\" has mailed itself to you." "$adr"
# ============================================================================
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Greetings from the self−mailing script.
# A mischievous person has run this script,
#+ which has caused it to mail itself to you.
# Apparently, some people have nothing better
#+ to do with their time.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
exit 0
mailto
Similar to the mail command, mailto sends e−mail messages from the command line or in a script.
However, mailto also permits sending MIME (multimedia) messages.
vacation
This utility automatically replies to e−mails that the intended recipient is on vacation and temporarily
unavailable. This runs on a network, in conjunction with sendmail, and is not applicable to a dial−up
POPmail account.
tput
Initialize terminal and/or fetch information about it from terminfo data. Various options permit
certain terminal operations. tput clear is the equivalent of clear, below. tput reset is the equivalent
of reset, below. tput sgr0 also resets the terminal, but without clearing the screen.
Issuing a tput cup X Y moves the cursor to the (X,Y) coordinates in the current terminal. A clear to
erase the terminal screen would normally precede this.
Note that stty offers a more powerful command set for controlling a terminal.
infocmp
This command prints out extensive information about the current terminal. It references the terminfo
database.
bash$ infocmp
# Reconstructed via infocmp from file:
/usr/share/terminfo/r/rxvt
rxvt|rxvt terminal emulator (X Window System),
am, bce, eo, km, mir, msgr, xenl, xon,
colors#8, cols#80, it#8, lines#24, pairs#64,
acsc=``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
bel=^G, blink=\E[5m, bold=\E[1m,
civis=\E[?25l,
clear=\E[H\E[2J, cnorm=\E[?25h, cr=^M,
...
reset
Reset terminal parameters and clear text screen. As with clear, the cursor and prompt reappear in the
upper lefthand corner of the terminal.
clear
The clear command simply clears the text screen at the console or in an xterm. The prompt and cursor
reappear at the upper lefthand corner of the screen or xterm window. This command may be used
either at the command line or in a script. See Example 10−25.
script
This utility records (saves to a file) all the user keystrokes at the command line in a console or an
xterm window. This, in effect, creates a record of a session.
factor
Decompose an integer into prime factors.
bc
Bash can't handle floating point calculations, and it lacks operators for certain important mathematical
functions. Fortunately, bc comes to the rescue.
Not just a versatile, arbitrary precision calculation utility, bc offers many of the facilities of a
programming language.
Since it is a fairly well−behaved UNIX utility, and may therefore be used in a pipe, bc comes in
handy in scripts.
Here is a simple template for using bc to calculate a script variable. This uses command substitution.
#!/bin/bash
# monthlypmt.sh: Calculates monthly payment on a mortgage.
echo
echo "Given the principal, interest rate, and term of a mortgage,"
echo "calculate the monthly payment."
bottom=1.0
echo
echo −n "Enter principal (no commas) "
read principal
echo −n "Enter interest rate (percent) " # If 12%, enter "12", not ".12".
read interest_r
echo −n "Enter term (months) "
read term
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Rick Boivie pointed out a more efficient implementation
#+ of the above loop, which decreases computation time by 2/3.
# bottom=`{
# echo "scale=9; bottom=$bottom; interest_rate=$interest_rate"
# for ((x=1; x <= $months; x++))
# do
# echo 'bottom = bottom * interest_rate + 1'
# done
# echo 'bottom'
# } | bc` # Embeds a 'for loop' within command substitution.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# On the other hand, Frank Wang suggests:
# bottom=$(echo "scale=9; ($interest_rate^$term−1)/($interest_rate−1)" | bc)
# Because . . .
# The algorithm behind the loop
#+ is actually a sum of geometric proportion series.
# The sum formula is e0(1−q^n)/(1−q),
#+ where e0 is the first element and q=e(n+1)/e(n)
#+ and n is the number of elements.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
echo
echo "monthly payment = \$$payment" # Echo a dollar sign in front of amount.
echo
exit 0
# Exercises:
# 1) Filter input to permit commas in principal amount.
# 2) Filter input to permit interest to be entered as percent or decimal.
# 3) If you are really ambitious,
# expand this script to print complete amortization tables.
#!/bin/bash
##########################################################################
# Shellscript: base.sh − print number to different bases (Bourne Shell)
# Author : Heiner Steven (heiner.steven@odn.de)
# Date : 07−03−95
# Category : Desktop
# $Id: base.sh,v 1.2 2000/02/06 19:55:35 heiner Exp $
# ==> Above line is RCS ID info.
##########################################################################
# Description
#
# Changes
# 21−03−95 stv fixed error occuring with 0xb as input (0.2)
##########################################################################
NOARGS=65
PN=`basename "$0"` # Program name
VER=`echo '$Revision: 1.2 $' | cut −d' ' −f2` # ==> VER=1.2
Usage () {
echo "$PN − print number to different bases, $VER (stv '95)
usage: $PN [number ...]
Msg () {
for i # ==> in [list] missing.
do echo "$PN: $i" >&2
done
}
PrintBases () {
# Determine base of the number
for i # ==> in [list] missing...
do # ==> so operates on command line arg(s).
case "$i" in
0b*) ibase=2;; # binary
0x*|[a−f]*|[A−F]*) ibase=16;; # hexadecimal
0*) ibase=8;; # octal
done
}
while [ $# −gt 0 ]
# ==> Is a "while loop" really necessary here,
# ==>+ since all the cases either break out of the loop
# ==>+ or terminate the script.
# ==> (Thanks, Paulo Marcel Coelho Aragao.)
do
case "$1" in
−−) shift; break;;
−h) Usage;; # ==> Help message.
−*) Usage;;
*) break;; # first number
esac # ==> More error checking for illegal input might be useful.
shift
done
if [ $# −gt 0 ]
then
PrintBases "$@"
else # read from stdin
while read line
do
PrintBases $line
done
fi
exit 0
An alternate method of invoking bc involves using a here document embedded within a command
substitution block. This is especially appropriate when a script needs to pass a list of options and
commands to bc.
...or...
#!/bin/bash
# Invoking 'bc' using command substitution
# in combination with a 'here document'.
exit 0
#!/bin/bash
# cannon.sh: Approximating PI by firing cannonballs.
get_random ()
# main() {
# Initialize variables.
shots=0
splashes=0
thuds=0
Pi=0
done
echo
echo "After $shots shots, PI looks like approximately $Pi."
# Tends to run a bit high . . .
# Probably due to round−off error and imperfect randomness of $RANDOM.
echo
# }
exit 0
Most persons avoid dc, since it requires non−intuitive RPN input. Yet, it has its uses.
#!/bin/bash
# hexconvert.sh: Convert a decimal number to hexadecimal.
if [ −z "$1" ]
then
echo "Usage: $0 number"
exit $E_NOARGS
# Need a command line argument.
fi
# Exercise: add argument validity checking.
hexcvt ()
{
if [ −z "$1" ]
then
echo 0
return # "Return" 0 if no arg passed to function.
fi
hexcvt "$1"
exit 0
Studying the info page for dc gives some insight into its intricacies. However, there seems to be a
small, select group of dc wizards who delight in showing off their mastery of this powerful, but
arcane utility.
#!/bin/bash
# factr.sh: Factor a number
if [ −z $1 ]
then
echo "Usage: $0 number"
exit $E_NOARGS
fi
exit 0
awk
Yet another way of doing floating point math in a script is using awk's built−in math functions in a
shell wrapper.
#!/bin/bash
# hypotenuse.sh: Returns the "hypotenuse" of a right triangle.
# ( square root of sum of squares of the "legs")
exit 0
jot, seq
These utilities emit a sequence of integers, with a user−selected increment.
The normal separator character between each integer is a newline, but this can be changed with the
−s option.
bash$ seq 5
1
2
3
4
5
bash$ seq −s : 5
1:2:3:4:5
#!/bin/bash
# Using "seq"
echo
echo; echo
echo; echo
BEGIN=75
END=80
echo; echo
BEGIN=45
INTERVAL=5
END=80
echo; echo
exit 0
A simpler example:
#!/bin/bash
# letter−count.sh: Counting letter occurrences in a text file.
# Written by Stefano Palmeri.
# Used in ABS Guide with permission.
# Slightly modified by document author.
show_help(){
echo
echo Usage: `basename $0` file letters
echo Note: `basename $0` arguments are case sensitive.
echo Example: `basename $0` foobar.txt G n U L i N U x.
echo
}
exit $?
#!/bin/bash
# Using getopt.
E_OPTERR=65
if [ "$#" −eq 0 ]
then # Script needs at least one command−line argument.
echo "Usage $0 −[options a,b,c]"
exit $E_OPTERR
fi
while [ ! −z "$1" ]
do
case "$1" in
−a) echo "Option \"a\"";;
−b) echo "Option \"b\"";;
−c) echo "Option \"c\"";;
−d) echo "Option \"d\" $2";;
*) break;;
esac
shift
done
exit 0
See Example 9−13 for a simplified emulation of getopt.
run−parts
The run−parts command [46] executes all the scripts in a target directory, sequentially in
ASCII−sorted filename order. Of course, the scripts need to have execute permission.
The cron daemon invokes run−parts to run the scripts in the /etc/cron.* directories.
yes
In its default behavior the yes command feeds a continuous string of the character y followed by a
line feed to stdout. A control−c terminates the run. A different output string may be specified, as in
yes different string, which would continually output different string to stdout.
One might well ask the purpose of this. From the command line or in a script, the output of yes can be
redirected or piped into a program expecting user input. In effect, this becomes a sort of poor man's
version of expect.
Caution advised when piping yes to a potentially dangerous system command, such as
fsck or fdisk. It may have unintended side−effects.
lp
The lp and lpr commands send file(s) to the print queue, to be printed as hard copy. [47] These
commands trace the origin of their names to the line printers of another era.
Formatting packages, such as groff and Ghostscript may send their output directly to lp.
Related commands are lpq, for viewing the print queue, and lprm, for removing jobs from the print
queue.
tee
[UNIX borrows an idea from the plumbing trade.]
This is a redirection operator, but with a difference. Like the plumber's "tee," it permits "siponing off"
to a file the output of a command or commands within a pipe, but without affecting the result. This is
useful for printing an ongoing process to a file or paper, perhaps to keep track of it for debugging
purposes.
(redirection)
|−−−−> to file
|
Unfortunately, pathchk does not return a recognizable error code, and it is therefore pretty much
useless in a script. Consider instead the file test operators.
dd
This is the somewhat obscure and much feared "data duplicator" command. Originally a utility for
exchanging data on magnetic tapes between UNIX minicomputers and IBM mainframes, this
command still has its uses. The dd command simply copies a file (or stdin/stdout), but with
conversions. Possible conversions are ASCII/EBCDIC, [49] upper/lower case, swapping of byte pairs
between input and output, and skipping and/or truncating the head or tail of the input file. A dd
−−help lists the conversion and other options that this powerful utility takes.
#!/bin/bash
# self−copy.sh
file_subscript=copy
exit $?
#!/bin/bash
# exercising−dd.sh
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
exit 0
To demonstrate just how versatile dd is, let's use it to capture keystrokes.
#!/bin/bash
# dd−keypress.sh: Capture keystrokes without needing to press ENTER.
# Thanks, S.C.
The dd command can copy raw data and disk images to and from devices, such as floppies and tape
drives (Example A−5). A common use is creating boot floppies.
dd if=kernel−image of=/dev/fd0H1440
Similarly, dd can copy the entire contents of a floppy, even one formatted with a "foreign" OS, to the
hard drive as an image file.
dd if=/dev/fd0 of=/home/bozo/projects/floppy.img
Other applications of dd include initializing temporary swap files (Example 28−2) and ramdisks
(Example 28−3). It can even do a low−level copy of an entire hard drive partition, although this is not
People (with presumably nothing better to do with their time) are constantly thinking of interesting
applications of dd.
#!/bin/bash
# blot−out.sh: Erase "all" traces of a file.
file=$1
if [ ! −e "$file" ]
then
echo "File \"$file\" not found."
exit $E_NOT_FOUND
fi
echo; echo −n "Are you absolutely sure you want to blot out \"$file\" (y/n)? "
read answer
case "$answer" in
[nN]) echo "Changed your mind, huh?"
exit $E_CHANGED_MIND
;;
*) echo "Blotting out file \"$file\".";;
esac
echo
exit 0
# This script may not play well with a journaled file system.
# Exercise (difficult): Fix it so it does.
# Tom Vier's "wipe" file−deletion package does a much more thorough job
#+ of file shredding than this simple script.
# http://www.ibiblio.org/pub/Linux/utils/file/wipe−2.0.0.tar.bz2
080490bc <.init>:
mcookie
This command generates a "magic cookie", a 128−bit (32−character) pseudorandom hexadecimal
number, normally used as an authorization "signature" by the X server. This also available for use in a
script as a "quick 'n dirty" random number.
random000=$(mcookie)
Of course, a script could use md5 for the same purpose.
#!/bin/bash
# tempfile−name.sh: temp filename generator
suffix=${BASE_STR:POS:LEN}
# Extract a 5−character string, starting at position 11.
temp_filename=$prefix.$suffix
# Construct the filename.
# sh tempfile−name.sh
# Temp filename = temp.e19ea
exit 0
units
This utility converts between different units of measure. While normally invoked in interactive mode,
units may find use in a script.
#!/bin/bash
# unit−conversion.sh
Unit1=miles
Unit2=meters
cfactor=`convert_units $Unit1 $Unit2`
quantity=3.73
exit 0
m4
A hidden treasure, m4 is a powerful macro processing filter, [50] virtually a complete language.
Although originally written as a pre−processor for RatFor, m4 turned out to be useful as a
stand−alone utility. In fact, m4 combines some of the functionality of eval, tr, and awk, in addition to
its extensive macro expansion facilities.
The April, 2002 issue of Linux Journal has a very nice article on m4 and its uses.
#!/bin/bash
# m4.sh: Using the m4 macro processor
# Strings
string=abcdA01
echo "len($string)" | m4 # 7
echo "substr($string,4)" | m4 # A01
echo "regexp($string,[0−1][0−1],\&Z)" | m4 # 01Z
# Arithmetic
echo "incr(22)" | m4 # 23
echo "eval(99 / 3)" | m4 # 33
exit 0
doexec
The doexec command enables passing an arbitrary list of arguments to a binary executable. In
particular, passing argv[0] (which corresponds to $0 in a script) lets the executable be invoked by
various names, and it can then carry out different sets of actions, according to the name by which it
was called. What this amounts to is roundabout way of passing options to an executable.
For example, the /usr/local/bin directory might contain a binary called "aaa". Invoking doexec
/usr/local/bin/aaa list would list all those files in the current working directory beginning with an "a",
while invoking (the same executable with) doexec /usr/local/bin/aaa delete would delete those files.
For example, sox soundfile.wav soundfile.au changes a WAV sound file into a (Sun audio format)
AU sound file.
Shell scripts are ideally suited for batch processing sox operations on sound files. For examples, see
the Linux Radio Timeshift HOWTO and the MP3do Project.
users
Show all logged on users. This is the approximate equivalent of who −q.
groups
Lists the current user and the groups she belongs to. This corresponds to the $GROUPS internal
variable, but gives the group names, rather than the numbers.
bash$ groups
bozita cdrom cdwriter audio xgrp
The chgrp command changes the group ownership of a file or files. You must be owner of the
file(s) as well as a member of the destination group (or root) to use this operation.
The adduser command is a synonym for useradd and is usually a symbolic link to it.
usermod
Modify a user account. Changes may be made to the password, group membership, expiration date,
and other attributes of a given user's account. With this command, a user's password may be locked,
which has the effect of disabling the account.
groupmod
Modify a given group. The group name and/or ID number may be changed using this command.
id
The id command lists the real and effective user IDs and the group IDs of the user associated with the
current process. This is the counterpart to the $UID, $EUID, and $GROUPS internal Bash variables.
bash$ id
uid=501(bozo) gid=501(bozo) groups=501(bozo),22(cdrom),80(cdwriter),81(audio)
The id command shows the effective IDs only when they differ from the real ones.
Also see Example 9−5.
who
Show all users logged on to the system.
bash$ who
bozo tty1 Apr 27 17:45
bozo pts/0 Apr 27 17:46
bozo pts/1 Apr 27 17:47
bozo pts/2 Apr 27 17:49
The −m gives detailed information about only the current user. Passing any two arguments to who is
the equivalent of who −m, as in who am i or who The Man.
bash$ who −m
localhost.localdomain!bozo pts/2 Apr 27 17:49
whoami is similar to who −m, but only lists the user name.
bash$ whoami
bozo
w
Show all logged on users and the processes belonging to them. This is an extended version of who.
The output of w may be piped to grep to find a specific user and/or process.
bash$ logname
bozo
bash$ whoami
bozo
However...
bash$ su
Password: ......
bash# whoami
root
bash# logname
bozo
While logname prints the name of the logged in user, whoami gives the name of the
user attached to the current process. As we have just seen, sometimes these are not the
same.
su
Runs a program or script as a substitute user. su rjones starts a shell as user rjones. A naked su
defaults to root. See Example A−15.
sudo
Runs a command as root (or another user). This may be used in a script, thus permitting a regular user
to run the script.
#!/bin/bash
# Some commands.
sudo cp /root/secretfile /home/bozo/secret
# Some more commands.
The file /etc/sudoers holds the names of users permitted to invoke sudo.
passwd
Sets, changes, or manages a user's password.
The passwd command can be used in a script, but should not be.
#!/bin/bash
# setnew−password.sh: For demonstration purposes only.
# Not a good idea to actually run this script.
# This script must be run as root.
E_NOSUCHUSER=70
SUCCESS=0
username=bozo
NEWPASSWORD=security_violation
exit 0
The passwd command's −l, −u, and −d options permit locking, unlocking, and deleting a user's
password. Only root may use these options.
ac
Show users' logged in time, as read from /var/log/wtmp. This is one of the GNU accounting
utilities.
bash$ ac
total 68.08
last
List last logged in users, as read from /var/log/wtmp. This command can also show remote
logins.
For example, to show the last few times the system rebooted:
Terminals
tty
Echoes the name of the current user's terminal. Note that each separate xterm window counts as a
different terminal.
bash$ tty
/dev/pts/1
stty
Shows and/or changes terminal settings. This complex command, used in a script, can control
terminal behavior and the way output displays. See the info page, and study it carefully.
#!/bin/bash
# erase.sh: Using "stty" to set an erase character when reading input.
# Warning: Even after the script exits, the new key value remains set.
exit 0
#!/bin/bash
# secret−pw.sh: secret password
echo
echo −n "Enter password "
read passwd
echo "password is $passwd"
echo −n "If someone had been looking over your shoulder, "
echo "your password would have been compromised."
exit 0
#!/bin/bash
# keypress.sh: Detect a user keypress ("hot keys").
echo
echo
echo "Key pressed was \""$Keypress"\"."
echo
exit 0
Also see Example 9−3.
Normally, a terminal works in the canonical mode. When a user hits a key, the resulting character does
not immediately go to the program actually running in this terminal. A buffer local to the terminal stores
keystrokes. When the user hits the ENTER key, this sends all the stored keystrokes to the program
running. There is even a basic line editor inside the terminal.
bash$ stty −a
speed 9600 baud; rows 36; columns 96; line = 0;
intr = ^C; quit = ^\; erase = ^H; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>;
start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O;
...
isig icanon iexten echo echoe echok −echonl −noflsh −xcase −tostop −echoprt
Using canonical mode, it is possible to redefine the special keys for the local terminal line editor.
The process controlling the terminal receives only 12 characters (11 alphabetic ones, plus a newline),
although the user hit 26 keys.
In non−canonical ("raw") mode, every key hit (including special editing keys such as ctl−H) sends a
character immediately to the controlling process.
The Bash prompt disables both icanon and echo, since it replaces the basic terminal line editor with its
own more elaborate one. For example, when you hit ctl−A at the Bash prompt, there's no ^A echoed by
the terminal, but Bash gets a \1 character, interprets it, and moves the cursor to the begining of the line.
Stéphane Chazelas
setterm
Set certain terminal attributes. This command writes to its terminal's stdout a string that changes
the behavior of that terminal.
The setterm command can be used within a script to change the appearance of text written to
stdout, although there are certainly better tools available for this purpose.
bash$ tset −r
Terminal type is xterm−xfree86.
Kill is control−U (^U).
Interrupt is control−C (^C).
setserial
Set or display serial port parameters. This command must be run by root user and is usually found in a
system setup script.
It can be very annoying to have a message about ordering pizza suddenly appear in the
middle of the text file you are editing. On a multi−user network, you might therefore
wish to disable write access to your terminal when you need to avoid interruptions.
wall
This is an acronym for "write all", i.e., sending a message to all users at every terminal logged into the
network. It is primarily a system administrator's tool, useful, for example, when warning everyone
that the system will shortly go down due to a problem (see Example 17−1).
If write access to a particular terminal has been disabled with mesg, then wall cannot
send a message to it.
uname
Output system specifications (OS, kernel version, etc.) to stdout. Invoked with the −a option, gives
verbose system info (see Example 12−5). The −s option shows only the OS type.
bash$ uname −a
Linux localhost.localdomain 2.2.15−2.5.0 #1 Sat Feb 5 00:13:43 EST 2000 i686 unknown
bash$ uname −s
Linux
arch
Show system architecture. Equivalent to uname −m. See Example 10−26.
bash$ arch
i686
bash$ uname −m
i686
lastcomm
Gives information about previous commands, as stored in the /var/account/pacct file.
Command name and user name can be specified by options. This is one of the GNU accounting
utilities.
lastlog
List the last login time of all system users. This references the /var/log/lastlog file.
bash$ lastlog
root tty1 Fri Dec 7 18:43:21 −0700 2001
bin **Never logged in**
daemon **Never logged in**
...
bozo tty1 Sat Dec 8 21:14:29 −0700 2001
This command will fail if the user invoking it does not have read permission for the
/var/log/lastlog file.
lsof
List open files. This command outputs a detailed table of all currently open files and gives
information about their owner, size, the processes associated with them, and more. Of course, lsof
may be piped to grep and/or awk to parse and analyze its results.
bash$ lsof
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
init 1 root mem REG 3,5 30748 30303 /sbin/init
init 1 root mem REG 3,5 73120 8069 /lib/ld−2.1.3.so
init 1 root mem REG 3,5 931668 8075 /lib/libc−2.1.3.so
cardmgr 213 root mem REG 3,5 36956 30357 /sbin/cardmgr
...
strace
System trace: diagnostic and debugging tool for tracing system calls and signals. This command and
ltrace, following, are useful for diagnosing why a given program or package fails to run . . . perhaps
due to missing libraries or related causes.
bash$ strace df
execve("/bin/df", ["df"], [/* 45 vars */]) = 0
uname({sys="Linux", node="bozo.localdomain", ...}) = 0
brk(0) = 0x804f5e4
bash$ ltrace df
__libc_start_main(0x804a910, 1, 0xbfb589a4, 0x804fb70, 0x804fb68 <unfinished ...>:
setlocale(6, "") = "en_US.UTF−8"
bindtextdomain("coreutils", "/usr/share/locale") = "/usr/share/locale"
textdomain("coreutils") = "coreutils"
__cxa_atexit(0x804b650, 0, 0, 0x8052bf0, 0xbfb58908) = 0
getenv("DF_BLOCK_SIZE") = NULL
...
nmap
Network mapper and port scanner. This command scans a server to locate open ports and the services
associated with those ports. It can also report information about packet filters and firewalls. This is an
important security tool for locking down a network against hacking attempts.
#!/bin/bash
exit 0
bash$ nc localhost.localdomain 25
220 localhost.localdomain ESMTP Sendmail 8.13.1/8.13.1; Thu, 31 Mar 2005 15:41:35 −0700
#! /bin/sh
## Duplicate DaveG's ident−scan thingie using netcat. Oooh, he'll be p*ssed.
## Args: target port [port port port ...]
## Hose stdout _and_ stderr together.
##
## Advantages: runs slower than ident−scan, giving remote inetd less cause
##+ for alarm, and only hits the few known daemon ports you specify.
## Disadvantages: requires numeric−only port args, the output sleazitude,
##+ and won't work for r−services when coming from high source ports.
# Script author: Hobbit <hobbit@avian.org>
# Used in ABS Guide with permission.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
E_BADARGS=65 # Need at least two args.
case "${2}" in
"" ) echo "Need HOST and at least one PORT." ; exit $E_BADARGS ;;
esac
TRG="$1"
shift
exit $?
# Notes:
# −−−−−
bash$ lsdev
Device DMA IRQ I/O Ports
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
cascade 4 2
dma 0080−008f
dma1 0000−001f
dma2 00c0−00df
fpu 00f0−00ff
ide0 14 01f0−01f7 03f6−03f6
...
du
Show (disk) file usage, recursively. Defaults to current working directory, unless otherwise specified.
bash$ du −ach
1.0k ./wi.sh
1.0k ./tst.sh
1.0k ./random.file
6.0k .
6.0k total
df
Shows filesystem usage in tabular form.
bash$ df
Filesystem 1k−blocks Used Available Use% Mounted on
/dev/hda5 273262 92607 166547 36% /
/dev/hda8 222525 123951 87085 59% /home
/dev/hda7 1408796 1075744 261488 80% /usr
dmesg
Lists all system bootup messages to stdout. Handy for debugging and ascertaining which device
drivers were installed and which system interrupts in use. The output of dmesg may, of course, be
parsed with grep, sed, or awk from within a script.
stat
Gives detailed and verbose statistics on a given file (even a directory or device file) or set of files.
If the target file does not exist, stat returns an error message.
vmstat
Display virtual memory statistics.
bash$ vmstat
procs memory swap io system cpu
r b w swpd free buff cache si so bi bo in cs us sy id
0 0 0 0 11040 2636 38952 0 0 33 7 271 88 8 3 89
netstat
Show current network statistics and information, such as routing tables and active connections. This
utility accesses information in /proc/net (Chapter 27). See Example 27−3.
bash$ netstat
Active Internet connections (w/o servers)
Proto Recv−Q Send−Q Local Address Foreign Address State
Active UNIX domain sockets (w/o servers)
Proto RefCnt Flags Type State I−Node Path
unix 11 [ ] DGRAM 906 /dev/log
unix 3 [ ] STREAM CONNECTED 4514 /tmp/.X11−unix/X0
unix 3 [ ] STREAM CONNECTED 4513
. . .
uptime
Shows how long the system has been running, along with associated statistics.
bash$ uptime
10:28pm up 1:57, 3 users, load average: 0.17, 0.34, 0.27
A load average of 1 or less indicates that the system handles processes immediately. A
load average greater than 1 means that processes are being queued. When the load
average gets above 3, then system performance is significantly degraded.
hostname
Lists the system's host name. This command sets the host name in an /etc/rc.d setup script
(/etc/rc.d/rc.sysinit or similar). It is equivalent to uname −n, and a counterpart to the
$HOSTNAME internal variable.
bash$ hostid
7f0100
This command allegedly fetches a "unique" serial number for a particular system.
Certain product registration procedures use this number to brand a particular user
license. Unfortunately, hostid only returns the machine network address in
hexadecimal, with pairs of bytes transposed.
This command is not part of the base Linux distribution, but may be obtained as part of the sysstat
utilities package, written by Sebastien Godard.
bash$ sar
Linux 2.4.9 (brooks.seringas.fr) 09/26/03
readelf
Show information and statistics about a designated elf binary. This is part of the binutils package.
System Logs
logger
Appends a user−generated message to the system log (/var/log/messages). You do not have to
be root to invoke logger.
# tail /var/log/message
# ...
# Jul 7 20:48:58 localhost ./test.sh[1712]: Logging at line 3.
logrotate
This utility manages the system log files, rotating, compressing, deleting, and/or e−mailing them, as
appropriate. This keeps the /var/log from getting cluttered with old log files. Usually cron runs
logrotate on a daily basis.
Job Control
ps
Process Statistics: lists currently executing processes by owner and PID (process ID). This is usually
invoked with ax or aux options, and may be piped to grep or sed to search for a specific process (see
Example 11−12 and Example 27−2).
pstree
Lists currently executing processes in "tree" format. The −p option shows the PIDs, as well as the
process names.
top
Continuously updated display of most cpu−intensive processes. The −b option displays in text mode,
so that the output may be parsed or accessed from a script.
bash$ top −b
8:30pm up 3 min, 3 users, load average: 0.49, 0.32, 0.13
45 processes: 44 sleeping, 1 running, 0 zombie, 0 stopped
CPU states: 13.6% user, 7.3% system, 0.0% nice, 78.9% idle
Mem: 78396K av, 65468K used, 12928K free, 0K shrd, 2352K buff
Swap: 157208K av, 0K used, 157208K free 37244K cached
PID USER PRI NI SIZE RSS SHARE STAT %CPU %MEM TIME COMMAND
848 bozo 17 0 996 996 800 R 5.6 1.2 0:00 top
1 root 8 0 512 512 444 S 0.0 0.6 0:04 init
2 root 9 0 0 0 0 SW 0.0 0.0 0:00 keventd
...
nice
Run a background job with an altered priority. Priorities run from 19 (lowest) to −20 (highest). Only
root may set the negative (higher) priorities. Related commands are renice, snice, and skill.
nohup
Keeps a command running even after user logs off. The command will run as a foreground process
unless followed by &. If you use nohup within a script, consider coupling it with a wait to avoid
creating an orphan or zombie process.
pidof
Identifies process ID (PID) of a running job. Since job control commands, such as kill and renice act
on the PID of a process (not its name), it is sometimes necessary to identify that PID. The pidof
command is the approximate counterpart to the $PPID internal variable.
#!/bin/bash
# kill−process.sh
NOPROCESS=2
exit 0
fuser
Identifies the processes (by PID) that are accessing a given file, set of files, or directory. May also be
invoked with the −k option, which kills those processes. This has interesting implications for system
security, especially in scripts preventing unauthorized users from accessing system services.
One important application for fuser is when physically inserting or removing storage media, such as
CD ROM disks or USB flash drives. Sometimes trying a umount fails with a device is busy error
message. This means that some user(s) and/or process(es) are accessing the device. An fuser −um
/dev/device_name will clear up the mystery, so you can kill any relevant processes.
The fuser command, invoked with the −n option identifies the processes accessing a port. This is
especially useful in combination with nmap.
cron
Administrative program scheduler, performing such duties as cleaning up and deleting system log
files and updating the slocate database. This is the superuser version of at (although each user may
have their own crontab file which can be changed with the crontab command). It runs as a daemon
and executes scheduled entries from /etc/crontab.
init
The init command is the parent of all processes. Called in the final step of a bootup, init determines
the runlevel of the system from /etc/inittab. Invoked by its alias telinit, and by root only.
telinit
Symlinked to init, this is a means of changing the system runlevel, usually done for system
maintenance or emergency filesystem repairs. Invoked only by root. This command can be dangerous
− be certain you understand it well before using!
runlevel
Shows the current and last runlevel, that is, whether the system is halted (runlevel 0), in single−user
mode (1), in multi−user mode (2 or 3), in X Windows (5), or rebooting (6). This command accesses
the /var/run/utmp file.
halt, shutdown, reboot
Command set to shut the system down, usually just prior to a power down.
service
Starts or stops a system service. The startup scripts in /etc/init.d and /etc/rc.d use this
command to start services at bootup.
Network
ifconfig
Network interface configuration and tuning utility.
bash$ ifconfig −a
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:10 errors:0 dropped:0 overruns:0 frame:0
TX packets:10 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:700 (700.0 b) TX bytes:700 (700.0 b)
The ifconfig command is most often used at bootup to set up the interfaces, or to shut them down
when rebooting.
# ...
[ −x /sbin/ifconfig ] || exit 0
# ...
for i in $interfaces ; do
if ifconfig $i 2>/dev/null | grep −q "UP" >/dev/null 2>&1 ; then
action "Shutting down interface $i: " ./ifdown $i boot
fi
# The GNU−specific "−q" option to "grep" means "quiet", i.e., producing no output.
# Redirecting output to /dev/null is therefore not strictly necessary.
# ...
bash$ route
Destination Gateway Genmask Flags MSS Window irtt Iface
pm3−67.bozosisp * 255.255.255.255 UH 40 0 0 ppp0
127.0.0.0 * 255.0.0.0 U 40 0 0 lo
default pm3−67.bozosisp 0.0.0.0 UG 40 0 0 ppp0
chkconfig
Check network configuration. This command lists and manages the network services started at bootup
in the /etc/rc?.d directory.
Originally a port from IRIX to Red Hat Linux, chkconfig may not be part of the core installation of
some Linux flavors.
tcpdump
Network packet "sniffer". This is a tool for analyzing and troubleshooting traffic on a network by
dumping packet headers that match specified criteria.
Of course, the output of tcpdump can be parsed, using certain of the previously discussed text
processing utilities.
Filesystem
mount
Mount a filesystem, usually on an external device, such as a floppy or CDROM. The file
/etc/fstab provides a handy listing of available filesystems, partitions, and devices, including
options, that may be automatically or manually mounted. The file /etc/mtab shows the currently
mounted filesystems and partitions (including the virtual ones, such as /proc).
mount −a mounts all filesystems and partitions listed in /etc/fstab, except those with a noauto
option. At bootup, a startup script in /etc/rc.d (rc.sysinit or something similar) invokes this
to get everything mounted.
# As root...
umount /mnt/cdrom
# You may now press the eject button and safely remove the disk.
The automount utility, if properly installed, can mount and unmount floppies or
CDROM disks as they are accessed or removed. On laptops with swappable floppy
and CDROM drives, this can cause problems, though.
sync
Forces an immediate write of all updated data from buffers to hard drive (synchronize drive with
buffers). While not strictly necessary, a sync assures the sys admin or user that the data just changed
will survive a sudden power failure. In the olden days, a sync; sync (twice, just to make
absolutely sure) was a useful precautionary measure before a system reboot.
At times, you may wish to force an immediate buffer flush, as when securely deleting a file (see
Example 12−55) or when the lights begin to flicker.
losetup
Sets up and configures loopback devices.
SIZE=1000000 # 1 meg
head −c $SIZE < /dev/zero > file # Set up file of designated size.
losetup /dev/loop0 file # Set it up as loopback device.
mke2fs /dev/loop0 # Create filesystem.
mount −o loop /dev/loop0 /mnt # Mount it.
# Thanks, S.C.
mkswap
Creates a swap partition or file. The swap area must subsequently be enabled with swapon.
swapon, swapoff
Enable / disable swap partitition or file. These commands usually take effect at bootup and shutdown.
mke2fs
Create a Linux ext2 filesystem. This command must be invoked as root.
#!/bin/bash
fdisk $NEWDISK
mke2fs −cv $NEWDISK1 # Check for bad blocks & verbose output.
# Note: /dev/hdb1, *not* /dev/hdb!
mkdir $MOUNTPOINT
chmod 777 $MOUNTPOINT # Makes new drive accessible to all users.
# Now, test...
# mount −t ext2 /dev/hdb1 /mnt/newdisk
# Try creating a directory.
# If it works, umount it, and proceed.
# Final step:
# Add the following line to /etc/fstab.
# /dev/hdb1 /mnt/newdisk ext2 defaults 1 1
exit 0
See also Example 13−8 and Example 28−3.
tune2fs
Tune ext2 filesystem. May be used to change filesystem parameters, such as maximum mount count.
This must be invoked as root.
This is an extremely dangerous command. Use it at your own risk, as you may
inadvertently destroy your filesystem.
dumpe2fs
Dump (list to stdout) very verbose filesystem info. This must be invoked as root.
Use this command with extreme caution. If something goes wrong, you may destroy
an existing filesystem.
fsck, e2fsck, debugfs
fsck: a front end for checking a UNIX filesystem (may invoke other utilities). The actual filesystem
type generally defaults to ext2.
debugfs: ext2 filesystem debugger. One of the uses of this versatile, but dangerous command is to
(attempt to) recover deleted files. For advanced users only!
All of these should be invoked as root, and they can damage or destroy a filesystem if
misused.
badblocks
Checks for bad blocks (physical media flaws) on a storage device. This command finds use when
formatting a newly installed hard drive or testing the integrity of backup media. [54] As an example,
badblocks /dev/fd0 tests a floppy disk.
The badblocks command may be invoked destructively (overwrite all data) or in non−destructive
read−only mode. If root user owns the device to be tested, as is generally the case, then root must
invoke this command.
lsusb, usbmodules
The lsusb command lists all USB (Universal Serial Bus) buses and the devices hooked up to them.
The usbmodules command outputs information about the driver modules for connected USB devices.
root# lsusb
Bus 001 Device 001: ID 0000:0000
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.00
bDeviceClass 9 Hub
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x0000
idProduct 0x0000
. . .
lspci
Lists pci busses present.
bash$ lspci
00:00.0 Host bridge: Intel Corporation 82845 845 (Brookdale) Chipset Host Bridge (rev 04)
00:01.0 PCI bridge: Intel Corporation 82845 845 (Brookdale) Chipset AGP Bridge (rev 04)
00:1d.0 USB Controller: Intel Corporation 82801CA/CAM USB (Hub #1) (rev 02)
00:1d.1 USB Controller: Intel Corporation 82801CA/CAM USB (Hub #2) (rev 02)
00:1d.2 USB Controller: Intel Corporation 82801CA/CAM USB (Hub #3) (rev 02)
00:1e.0 PCI bridge: Intel Corporation 82801 Mobile PCI Bridge (rev 42)
. . .
mkbootdisk
Creates a boot floppy which can be used to bring up the system if, for example, the MBR (master boot
record) becomes corrupted. The mkbootdisk command is actually a Bash script, written by Erik
Troan, in the /sbin directory.
chroot
CHange ROOT directory. Normally commands are fetched from $PATH, relative to /, the default
root directory. This changes the root directory to a different one (and also changes the working
directory to there). This is useful for security purposes, for instance when the system administrator
wishes to restrict certain users, such as those telnetting in, to a secured portion of the filesystem (this
is sometimes referred to as confining a guest user to a "chroot jail"). Note that after a chroot, the
execution path for system binaries is no longer valid.
The chroot command is also handy when running from an emergency boot floppy (chroot to
/dev/fd0), or as an option to lilo when recovering from a system crash. Other uses include
installation from a different filesystem (an rpm option) or running a readonly filesystem from a CD
ROM. Invoke only as root, and use with care.
It might be necessary to copy certain system files to a chrooted directory, since the
normal $PATH can no longer be relied upon.
lockfile
This utility is part of the procmail package (www.procmail.org). It creates a lock file, a semaphore
file that controls access to a file, device, or resource. The lock file serves as a flag that this particular
file, device, or resource is in use by a particular process ("busy"), and this permits only restricted
access (or no access) to other processes.
lockfile /home/bozo/lockfiles/$0.lock
# Creates a write−protected lockfile prefixed with the name of the script.
Lock files are used in such applications as protecting system mail folders from simultaneously being
changed by multiple users, indicating that a modem port is being accessed, and showing that an
instance of Netscape is using its cache. Scripts may check for the existence of a lock file created by a
certain process to check if that process is running. Note that if a script attempts to create a lock file
that already exists, the script will likely hang.
Normally, applications create and check for lock files in the /var/lock directory. [55] A script can
test for the presence of a lock file by something like the following.
appname=xyzip
# Application "xyzip" created lock file "/var/lock/xyzip.lock".
if [ −e "/var/lock/$appname.lock" ]
then
...
flock
Much less useful than the lockfile command is flock. It sets an "advisory" lock on a file and then
executes a command while the lock is on. This is to prevent any other process from setting a lock on
that file until completion of the specified command.
root# ./MAKEDEV
This is a sort of advanced version of mknod.
tmpwatch
Automatically deletes files which have not been accessed within a specified period of time. Usually
invoked by cron to remove stale log files.
Backup
dump, restore
The dump command is an elaborate filesystem backup utility, generally used on larger installations
and networks. [56] It reads raw disk partitions and writes a backup file in a binary format. Files to be
backed up may be saved to a variety of storage media, including disks and tape drives. The restore
command restores backups made with dump.
fdformat
Perform a low−level format on a floppy disk.
System Resources
ulimit
Sets an upper limit on use of system resources. Usually invoked with the −f option, which sets a limit
on file size (ulimit −f 1000 limits files to 1 meg maximum). The −t option limits the coredump size
(ulimit −c 0 eliminates coredumps). Normally, the value of ulimit would be set in /etc/profile
and/or ~/.bash_profile (see Appendix G).
Judicious use of ulimit can protect a system against the dreaded fork bomb.
#!/bin/bash
# This script is for illustrative purposes only.
# Run it at your own peril −− it *will* freeze your system.
exit 0 # Will not exit here, because this script will never terminate.
A ulimit −Hu XX (where XX is the user process limit) in /etc/profile would abort this
script when it exceeds the preset limit.
quota
Example 13−10. Using umask to hide an output file from prying eyes
#!/bin/bash
# rot13a.sh: Same as "rot13.sh" script, but writes output to "secure" file.
exit 0
rdev
Get info about or make changes to root device, swap space, or video mode. The functionality of rdev
has generally been taken over by lilo, but rdev remains useful for setting up a ram disk. This is a
dangerous command, if misused.
Modules
lsmod
List installed kernel modules.
bash$ lsmod
Module Size Used by
autofs 9456 2 (autoclean)
opl3 11376 0
serial_cs 5456 0 (unused)
sb 34752 0
uart401 6384 0 [sb]
sound 58368 0 [opl3 sb uart401]
soundlow 464 0 [sound]
soundcore 2800 6 [sb sound]
ds 6448 2 [serial_cs]
i82365 22928 2
pcmcia_core 45984 0 [serial_cs ds i82365]
Miscellaneous
env
Runs a program or script with certain environmental variables set or changed (without changing the
overall system environment). The [varname=xxx] permits changing the environmental variable
varname for the duration of the script. With no options specified, this command lists all the
environmental variable settings.
The first line of a script (the "sha−bang" line) may use env when the path to the
shell or interpreter is unknown.
#! /usr/bin/env perl
The default is two−second intervals, but this may be changed with the −n option.
#!/bin/sh
# −−> Comments added by the author of this document marked by "# −−>".
for i in /var/lock/subsys/*; do
# −−> Standard for/in loop, but since "do" is on same line,
# −−> it is necessary to add ";".
# Check if the script is there.
[ ! −f $i ] && continue
# −−> This is a clever use of an "and list", equivalent to:
# −−> if [ ! −f "$i" ]; then continue
Exercise 1. In /etc/rc.d/init.d, analyze the halt script. It is a bit longer than killall, but similar in
concept. Make a copy of this script somewhere in your home directory and experiment with it (do not run it as
root). Do a simulated run with the −vn flags (sh −vn scriptname). Add extensive comments. Change
the "action" commands to "echos".
Exercise 2. Look at some of the more complex scripts in /etc/rc.d/init.d. See if you can understand
parts of them. Follow the above procedure to analyze them. For some additional insight, you might also
examine the file sysvinitfiles in /usr/share/doc/initscripts−?.??, which is part of the
"initscripts" documentation.
The classic form of command substitution uses backquotes (`...`). Commands within backquotes (backticks)
generate command line text.
script_name=`basename $0`
echo "The name of this script is $script_name."
The output of commands can be used as arguments to another command, to set a variable, and even for
generating the argument list in a for loop.
textfile_listing=`ls *.txt`
# Variable contains names of all *.txt files in current working directory.
echo $textfile_listing
# Thanks, S.C.
Even when there is no word splitting, command substitution can remove trailing newlines.
Thanks, S.C.
Using echo to output an unquoted variable set with command substitution removes trailing
newlines characters from the output of the reassigned command(s). This can cause unpleasant
surprises.
dir_listing=`ls −l`
echo $dir_listing # unquoted
# Note:
# The variables may contain embedded whitespace,
if [ −f /fsckoptions ]; then
fsckoptions=`cat /fsckoptions`
...
fi
#
#
if [ −e "/proc/ide/${disk[$device]}/media" ] ; then
hdmedia=`cat /proc/ide/${disk[$device]}/media`
...
fi
#
#
if [ ! −n "`uname −r | grep −− "−"`" ]; then
ktag="`cat /proc/version`"
...
fi
#
#
if [ $usb = "1" ]; then
sleep 5
mouseoutput=`cat /proc/bus/usb/devices 2>/dev/null|grep −E "^I.*Cls=03.*Prot=02"`
kbdoutput=`cat /proc/bus/usb/devices 2>/dev/null|grep −E "^I.*Cls=03.*Prot=01"`
...
fi
Do not set a variable to the contents of a long text file unless you have a very good reason for doing so.
Do not set a variable to the contents of a binary file, even as a joke.
#!/bin/bash
# stupid−script−tricks.sh: Don't try this at home, folks.
# From "Stupid Script Tricks," Volume I.
# echo "$dangerous_variable"
# Don't try this! It would hang the script.
exit 0
Notice that a buffer overrun does not occur. This is one instance where an interpreted language, such as
Bash, provides more protection from programmer mistakes than a compiled language.
Command substitution permits setting a variable to the output of a loop. The key to this is grabbing the output
of an echo command within the loop.
#!/bin/bash
# csubloop.sh: Setting a variable to the output of a loop.
variable1=`for i in 1 2 3 4 5
do
echo −n "$i" # The 'echo' command is critical
done` #+ to command substitution here.
i=0
variable2=`while [ "$i" −lt 10 ]
do
echo −n "$i" # Again, the necessary 'echo'.
let "i += 1" # Increment.
done`
exit 0
Command substitution makes it possible to extend the toolset available to Bash. It is simply a matter of
writing a program or script that outputs to stdout (like a well−behaved UNIX tool should) and assigning
that output to a variable.
#include <stdio.h>
int main()
{
printf( "Hello, world." );
return (0);
}
bash$ gcc −o hello hello.c
#!/bin/bash
# hello.sh
greeting=`./hello`
echo $greeting
bash$ sh hello.sh
Hello, world.
#!/bin/bash
# agram2.sh
# Example of nested command substitution.
E_NOARGS=66
E_BADARG=67
MINLEN=7
if [ −z "$1" ]
then
echo "Usage $0 LETTERSET"
exit $E_NOARGS # Script needs a command−line argument.
elif [ ${#1} −lt $MINLEN ]
then
echo "Argument must have at least $MINLEN letters."
exit $E_BADARG
fi
echo
echo "${#Anagrams[*]} 7+ letter anagrams found"
echo
echo ${Anagrams[0]} # First anagram.
exit $?
Examples of command substitution in shell scripts:
1. Example 10−7
2. Example 10−26
3. Example 9−29
4. Example 12−3
5. Example 12−19
6. Example 12−15
7. Example 12−49
8. Example 10−13
9. Example 10−10
10. Example 12−29
11. Example 16−8
12. Example A−17
13. Example 27−2
14. Example 12−42
15. Example 12−43
16. Example 12−44
Variations
z=$(($z+3))
z=$((z+3)) # Also correct.
# Within double parentheses,
#+ parameter dereferencing
#+ is optional.
# You may also use operations within double parentheses without assignment.
n=0
echo "n = $n" # n = 0
(( n += 1 )) # Increment.
# (( $n += 1 )) is incorrect!
echo "n = $n" # n = 1
let z=z+3
let "z += 3" # Quotes permit the use of spaces in variable assignment.
# The 'let' operator actually performs arithmetic evaluation,
#+ rather than expansion.
Examples of arithmetic expansion in scripts:
1. Example 12−9
2. Example 10−14
3. Example 26−1
4. Example 26−11
5. Example A−17
There are always three default "files" open, stdin (the keyboard), stdout (the screen), and stderr (error
messages output to the screen). These, and any other open files, can be redirected. Redirection simply means
capturing output from a file, command, program, script, or even code block within a script (see Example 3−1
and Example 3−2) and sending it as input to another file, command, program, or script.
Each open file gets assigned a file descriptor. [61] The file descriptors for stdin, stdout, and stderr are
0, 1, and 2, respectively. For opening additional files, there remain descriptors 3 to 9. It is sometimes useful to
assign one of these additional file descriptors to stdin, stdout, or stderr as a temporary duplicate link.
[62] This simplifies restoration to normal after complex redirection and reshuffling (see Example 16−1).
COMMAND_OUTPUT >
# Redirect stdout to a file.
# Creates the file if not present, otherwise overwrites it.
: > filename
# The > truncates file "filename" to zero length.
# If file not present, creates zero−length file (same effect as 'touch').
# The : serves as a dummy placeholder, producing no output.
> filename
# The > truncates file "filename" to zero length.
# If file not present, creates zero−length file (same effect as 'touch').
# (Same result as ": >", above, but this does not work with some shells.)
COMMAND_OUTPUT >>
# Redirect stdout to a file.
# Creates the file if not present, otherwise appends to it.
# Single−line redirection commands (affect only the line they are on):
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
1>filename
# Redirect stdout to file "filename."
1>>filename
# Redirect and append stdout to file "filename."
2>filename
# Redirect stderr to file "filename."
2>>filename
# Redirect and append stderr to file "filename."
&>filename
# Redirect both stdout and stderr to file "filename."
M>N
# "M" is a file descriptor, which defaults to 1, if not explicitly set.
# "N" is a filename.
# File descriptor "M" is redirect to file "N."
M>&N
# "M" is a file descriptor, which defaults to 1, if not set.
# "N" is another file descriptor.
2>&1
# Redirects stderr to stdout.
# Error messages get sent to same place as standard output.
i>&j
# Redirects file descriptor i to j.
# All output of file pointed to by i gets sent to file pointed to by j.
>&j
# Redirects, by default, file descriptor 1 (stdout) to j.
# All stdout gets sent to file pointed to by j.
0< FILENAME
< FILENAME
# Accept input from a file.
# Companion command to ">", and often used in combination with it.
#
# grep search−word <filename
[j]<>filename
# Open file "filename" for reading and writing, and assign file descriptor "j" to it.
# If "filename" does not exist, create it.
# If file descriptor "j" is not specified, default to fd 0, stdin.
#
# An application of this is writing at a specified place in a file.
echo 1234567890 > File # Write string to "File".
exec 3<> File # Open "File" and assign fd 3 to it.
read −n 4 <&3 # Read only 4 characters.
echo −n . >&3 # Write a decimal point there.
exec 3>&− # Close fd 3.
cat File # ==> 1234.67890
# Random access, by golly.
# Note, however, that the following does *not* give the same result.
ls −yz 2>&1 >> command.log
# Outputs an error message and does not write to file.
n<&−
Close input file descriptor n.
0<&−, <&−
Close stdin.
n>&−
Close output file descriptor n.
1>&−, >&−
Close stdout.
Child processes inherit open file descriptors. This is why pipes work. To prevent an fd from being inherited,
close it.
# Thanks, S.C.
For a more detailed introduction to I/O redirection see Appendix E.
An exec <filename command redirects stdin to a file. From that point on, all stdin comes from that file,
rather than its normal source (usually keyboard input). This provides a method of reading a file line by line
and possibly parsing each line of input using sed and/or awk.
#!/bin/bash
# Redirecting stdin using 'exec'.
echo
echo "Following lines read from file."
echo "−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−"
echo $a1
echo $a2
echo
exit 0
Similarly, an exec >filename command redirects stdout to a designated file. This sends all command
output that would normally go to stdout to that file.
exec N > filename affects the entire script or current shell. Redirection in the PID of the script or shell
from that point on has changed. However . . .
N > filename affects only the newly−forked process, not the entire script or shell.
LOGFILE=logfile.txt
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− #
# All output from commands in this block sent to file $LOGFILE.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− #
exec 1>&6 6>&− # Restore stdout and close file descriptor #6.
echo
echo "== stdout now restored to default == "
echo
ls −al
echo
exit 0
Example 16−3. Redirecting both stdin and stdout in the same script with exec
#!/bin/bash
# upperconv.sh
# Converts a specified input file to uppercase.
E_FILE_ACCESS=70
E_WRONG_ARGS=71
if [ −z "$2" ]
then
echo "Need to specify output file."
echo "Usage: $0 input−file output−file"
exit $E_WRONG_ARGS
fi
exec 4<&0
exec < $1 # Will read from input file.
exec 7>&1
exec > $2 # Will write to output file.
# Assumes output file writable (add check?).
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
cat − | tr a−z A−Z # Uppercase conversion.
# ^^^^^ # Reads from stdin.
# ^^^^^^^^^^ # Writes to stdout.
# However, both stdin and stdout were redirected.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
exit 0
I/O redirection is a clever way of avoiding the dreaded inaccessible variables within a subshell problem.
#!/bin/bash
# avoid−subshell.sh
# Suggested by Matthew Walker.
Lines=0
echo
echo "−−−−−−−−−−−−−−−−−−−−−−−−"
echo
exit 0
$ cat myfile.txt
Line 1.
Line 2.
Line 3.
Line 4.
Line 5.
Line 6.
Line 7.
Line 8.
#!/bin/bash
# redir2.sh
if [ −z "$1" ]
then
Filename=names.data # Default, if no filename specified.
else
Filename=$1
fi
#+ Filename=${1:−names.data}
# can replace the above test (parameter substitution).
count=0
echo
exit 0
# However . . .
# Bash *can* sometimes start a subshell in a PIPED "while−read" loop,
#+ as distinct from a REDIRECTED "while" loop.
abc=hi
echo −e "1\n2\n3" | while read l
do abc="$l"
echo $abc
done
echo $abc
#!/bin/bash
if [ −z "$1" ]
then
Filename=names.data # Default, if no filename specified.
else
Filename=$1
fi
count=0
echo
# The original version of this script terminated the "while" loop with
#+ done <"$Filename"
# Exercise:
# Why is this unnecessary?
exit 0
#!/bin/bash
# Same as previous example, but with "until" loop.
if [ −z "$1" ]
then
Filename=names.data # Default, if no filename specified.
else
Filename=$1
fi
exit 0
#!/bin/bash
if [ −z "$1" ]
then
Filename=names.data # Default, if no filename specified.
else
Filename=$1
fi
for name in `seq $line_count` # Recall that "seq" prints sequence of numbers.
# while [ "$name" != Smith ] −− more complicated than a "while" loop −−
do
read name # Reads from $Filename, rather than stdin.
echo $name
if [ "$name" = Smith ] # Need all this extra baggage here.
then
break
exit 0
We can modify the previous example to also redirect the output of the loop.
Example 16−9. Redirected for loop (both stdin and stdout redirected)
#!/bin/bash
if [ −z "$1" ]
then
Filename=names.data # Default, if no filename specified.
else
Filename=$1
fi
line_count=`wc $Filename | awk '{ print $1 }'` # Number of lines in target file.
exit 0
#!/bin/bash
if [ −z "$1" ]
then
Filename=names.data # Default, if no filename specified.
else
Filename=$1
fi
TRUE=1
exit 0
Aristotle
Belisarius
Capablanca
Euler
Goethe
Hamurabi
Jonah
Laplace
Maroczy
Purcell
Schmidt
Semmelweiss
Smith
Turing
Venn
Wilson
Znosko−Borowski
16.3. Applications
Clever use of I/O redirection permits parsing and stitching together snippets of command output (see Example
11−7). This permits generating report and log files.
#!/bin/bash
# logevents.sh, by Stephane Chazelas.
FD_DEBUG1=3
FD_DEBUG2=4
FD_DEBUG3=5
case $LOG_LEVEL in
1) exec 3>&2 4> /dev/null 5> /dev/null;;
2) exec 3>&2 4>&2 5> /dev/null;;
3) exec 3>&2 4>&2 5>&2;;
*) exec 3> /dev/null 4> /dev/null 5> /dev/null;;
esac
FD_LOGVARS=6
if [[ $LOG_VARS ]]
then exec 6>> /var/log/vars.log
else exec 6> /dev/null # Bury output.
fi
FD_LOGEVENTS=7
if [[ $LOG_EVENTS ]]
then
# then exec 7 >(exec gawk '{print strftime(), $0}' >> /var/log/event.log)
# Above line will not work in Bash, version 2.04.
exec 7>> /var/log/event.log # Append to "event.log".
log # Write time and date.
else exec 7> /dev/null # Bury output.
fi
exit 0
A here document is a special−purpose code block. It uses a form of I/O redirection to feed a command list to
an interactive program or a command, such as ftp, cat, or the ex text editor.
COMMAND <<InputComesFromHERE
...
InputComesFromHERE
A limit string delineates (frames) the command list. The special symbol << designates the limit string. This
has the effect of redirecting the output of a file into the stdin of the program or command. It is similar to
interactive−program < command−file, where command−file contains
command #1
command #2
...
The here document alternative looks like this:
#!/bin/bash
interactive−program <<LimitString
command #1
command #2
...
LimitString
Choose a limit string sufficiently unusual that it will not occur anywhere in the command list and confuse
matters.
Note that here documents may sometimes be used to good effect with non−interactive utilities and commands,
such as, for example, wall.
#!/bin/bash
wall <<zzz23EndOfMessagezzz23
E−mail your noontime orders for pizza to the system administrator.
(Add an extra dollar for anchovy or mushroom topping.)
# Additional message text goes here.
# Note: 'wall' prints comment lines.
zzz23EndOfMessagezzz23
exit 0
Even such unlikely candidates as vi lend themselves to here documents.
E_BADARGS=65
if [ −z "$1" ]
then
echo "Usage: `basename $0` filename"
exit $E_BADARGS
fi
TARGETFILE=$1
# Bram Moolenaar points out that this may not work with 'vim',
#+ because of possible problems with terminal interaction.
exit 0
The above script could just as effectively have been implemented with ex, rather than vi. Here documents
containing a list of ex commands are common enough to form their own category, known as ex scripts.
#!/bin/bash
# Replace all instances of "Smith" with "Jones"
#+ in files with a ".txt" filename suffix.
ORIGINAL=Smith
REPLACEMENT=Jones
cat <<End−of−message
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
This is line 1 of the message.
This is line 2 of the message.
This is line 3 of the message.
This is line 4 of the message.
This is the last line of the message.
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
End−of−message
exit 0
#−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Code below disabled, due to "exit 0" above.
#!/bin/bash
# Same as previous example, but...
cat <<−ENDOFMESSAGE
This is line 1 of the message.
This is line 2 of the message.
This is line 3 of the message.
This is line 4 of the message.
This is the last line of the message.
ENDOFMESSAGE
# The output of the script will be flush left.
# Leading tab in each line will not show.
exit 0
A here document supports parameter and command substitution. It is therefore possible to pass different
parameters to the body of the here document, changing its output accordingly.
#!/bin/bash
# Another 'cat' here document, using parameter substitution.
if [ $# −ge $CMDLINEPARAM ]
then
NAME=$1 # If more than one command line param,
#+ then just take the first.
else
NAME="John Doe" # Default, if no command line parameter.
fi
cat <<Endofmessage
Endofmessage
exit 0
This is a useful script containing a here document with parameter substitution.
#!/bin/bash
# upload.sh
E_ARGERROR=65
if [ −z "$1" ]
then
echo "Usage: `basename $0` Filename−to−upload"
exit $E_ARGERROR
fi
Server="ibiblio.org"
Directory="/incoming/Linux"
# These need not be hard−coded into script,
#+ but may instead be changed to command line argument.
exit 0
Quoting or escaping the "limit string" at the head of a here document disables parameter substitution within its
body.
#!/bin/bash
# A 'cat' here document, but with parameter substitution disabled.
NAME="John Doe"
RESPONDENT="the author of this fine script"
cat <<'Endofmessage'
Endofmessage
exit 0
Disabling parameter substitution permits outputting literal text. Generating scripts or even program code is
one use for this.
#!/bin/bash
# generate−script.sh
# Based on an idea by Albert Reiner.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# 'Here document containing the body of the generated script.
(
cat <<'EOF'
#!/bin/bash
a=7
b=3
exit 0
EOF
) > $OUTFILE
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
if [ −f "$OUTFILE" ]
then
chmod 755 $OUTFILE
# Make the generated file executable.
else
echo "Problem in creating file: \"$OUTFILE\""
fi
exit 0
It is possible to set a variable from the output of a here document.
variable=$(cat <<SETVAR
This variable
runs over multiple lines.
SETVAR)
echo "$variable"
#!/bin/bash
# here−function.sh
GetPersonalData ()
{
read firstname
read lastname
read address
read city
read state
read zipcode
} # This certainly looks like an interactive function, but...
echo
echo "$firstname $lastname"
echo "$address"
echo "$city, $state $zipcode"
echo
exit 0
It is possible to use : as a dummy command accepting output from a here document. This, in effect, creates an
"anonymous" here document.
#!/bin/bash
: <<TESTVARIABLES
${HOSTNAME?}${USER?}${MAIL?} # Print error message if one of the variables not set.
TESTVARIABLES
exit 0
#!/bin/bash
: <<COMMENTBLOCK
echo "This line will not echo."
This is a comment line missing the "#" prefix.
This is another comment line missing the "#" prefix.
&*@!!++=
The above line will cause no error message,
because the Bash interpreter will ignore it.
COMMENTBLOCK
: <<DEBUGXXX
for file in *
do
cat "$file"
done
DEBUGXXX
exit 0
Yet another twist of this nifty trick makes "self−documenting" scripts possible.
#!/bin/bash
# self−document.sh: self−documenting script
# Modification of "colm.sh".
DOC_REQUEST=70
: <<DOCUMENTATIONXX
List the statistics of a specified directory in tabular format.
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
The command line parameter gives the directory to be listed.
If no directory specified or directory specified cannot be read,
then list the current working directory.
DOCUMENTATIONXX
if [ −z "$1" −o ! −r "$1" ]
then
directory=.
else
exit 0
Using a cat script is an alternate way of accomplishing this.
DOC_REQUEST=70
DOCUMENTATIONXX
exit $DOC_REQUEST
fi
See also Example A−27 for one more excellent example of a self−documenting script.
Here documents create temporary files, but these files are deleted after opening and are not accessible to
any other process.
The closing limit string, on the final line of a here document, must start in the first character position.
There can be no leading whitespace. Trailing whitespace after the limit string likewise causes
unexpected behavior. The whitespace prevents the limit string from being recognized.
#!/bin/bash
echo "−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−"
cat <<LimitString
echo "This is line 1 of the message inside the here document."
echo "This is line 2 of the message inside the here document."
echo "This is the final line of the message inside the here document."
LimitString
#^^^^Indented limit string. Error! This script will not behave as expected.
echo "−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−"
exit 0
echo "This line had better not echo." # Follows an 'exit' command.
For those tasks too complex for a "here document", consider using the expect scripting language, which is
specifically tailored for feeding input into interactive programs.
A here string can be considered as a stripped−down form of a here document. It consists of nothing more than
COMMAND <<<$WORD, where $WORD is expanded and fed to the stdin of COMMAND.
#!/bin/bash
# prepend.sh: Add text at beginning of file.
#
# Example contributed by Kenny Stauffer,
#+ and slightly modified by document author.
E_NOSUCHFILE=65
exit 0
#!/bin/bash
# Script by Francisco Lobo,
#+ and slightly modified and commented by ABS Guide author.
# Used in ABS Guide with permission. (Thank you!)
# This script will not run under Bash version < 3.0.
E_MISSING_ARG=67
if [ −z "$1" ]
then
echo "Usage: $0 mailbox−file"
exit $E_MISSING_ARG
fi
do
if [[ $mail =~ "^From " ]] # Match "From" field in message.
then
(( body = 0 )) # "Zero out" variables.
(( match = 0 ))
unset date
elif (( body ))
then
(( match ))
# echo "$mail"
# Uncomment above line if you want entire body of message to display.
case "$header" in
[Ff][Rr][Oo][Mm] ) [[ $value =~ "$2" ]] && (( match++ )) ;;
# Match "From" line.
[Dd][Aa][Tt][Ee] ) read −r −a date <<< "$value" ;;
# ^^^
# Match "Date" line.
[Rr][Ee][Cc][Ee][Ii][Vv][Ee][Dd] ) read −r −a sender <<< "$value" ;;
# ^^^
# Match IP Address (may be spoofed).
esac
fi
exit $?
# Exercises:
# −−−−−−−−−
# 1) Break the single function, above, into multiple functions,
#+ for the sake of readability.
# 2) Add additional parsing to the script, checking for various keywords.
$ mailbox_grep.sh scam_mail
−−> MESSAGE of Thu, 5 Jan 2006 08:00:56 −0500 (EST)
−−> IP address of sender: 196.3.62.4
Exercise: Find other uses for here strings.
Don't break the chain! Send out your ten copies today!
At this point, we are ready to delve into certain of the difficult and unusual aspects of scripting. Along the
way, we will attempt to "push the envelope" in various ways and examine boundary conditions (what happens
when we move into uncharted territory?).
Table of Contents
19. Regular Expressions
19.1. A Brief Introduction to Regular Expressions
19.2. Globbing
20. Subshells
21. Restricted Shells
22. Process Substitution
23. Functions
23.1. Complex Functions and Function Complexities
23.2. Local Variables
23.3. Recursion Without Local Variables
24. Aliases
25. List Constructs
26. Arrays
27. /dev and /proc
27.1. /dev
27.2. /proc
28. Of Zeros and Nulls
29. Debugging
30. Options
31. Gotchas
32. Scripting With Style
32.1. Unofficial Shell Scripting Stylesheet
33. Miscellany
33.1. Interactive and non−interactive shells and scripts
33.2. Shell Wrappers
33.3. Tests and Comparisons: Alternatives
33.4. Recursion
33.5. "Colorizing" Scripts
33.6. Optimizations
33.7. Assorted Tips
33.8. Security Issues
33.9. Portability Issues
33.10. Shell Scripting Under Windows
34. Bash, versions 2 and 3
34.1. Bash, version2
34.2. Bash, version 3
To fully utilize the power of shell scripting, you need to master Regular Expressions. Certain commands and
utilities commonly used in scripts, such as grep, expr, sed and awk interpret and use REs.
• A character set. These are the characters retaining their literal meaning. The simplest type of Regular
Expression consists only of a character set, with no metacharacters.
• An anchor. These designate (anchor) the position in the line of text that the RE is to match. For
example, ^, and $ are anchors.
• Modifiers. These expand or narrow (modify) the range of text the RE is to match. Modifiers include
the asterisk, brackets, and the backslash.
The main uses for Regular Expressions (REs) are text searches and string manipulation. An RE matches a
single character or a set of characters −− a string or a part of a string.
• The asterisk −− * −− matches any number of repeats of the character string or RE preceding it,
including zero.
"[^b−d]" matches all characters except those in the range b to d. This is an instance of ^ negating or
inverting the meaning of the following RE (taking on a role similar to ! in a different context).
Combined sequences of bracketed characters match common word patterns. "[Yy][Ee][Ss]" matches
yes, Yes, YES, yEs, and so forth. "[0−9][0−9][0−9]−[0−9][0−9]−[0−9][0−9][0−9][0−9]" matches
any Social Security number.
• The backslash −− \ −− escapes a special character, which means that character gets interpreted
literally.
A "\$" reverts back to its literal meaning of "$", rather than its RE meaning of end−of−line. Likewise
a "\\" has the literal meaning of "\".
•
Escaped "angle brackets" −− \<...\> −− mark word boundaries.
The angle brackets must be escaped, since otherwise they have only their literal character meaning.
"\<the\>" matches the word "the", but not the words "them", "there", "other", etc.
• Extended REs. Additional metacharacters added to the basic set. Used in egrep, awk, and Perl.
•
The question mark −− ? −− matches zero or one of the previous RE. It is generally used for matching
single characters.
•
The plus −− + −− matches one or more of the previous RE. It serves a role similar to the *, but does
not match zero occurrences.
# Thanks, S.C.
• Escaped "curly brackets" −− \{ \} −− indicate the number of occurrences of a preceding RE to match.
It is necessary to escape the curly brackets since they have only their literal character meaning
otherwise. This usage is technically not part of the basic RE set.
Perl and some egrep versions do not require escaping the curly brackets.
• Parentheses −− ( ) −− enclose groups of REs. They are useful with the following "|" operator and in
substring extraction using expr.
• The −− | −− "or" RE operator matches any of a set of alternate characters.
Some versions of sed, ed, and ex support escaped versions of the extended Regular Expressions
described above, as do the GNU utilities.
POSIX character classes generally require quoting or double brackets ([[ ]]).
These character classes may even be used with globbing, to a limited extent.
bash$ ls −l ?[[:digit:]][[:digit:]]?
−rw−rw−r−− 1 bozo bozo 0 Aug 21 14:47 a33b
To see POSIX character classes used in scripts, refer to Example 12−18 and Example
12−19.
Sed, awk, and Perl, used as filters in scripts, take REs as arguments when "sifting" or transforming files or I/O
streams. See Example A−12 and Example A−17 for illustrations of this.
The standard reference on this complex topic is Friedl's Mastering Regular Expressions. Sed & Awk, by
Dougherty and Robbins also gives a very lucid treatment of REs. See the Bibliography for more information
on these books.
19.2. Globbing
Bash itself cannot recognize Regular Expressions. Inside scripts, it is commands and utilities −− such as sed
and awk −− that interpret RE's.
Bash does carry out filename expansion [64] −− a process known as globbing −− but this does not use the
standard RE set. Instead, globbing recognizes and expands wildcards. Globbing interprets the standard
wildcard characters, * and ?, character lists in square brackets, and certain other special characters (such as ^
for negating the sense of a match). There are important limitations on wildcard characters in globbing,
however. Strings containing * will not match filenames that start with a dot, as, for example, .bashrc. [65]
Likewise, the ? has a different meaning in globbing than as part of an RE.
bash$ ls −l
total 2
bash$ ls −l t?.sh
−rw−rw−r−− 1 bozo bozo 466 Aug 6 17:48 t2.sh
bash$ ls −l [ab]*
−rw−rw−r−− 1 bozo bozo 0 Aug 6 18:42 a.1
−rw−rw−r−− 1 bozo bozo 0 Aug 6 18:42 b.1
bash$ ls −l [a−c]*
−rw−rw−r−− 1 bozo bozo 0 Aug 6 18:42 a.1
−rw−rw−r−− 1 bozo bozo 0 Aug 6 18:42 b.1
−rw−rw−r−− 1 bozo bozo 0 Aug 6 18:42 c.1
bash$ ls −l [^ab]*
−rw−rw−r−− 1 bozo bozo 0 Aug 6 18:42 c.1
−rw−rw−r−− 1 bozo bozo 466 Aug 6 17:48 t2.sh
−rw−rw−r−− 1 bozo bozo 758 Jul 30 09:02 test1.txt
bash$ ls −l {b*,c*,*est*}
−rw−rw−r−− 1 bozo bozo 0 Aug 6 18:42 b.1
−rw−rw−r−− 1 bozo bozo 0 Aug 6 18:42 c.1
−rw−rw−r−− 1 bozo bozo 758 Jul 30 09:02 test1.txt
Bash performs filename expansion on unquoted command−line arguments. The echo command demonstrates
this.
bash$ echo *
a.1 b.1 c.1 t2.sh test1.txt
bash$ echo t*
t2.sh test1.txt
It is possible to modify the way Bash interprets special characters in globbing. A set −f command
disables globbing, and the nocaseglob and nullglob options to shopt change globbing behavior.
See also Example 10−4.
Running a shell script launches another instance of the command processor. Just as your commands are
interpreted at the command line prompt, similarly does a script batch process a list of commands in a file.
Each shell script running is, in effect, a subprocess of the parent shell, the one that gives you the prompt at the
console or in an xterm window.
A shell script can also launch subprocesses. These subshells let the script do parallel processing, in effect
executing multiple subtasks simultaneously.
In general, an external command in a script forks off a subprocess, whereas a Bash builtin does not. For this
reason, builtins execute more quickly than their external command equivalents.
Variables in a subshell are not visible outside the block of code in the subshell. They are not accessible
to the parent process, to the shell that launched the subshell. These are, in effect, local variables.
#!/bin/bash
# subshell.sh
echo
outer_variable=Outer
(
echo "Subshell level INSIDE subshell = $BASH_SUBSHELL"
inner_variable=Inner
echo
echo "Subshell level OUTSIDE subshell = $BASH_SUBSHELL"
echo
if [ −z "$inner_variable" ]
then
echo "inner_variable undefined in main body of shell"
else
echo "inner_variable defined in main body of shell"
fi
echo
exit 0
See also Example 31−2.
Directory changes made in a subshell do not carry over to the parent shell.
#!/bin/bash
# allprofs.sh: print all user profiles
# This script written by Heiner Steven, and modified by the document author.
exit 0
A subshell may be used to set up a "dedicated environment" for a command group.
COMMAND1
COMMAND2
COMMAND3
(
IFS=:
PATH=/bin
unset TERMINFO
set −C
shift 5
COMMAND4
COMMAND5
exit 3 # Only exits the subshell.
)
# The parent shell has not been affected, and the environment is preserved.
COMMAND6
COMMAND7
One application of this is testing whether a variable is defined.
if (set −u; : $variable) 2> /dev/null
then
echo "Variable is set."
#!/bin/bash
echo
set −r
# set −−restricted has same effect.
echo "==> Now in restricted mode. <=="
echo
echo
echo
echo
echo
exit 0
<(command)
These initiate process substitution. This uses /dev/fd/<n> files to send the results of the process
within parentheses to another process. [66]
There is no space between the the "<" or ">" and the parentheses. Space there would
give an error message.
Bash creates a pipe with two file descriptors, −−fIn and fOut−−. The stdin of true connects to fOut
(dup2(fOut, 0)), then Bash passes a /dev/fd/fIn argument to echo. On systems lacking /dev/fd/<n>
files, Bash may use temporary files. (Thanks, S.C.)
Process substitution can compare the output of two different commands, or even the output of different
options to the same command.
# Output:
# Kernel IP routing table
# Destination Gateway Genmask Flags Metric Ref Use Iface
# 127.0.0.0 0.0.0.0 255.0.0.0 U 0 0 0 lo
Like "real" programming languages, Bash has functions, though in a somewhat limited implementation. A
function is a subroutine, a code block that implements a set of operations, a "black box" that performs a
specified task. Wherever there is repetitive code, when a task repeats with only slight variations, then consider
using a function.
function function_name {
command...
}
or
function_name () {
command...
}
This second form will cheer the hearts of C programmers (and is more portable).
As in C, the function's opening bracket may optionally appear on the second line.
function_name ()
{
command...
}
#!/bin/bash
JUST_A_SECOND=1
funky ()
{ # This is about as simple as functions get.
echo "This is a funky function."
echo "Now exiting funky function."
} # Function declaration must precede call.
fun ()
{ # A somewhat more complex function.
i=0
REPEATS=30
echo
echo "And now the fun really begins."
echo
funky
fun
exit 0
The function definition must precede the first call to it. There is no method of "declaring" the function, as, for
example, in C.
f1
# Will give an error message, since function "f1" not yet defined.
# However...
f1 ()
{
echo "Calling function \"f2\" from within function \"f1\"."
f2
}
f2 ()
{
echo "Function \"f2\"."
}
# Thanks, S.C.
It is even possible to nest a function within another function, although this is not very useful.
f1 ()
{
f2 () # nested
{
echo "Function \"f2\", inside \"f1\"."
}
echo
# Thanks, S.C.
Function declarations can appear in unlikely places, even where a command would otherwise go.
if [ "$USER" = bozo ]
then
bozo_greet () # Function definition embedded in an if/then construct.
{
echo "Hello, Bozo."
}
fi
bozo_greet # Works only for Bozo, and other users get an error.
# Thanks, S.C.
#!/bin/bash
# Functions and parameters
func2 () {
if [ −z "$1" ] # Is parameter #1 zero length?
then
echo "−Parameter #1 is zero length.−" # Or no parameter passed.
else
echo "−Param #1 is \"$1\".−"
fi
if [ "$2" ]
then
echo "−Parameter #2 is \"$2\".−"
fi
return 0
}
echo
exit 0
The shift command works on arguments passed to functions (see Example 33−15).
But, what about command−line arguments passed to the script? Does a function see them? Well, let's clear up
the confusion.
#!/bin/bash
# func−cmdlinearg.sh
# Call this script with a command−line argument,
#+ something like $0 arg1.
func ()
echo "============================================================"
echo
echo "Second call to function: command−line arg passed explicitly."
func $1
# Now it's seen!
exit 0
In contrast to certain other programming languages, shell scripts normally pass only value parameters to
functions. Variable names (which are actually pointers), if passed as parameters to functions, will be treated as
string literals. Functions interpret their arguments literally.
Indirect variable references (see Example 34−2) provide a clumsy sort of mechanism for passing variable
pointers to functions.
#!/bin/bash
# ind−func.sh: Passing an indirect reference to a function.
echo_var ()
{
echo "$1"
}
message=Hello
Hello=Goodbye
echo "−−−−−−−−−−−−−"
exit 0
The next logical question is whether parameters can be dereferenced after being passed to a function.
#!/bin/bash
# dereference.sh
# Dereferencing parameter passed to a function.
# Script by Bruce W. Clare.
dereference ()
{
y=\$"$1" # Name of variable.
echo $y # $Junk
Junk="Some Text"
echo $Junk "before" # Some Text before
dereference Junk
echo $Junk "after" # Some Different Text after
exit 0
#!/bin/bash
# ref−params.sh: Dereferencing a parameter passed to a function.
# (Complex Example)
my_read () {
# Called with my_read varname,
#+ outputs the previous value between brackets as the default value,
#+ then asks for a new value.
local local_var
echo
exit 0
exit status
Functions return a value, called an exit status. The exit status may be explicitly specified by a return
statement, otherwise it is the exit status of the last command in the function (0 if successful, and a
non−zero error code if not). This exit status may be used in the script by referencing it as $?. This
mechanism effectively permits script functions to have a "return value" similar to C functions.
return
Terminates a function. A return command [67] optionally takes an integer argument, which is
returned to the calling script as the "exit status" of the function, and this exit status is assigned to the
variable $?.
#!/bin/bash
# max.sh: Maximum of two integers.
max2 33 34
return_val=$?
# Exercise (easy):
# −−−−−−−−−−−−−−−
# Convert this to an interactive script,
#+ that is, have the script ask for input (two numbers).
count_lines_in_etc_passwd()
{
[[ −r /etc/passwd ]] && REPLY=$(echo $(wc −l < /etc/passwd))
# If /etc/passwd is readable, set REPLY to line count.
# Returns both a parameter value and status information.
# The 'echo' seems unnecessary, but . . .
#+ it removes excess whitespace from the output.
}
if count_lines_in_etc_passwd
then
echo "There are $REPLY lines in /etc/passwd."
else
echo "Cannot count lines in /etc/passwd."
fi
# Thanks, S.C.
#!/bin/bash
# Extending the range and otherwise improving the script is left as an exercise.
LIMIT=200
E_ARG_ERR=65
E_OUT_OF_RANGE=66
if [ −z "$1" ]
then
echo "Usage: `basename $0` number−to−convert"
exit $E_ARG_ERR
fi
num=$1
if [ "$num" −gt $LIMIT ]
then
echo "Out of range!"
exit $E_OUT_OF_RANGE
fi
return $number
# Exercise:
# −−−−−−−−
# Explain how this function works.
# Hint: division by successive subtraction.
}
echo
exit 0
See also Example 10−28.
The largest positive integer a function can return is 255. The return command is closely
tied to the concept of exit status, which accounts for this particular limitation. Fortunately,
there are various workarounds for those situations requiring a large integer return value
from a function.
#!/bin/bash
# return−test.sh
return_test 27 # o.k.
# ======================================================
return_test −151896 # Do large negative numbers work?
echo $? # Will this return −151896?
# No! It returns 168.
# Version of Bash before 2.05b permitted
#+ large negative integer return values.
# Newer versions of Bash plug this loophole.
# This may break older scripts.
# Caution!
# ======================================================
exit 0
A workaround for obtaining large integer "return values" is to simply assign the "return
value" to a global variable.
alt_return_test ()
{
fvar=$1
Return_Val=$fvar
return # Returns 0 (success).
}
alt_return_test 1
echo $? # 0
echo "return value = $Return_Val" # 1
alt_return_test 256
echo "return value = $Return_Val" # 256
alt_return_test 257
echo "return value = $Return_Val" # 257
alt_return_test 25701
echo "return value = $Return_Val" #25701
A more elegant method is to have the function echo its "return value to stdout," and then
capture it by command substitution. See the discussion of this in Section 33.7.
#!/bin/bash
# max2.sh: Maximum of two LARGE integers.
exit 0
# Exercises:
# −−−−−−−−−
# 1) Find a more elegant way of testing
#+ the parameters passed to the function.
# 2) Simplify the if/then structure at "OUTPUT."
# 3) Rewrite the script to take input from command−line parameters.
Here is another example of capturing a function "return value." Understanding it requires
some knowledge of awk.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Usage example:
month=4 # April, for example (4th month).
days_in=$(month_length $month)
echo $days_in # 30
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
See also Example A−7.
Exercise: Using what we have just learned, extend the previous Roman numerals
example to accept arbitrarily large input.
Redirection
#!/bin/bash
# realname.sh
#
# From username, gets "real name" from /etc/passwd.
file=/etc/passwd
pattern=$1
if [ $# −ne "$ARGCOUNT" ]
then
echo "Usage: `basename $0` USERNAME"
exit $E_WRONGARGS
fi
file_excerpt () # Scan file for pattern, then print relevant portion of line.
{
while read line # "while" does not necessarily need "[ condition ]"
do
echo "$line" | grep $1 | awk −F":" '{ print $5 }' # Have awk use ":" delimiter.
done
} <$file # Redirect into function's stdin.
file_excerpt $pattern
exit 0
There is an alternate, and perhaps less confusing method of redirecting a function's stdin. This
involves redirecting the stdin to an embedded bracketed code block within the function.
# Instead of:
Function ()
{
...
} < file
# Try this:
Function ()
{
{
...
} < file
}
# Similarly,
# Thanks, S.C.
local variables
A variable declared as local is one that is visible only within the block of code in which it appears. It
has local "scope". In a function, a local variable has meaning only within that function block.
#!/bin/bash
# Global and local variables inside a function.
func ()
func
echo
echo "\"loc_var\" outside function = $loc_var"
# $loc_var outside function =
# No, $loc_var not visible globally.
echo "\"global_var\" outside function = $global_var"
# $global_var outside function = 999
# $global_var is visible globally.
echo
exit 0
# In contrast to C, a Bash variable declared inside a function
#+ is local *only* if declared as such.
Before a function is called, all variables declared within the function are invisible outside the
body of the function, not just those explicitly declared as local.
#!/bin/bash
func ()
{
global_var=37 # Visible only within the function block
#+ before the function has been called.
} # END OF FUNCTION
func
echo "global_var = $global_var" # global_var = 37
# Has been set by function call.
#!/bin/bash
# factorial
# −−−−−−−−−
MAX_ARG=5
E_WRONG_ARGS=65
E_RANGE_ERR=66
if [ −z "$1" ]
then
echo "Usage: `basename $0` number"
exit $E_WRONG_ARGS
fi
fact ()
{
local number=$1
# Variable "number" must be declared as local,
#+ otherwise this doesn't work.
if [ "$number" −eq 0 ]
then
factorial=1 # Factorial of 0 = 1.
else
let "decrnum = number − 1"
fact $decrnum # Recursive function call (the function calls itself).
let "factorial = $number * $?"
fi
return $factorial
}
fact $1
echo "Factorial of $1 is $?."
exit 0
See also Example A−16 for an example of recursion in a script. Be aware that recursion is resource−intensive
and executes slowly, and is therefore generally not appropriate to use in a script.
#! /bin/bash
#
#=================================================================#
# The Tower of Hanoi is a mathematical puzzle attributed to
#+ Edouard Lucas, a nineteenth−century French mathematician.
#
# There are three vertical posts set in a base.
# The first post has a set of annular rings stacked on it.
# These rings are flat disks with a hole drilled out of the center,
#+ so they can slip over the posts.
# The rings have different diameters, and they stack in descending
#+ order, according to size.
# The smallest ring is on top, and the largest on the bottom.
#
# The task is to transfer the stack of rings
#+ to one of the other posts.
# You can move only one ring at a time to another post.
# You are permitted to move rings back to the original post.
# You may place a smaller ring atop a larger one,
#+ but *not* vice versa.
# Again, it is forbidden to place a larger ring atop a smaller one.
#
# For a small number of rings, only a few moves are required.
#+ For each additional ring,
#+ the required number of moves approximately doubles,
#+ and the "strategy" becomes increasingly complicated.
#
# For more information, see http://hanoi.kernelthread.com.
#
#
# ... ... ...
# | | | | | |
# _|_|_ | | | |
# |_____| | | | |
# |_______| | | | |
# |_________| | | | |
# |___________| | | | |
# | | | | | |
# .−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−.
# |**************************************************************|
# #1 #2 #3
#
#=================================================================#
case $# in
1)
case $(($1>0)) in # Must have at least one disk.
1)
dohanoi $1 1 3 2
echo "Total moves = $Moves"
exit 0;
;;
*)
echo "$0: illegal value for number of disks";
exit $E_BADPARAM;
;;
esac
;;
*)
echo "usage: $0 N"
echo " Where \"N\" is the number of disks."
exit $E_NOPARAM;
;;
esac
# Exercises:
# −−−−−−−−−
# 1) Would commands beyond this point ever be executed?
# Why not? (Easy)
# 2) Explain the workings of the workings of the "dohanoi" function.
# (Difficult)
A Bash alias is essentially nothing more than a keyboard shortcut, an abbreviation, a means of avoiding
typing a long command sequence. If, for example, we include alias lm="ls −l | more" in the ~/.bashrc
file, then each lm typed at the command line will automatically be replaced by a ls −l | more. This can save a
great deal of typing at the command line and avoid having to remember complex combinations of commands
and options. Setting alias rm="rm −i" (interactive mode delete) may save a good deal of grief, since it can
prevent inadvertently losing important files.
In a script, aliases have very limited usefulness. It would be quite nice if aliases could assume some of the
functionality of the C preprocessor, such as macro expansion, but unfortunately Bash does not expand
arguments within the alias body. [70] Moreover, a script fails to expand an alias itself within "compound
constructs", such as if/then statements, loops, and functions. An added limitation is that an alias will not
expand recursively. Almost invariably, whatever we would like an alias to do could be accomplished much
more effectively with a function.
#!/bin/bash
# alias.sh
shopt −s expand_aliases
# Must set this option, else script will not expand aliases.
echo
directory=/usr/X11R6/bin/
prefix=mk* # See if wild card causes problems.
echo "Variables \"directory\" + \"prefix\" = $directory$prefix"
echo
TRUE=1
if [ TRUE ]
then
alias rr="ls −l"
echo "Trying aliased \"rr\" within if/then statement:"
rr /usr/X11R6/bin/mk* #* Error message results!
# Aliases not expanded within compound statements.
echo "However, previously expanded alias still recognized:"
ll /usr/X11R6/bin/mk*
fi
echo
count=0
while [ $count −lt 3 ]
do
alias rrr="ls −l"
echo "Trying aliased \"rrr\" within \"while\" loop:"
rrr /usr/X11R6/bin/mk* #* Alias will not expand here either.
# alias.sh: line 57: rrr: command not found
let count+=1
done
echo; echo
exit 0
The unalias command removes a previously set alias.
#!/bin/bash
# unalias.sh
echo
exit 0
bash$ ./unalias.sh
total 6
drwxrwxr−x 2 bozo bozo 3072 Feb 6 14:04 .
drwxr−xr−x 40 bozo bozo 2048 Feb 6 14:04 ..
The "and list" and "or list" constructs provide a means of processing a number of commands consecutively.
These can effectively replace complex nested if/then or even case statements.
and list
#!/bin/bash
# "and list"
if [ ! −z "$1" ] && echo "Argument #1 = $1" && [ ! −z "$2" ] && echo "Argument #2 = $2"
then
echo "At least 2 arguments passed to script."
# All the chained commands return true.
else
echo "Less than 2 arguments passed to script."
# At least one of the chained commands returns false.
fi
# Note that "if [ ! −z $1 ]" works, but its supposed equivalent,
# if [ −n $1 ] does not.
# However, quoting fixes this.
# if [ −n "$1" ] works.
# Careful!
# It is always best to QUOTE tested variables.
exit 0
#!/bin/bash
test $# −ne $ARGS && echo "Usage: `basename $0` $ARGS argument(s)" && exit $E_BADARGS
# If condition 1 tests true (wrong number of args passed to script),
#+ then the rest of the line executes, and script terminates.
exit 0
#!/bin/bash
E_BADARGS=65
if [ −z "$1" ]
then
echo "Usage: `basename $0` filename"
exit $E_BADARGS # No arg? Bail out.
else
file=$1 # Set filename.
fi
exit 0
# ==> The following snippets from the /etc/rc.d/init.d/single script by Miquel van Smoorenburg
#+==> illustrate use of "and" and "or" lists.
# ==> "Arrowed" comments added by document author.
# ==> . . .
# If they want to run something in single user mode, might as well run it...
for i in /etc/rc1.d/S[0−9][0−9]* ; do
# Check if the script is there.
[ −x "$i" ] || continue
# ==> If corresponding file in $PWD *not* found,
#+==> then "continue" by jumping to the top of the loop.
# ==> . . .
The exit status of an and list or an or list is the exit status of the last command executed.
Clever combinations of "and" and "or" lists are possible, but the logic may easily become convoluted and
require extensive debugging.
# Same result as
( false && true ) || echo false # false
# But *not*
false && ( true || echo false ) # (nothing echoed)
# It's best to avoid such complexities, unless you know what you're doing.
# Thanks, S.C.
See Example A−7 and Example 7−4 for illustrations of using an and / or list to test variables.
Newer versions of Bash support one−dimensional arrays. Array elements may be initialized with the
variable[xx] notation. Alternatively, a script may introduce the entire array by an explicit declare −a
variable statement. To dereference (find the contents of) an array element, use curly bracket notation, that
is, ${variable[xx]}.
#!/bin/bash
area[11]=23
area[13]=37
area[51]=UFOs
echo
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Another array, "area2".
# Another way of assigning array variables...
# array_name=( XXX YYY ZZZ ... )
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Yet another array, "area3".
# Yet another way of assigning array variables...
# array_name=([xx]=XXX [yy]=YYY ...)
area3=([17]=seventeen [24]=twenty−four)
exit 0
Bash permits array operations on variables, even if the variables are not explicitly
declared as arrays.
string=abcABC123ABCabc
echo ${string[@]} # abcABC123ABCabc
echo ${string[*]} # abcABC123ABCabc
echo ${string[0]} # abcABC123ABCabc
echo ${string[1]} # No output!
# Why?
echo ${#string[@]} # 1
# One element in the array.
# The string itself.
#!/bin/bash
# poem.sh: Pretty−prints one of the document author's favorite poems.
# Attribution.
Attrib[1]=" Wallace Stevens"
echo
echo
exit 0
# Exercise:
# −−−−−−−−
# Modify this script to pretty−print a poem from a text data file.
Array variables have a syntax all their own, and even standard Bash commands and operators have special
options adapted for array use.
#!/bin/bash
# array−ops.sh: More fun with arrays.
echo "−−−−−−−−−−−−−−"
echo ${#array[0]} # 4
# Length of first element of array.
echo ${#array} # 4
# Length of first element of array.
# (Alternate notation)
echo ${#array[1]} # 3
# Length of second element of array.
# Arrays in Bash have zero−based indexing.
echo ${#array[*]} # 6
# Number of elements in array.
echo ${#array[@]} # 6
# Number of elements in array.
exit 0
Many of the standard string operations work on arrays.
#!/bin/bash
# array−strops.sh: String operations on arrays.
# Script by Michael Zick.
# Used with permission.
echo
echo "−−−−−−−−−−−−−−−−−−−−−−−"
# Substring Removal
# Removes shortest match from front of string(s),
#+ where the substring is a regular expression.
echo "−−−−−−−−−−−−−−−−−−−−−−−"
# Substring Replacement
echo "−−−−−−−−−−−−−−−−−−−−−−−"
newstr() {
echo −n "!!!"
}
echo ${arrayZ[@]/%e/$(newstr)}
# on!!! two thre!!! four fiv!!! fiv!!!
# Q.E.D: The replacement action is an 'assignment.'
echo
exit 0
Command substitution can construct the individual elements of an array.
#!/bin/bash
# script−array.sh: Loads this script into an array.
# Inspired by an e−mail from Chris Martin (thanks!).
echo
exit 0
# Exercise:
# −−−−−−−−
# Modify this script so it lists itself
#+ in its original format,
#+ complete with whitespace, line breaks, etc.
In an array context, some Bash builtins have a slightly altered meaning. For example, unset deletes array
elements, or even an entire array.
#!/bin/bash
declare −a colors
# All subsequent commands in this script will treat
#+ the variable "colors" as an array.
echo "Enter your favorite colors (separated from each other by a space)."
echo
element_count=${#colors[@]}
# Special syntax to extract number of elements in array.
# element_count=${#colors[*]} works also.
#
# The "@" variable allows word splitting within quotes
#+ (extracts variables separated by whitespace).
#
# This corresponds to the behavior of "$@" and "$*"
#+ in positional parameters.
echo
# Again, list all the elements in the array, but using a more elegant method.
echo ${colors[@]} # echo ${colors[*]} also works.
echo
exit 0
As seen in the previous example, either ${array_name[@]} or ${array_name[*]} refers to all the elements
of the array. Similarly, to get a count of the number of elements in an array, use either ${#array_name[@]}
or ${#array_name[*]}. ${#array_name} is the length (number of characters) of ${array_name[0]}, the first
element of the array.
#!/bin/bash
# empty−array.sh
# ===================================================================
ListArray
ListArray
# or
array0[${#array0[*]}]="new2"
array1[${#array1[*]}]="new2"
array2[${#array2[*]}]="new2"
ListArray
ListArray
# Replacement:
array4=( ${array0[@]/second/2nd} )
echo
echo "Elements in array4: ${array4[@]}"
array7=( ${array0[@]#new1} )
echo # After array6 this should not be a surprise.
echo "Elements in array7: ${array7[@]}"
zap='new*'
array9=( ${array0[@]/$zap/} )
echo
echo "Elements in array9: ${array9[@]}"
exit 0
The relationship of ${array_name[@]} and ${array_name[*]} is analogous to that between $@ and $*. This
powerful array notation has a number of uses.
# Copying an array.
array2=( "${array1[@]}" )
# or
array2="${array1[@]}"
# Thanks, S.C.
The array=( element1 element2 ... elementN ) initialization operation, with the help of command
substitution, makes it possible to load the contents of a text file into an array.
#!/bin/bash
filename=sample_file
# cat sample_file
#
# 1 a b c
# 2 d e fg
declare −a array1
element_count=${#array1[*]}
echo $element_count # 8
Clever scripting makes it possible to add array operations.
#! /bin/bash
# array−assign.bash
# set −vx
echo
echo '− − testing: =( ${array[@]} ) − −'
times
declare −a bigTwo=( ${bigOne[@]} )
# ^ ^
times
echo
echo '− − testing: =${array[@]} − −'
times
declare −a bigThree=${bigOne[@]}
# No parentheses this time.
times
# Comparing the numbers shows that the second form, pointed out
#+ by Stephane Chazelas, is from three to four times faster.
#
# William Park explains:
#+ The bigTwo array assigned as single string, whereas
#+ bigThree assigned element by element.
# So, in essence, you have:
# bigTwo=( [0]="... ... ..." )
# bigThree=( [0]="..." [1]="..." [2]="..." ... )
# Note:
# −−−−
# The "declare −a" statements in lines 31 and 43
#+ are not strictly necessary, since it is implicit
#+ in the Array=( ... ) assignment form.
# However, eliminating these declarations slows down
#+ the execution of the following sections of the script.
exit 0
#! /bin/bash
# CopyArray.sh
#
# This script written by Michael Zick.
# Used here with permission.
CpArray_Mac() {
Hype()
{
local −a TMP
local −a hype=( Really Rocks )
$($CopyArray $1 TMP)
TMP=( ${TMP[@]} ${hype[@]} )
$($CopyArray TMP $2)
}
exit 0
#! /bin/bash
# array−append.bash
# Subscript packed.
declare −a array1=( zero1 one1 two1 )
# Subscript sparse ([1] is not defined).
declare −a array2=( [0]=zero2 [2]=two2 [3]=three2 )
echo
echo '− Confirm that the array is really subscript sparse. −'
echo "Number of elements: 4" # Hard−coded for illustration.
for (( i = 0 ; i < 4 ; i++ ))
do
echo "Element [$i]: ${array2[$i]}"
done
# See also the more general code example in basics−reviewed.bash.
declare −a dest
declare −a subArray=${dest[1]}
cnt=${#subArray[@]}
exit 0
−−
Arrays permit deploying old familiar algorithms as shell scripts. Whether this is necessarily a good idea is left
to the reader to decide.
#!/bin/bash
# bubble.sh: Bubble sort, of sorts.
exchange()
{
# Swaps two members of the array.
local temp=${Countries[$1]} # Temporary storage
#+ for element getting swapped out.
Countries[$1]=${Countries[$2]}
Countries[$2]=$temp
return
}
number_of_elements=${#Countries[@]}
let "comparisons = $number_of_elements − 1"
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Paulo Marcel Coelho Aragao suggests for−loops as a simpler altenative.
#
# for (( last = $number_of_elements − 1 ; last > 1 ; last−− ))
# do
# for (( i = 0 ; i < last ; i++ ))
# do
# [[ "${Countries[$i]}" > "${Countries[$((i+1))]}" ]] \
# && exchange $i $((i+1))
# done
# done
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
echo
echo "$count: ${Countries[@]}" # Print resultant array at end of each pass.
echo
let "count += 1" # Increment pass count.
exit 0
−−
#!/bin/bash
# "Nested" array.
exit 0
−−
Embedded arrays in combination with indirect references create some fascinating possibilities
#!/bin/bash
# embedded−arrays.sh
# Embedded arrays and indirect references.
ARRAY1=(
VAR1_1=value11
VAR1_2=value12
VAR1_3=value13
)
ARRAY2=(
VARIABLE="test"
STRING="VAR1=value1 VAR2=value2 VAR3=value3"
ARRAY21=${ARRAY1[*]}
) # Embed ARRAY1 within this second array.
function print () {
OLD_IFS="$IFS"
IFS=$'\n' # To print each array element
#+ on a separate line.
TEST1="ARRAY2[*]"
local ${!TEST1} # See what happens if you delete this line.
# Indirect reference.
# This makes the components of $TEST1
#+ accessible to this function.
# Print variable
echo "Variable VARIABLE: $VARIABLE"
print
echo
exit 0
Arrays enable implementing a shell script version of the Sieve of Eratosthenes. Of course, a
resource−intensive application of this nature should really be written in a compiled language, such as C. It
runs excruciatingly slowly as a script.
#!/bin/bash
# sieve.sh (ex68.sh)
# Sieve of Eratosthenes
# Ancient algorithm for finding prime numbers.
PRIME=1
NON_PRIME=0
let SPLIT=UPPER_LIMIT/2
# Optimization:
# Need to test numbers only halfway to upper limit (why?).
declare −a Primes
# Primes[] is an array.
initialize ()
{
# Initialize the array.
i=$LOWER_LIMIT
until [ "$i" −gt "$UPPER_LIMIT" ]
do
Primes[i]=$PRIME
let "i += 1"
done
# Assume all array members guilty (prime)
#+ until proven innocent.
}
print_primes ()
{
# Print out the members of the Primes[] array tagged as prime.
i=$LOWER_LIMIT
done
let i=$LOWER_LIMIT+1
# We know 1 is prime, so let's start with 2.
t=$i
fi
# ==============================================
# main ()
# Invoke the functions sequentially.
initialize
sift
print_primes
# This is what they call structured programming.
# ==============================================
echo
exit 0
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− #
# Code below line will not execute, because of 'exit.'
i=1
until (( ( i += 1 ) > SPLIT )) # Need check only halfway.
do
if [[ −n $Primes[i] ]]
then
t=$i
until (( ( t += i ) > UPPER_LIMIT ))
do
Primes[t]=
done
fi
done
echo ${Primes[*]}
exit 0
Compare this array−based prime number generator with an alternative that does not use arrays, Example
A−16.
−−
Arrays lend themselves, to some extent, to emulating data structures for which Bash has no native support.
#!/bin/bash
# stack.sh: push−down stack simulation
declare −a stack
return
}
Data=${stack[$SP]}
let "SP += 1" # Bump stack pointer.
return
}
# =======================================================
# Now, for some fun.
echo
echo
push garbage
pop
status_report # Garbage in, garbage out.
pop # FINAL
status_report
pop # skidoo
status_report
pop # 23
status_report # Last−in, first−out!
echo
exit 0
# =======================================================
# Exercises:
# −−−−−−−−−
Fancy manipulation of array "subscripts" may require intermediate variables. For projects involving this,
again consider using a more powerful programming language, such as Perl or C.
#!/bin/bash
# Q(1) = Q(2) = 1
# Q(n) = Q(n − Q(n−1)) + Q(n − Q(n−2)), for n>2
echo
echo "Q−series [$LIMIT terms]:"
echo −n "${Q[1]} " # Output first two terms.
echo −n "${Q[2]} "
done
echo
exit 0
Bash supports only one−dimensional arrays, though a little trickery permits simulating multi−dimensional
ones.
#!/bin/bash
# twodim.sh: Simulating a two−dimensional array.
Rows=5
Columns=5
# 5 X 5 Array.
load_alpha ()
{
local rc=0
local index
for i in A B C D E F G H I J K L M N O P Q R S T U V W X Y
do # Use different symbols if you like.
local row=`expr $rc / $Columns`
local column=`expr $rc % $Rows`
let "index = $row * $Rows + $column"
alpha[$index]=$i
# alpha[$row][$column]
let "rc += 1"
done
# Simpler would be
#+ declare −a alpha=( A B C D E F G H I J K L M N O P Q R S T U V W X Y )
#+ but this somehow lacks the "flavor" of a two−dimensional array.
}
print_alpha ()
{
local row=0
local index
echo
done
echo
}
if [[ "$1" −ge 0 && "$1" −lt "$Rows" && "$2" −ge 0 && "$2" −lt "$Columns" ]]
then
let "index = $1 * $Rows + $2"
# Now, print it rotated.
echo −n " ${alpha[index]}"
# alpha[$row][$column]
fi
if [ "$row" −ge 0 ]
then
let "t1 = $column − $row"
let "t2 = $column"
else
let "t1 = $column"
let "t2 = $column + $row"
fi
echo; echo
done
exit 0
# Exercises:
# −−−−−−−−−
# 1) Rewrite the array loading and printing functions
# in a more intuitive and less kludgy fashion.
#
# 2) Figure out how the array rotation functions work.
# Hint: think about the implications of backwards−indexing an array.
#
# 3) Rewrite this script to handle a non−square array,
# such as a 6 X 4 one.
# Try to minimize "distortion" when the array is rotated.
A two−dimensional array is essentially equivalent to a one−dimensional one, but with additional addressing
modes for referencing and manipulating the individual elements by row and column position.
For an even more elaborate example of simulating a two−dimensional array, see Example A−10.
−−
• Example 14−3
A Linux or UNIX machine typically has the /dev and /proc special−purpose directories.
27.1. /dev
The /dev directory contains entries for the physical devices that may or may not be present in the hardware.
[71] The hard drive partitions containing the mounted filesystem(s) have entries in /dev, as a simple df
shows.
bash$ df
Filesystem 1k−blocks Used Available Use%
Mounted on
/dev/hda6 495876 222748 247527 48% /
/dev/hda1 50755 3887 44248 9% /boot
/dev/hda8 367013 13262 334803 4% /home
/dev/hda5 1714416 1123624 503704 70% /usr
Among other things, the /dev directory also contains loopback devices, such as /dev/loop0. A loopback
device is a gimmick that allows an ordinary file to be accessed as if it were a block device. [72] This enables
mounting an entire filesystem within a single large file. See Example 13−8 and Example 13−7.
A few of the pseudo−devices in /dev have other specialized uses, such as /dev/null, /dev/zero,
/dev/urandom, /dev/sda1, /dev/udp, and /dev/tcp.
For instance:
To mount a USB flash drive, append the following line to /etc/fstab. [73]
Downloading a URL:
#!/bin/bash
# dev−tcp.sh: /dev/tcp redirection to check Internet connection.
: <<EXPLANATION
If bash was compiled with −−enable−net−redirections, it has the capability of
using a special character device for both TCP and UDP redirections. These
redirections are used identically as STDIN/STDOUT/STDERR. The device entries
are 30,36 for /dev/tcp:
mknod /dev/tcp c 30 36
exit $MYEXIT
27.2. /proc
The /proc directory is actually a pseudo−filesystem. The files in /proc mirror currently running system
and kernel processes and contain information and statistics about them.
Block devices:
1 ramdisk
2 fd
3 ide0
9 md
3 0 3007872 hda 4472 22260 114520 94240 3551 18703 50384 549710 0 111550 644030
3 1 52416 hda1 27 395 844 960 4 2 14 180 0 800 1140
3 2 1 hda2 0 0 0 0 0 0 0 0 0 0 0
3 4 165280 hda4 10 0 20 210 0 0 0 0 0 210 210
...
Shell scripts may extract data from certain of the files in /proc. [75]
if [ $CPU = Pentium ]
then
run_some_commands
...
else
run_different_commands
...
fi
if [ "$bus_speed" = "$USB1" ]
then
echo "USB 1.1 port found."
# Do something appropriate for USB 1.1.
fi
The /proc directory contains subdirectories with unusual numerical names. Every one of these names maps
to the process ID of a currently running process. Within each of these subdirectories, there are a number of
files that hold useful information about the corresponding process. The stat and status files keep running
statistics on the process, the cmdline file holds the command−line arguments the process was invoked with,
and the exe file is a symbolic link to the complete path name of the invoking process. There are a few more
such files, but these seem to be the most interesting from a scripting standpoint.
#!/bin/bash
# pid−identifier.sh: Gives complete path name to process associated with pid.
if [ $# −ne $ARGNO ]
then
echo "Usage: `basename $0` PID−number" >&2 # Error message >stderr.
exit $E_WRONGARGS
fi
if [ −z "$pidno" ] # If, after all the filtering, the result is a zero−length string,
then # no running process corresponds to the pid given.
echo "No such process running."
exit $E_NOSUCHPROCESS
fi
# Alternatively:
# if ! ps $1 > /dev/null 2>&1
# then # no running process corresponds to the pid given.
# echo "No such process running."
# exit $E_NOSUCHPROCESS
# fi
exit 0
#!/bin/bash
pidno=$( ps ax | grep −v "ps ax" | grep −v grep | grep $PROCNAME | awk '{ print $1 }' )
# Finding the process number of 'pppd', the 'ppp daemon'.
# Have to filter out the process lines generated by the search itself.
#
# However, as Oleg Philon points out,
if [ ! −e "/proc/$pidno/$PROCFILENAME" ]
# While process running, then "status" file exists.
then
echo "Disconnected."
exit $NOTCONNECTED
fi
sleep $INTERVAL
echo; echo
done
exit 0
# Exercises:
# −−−−−−−−−
# Improve the script so it exits on a "q" keystroke.
# Make the script more user−friendly in other ways.
In general, it is dangerous to write to the files in /proc, as this can corrupt the filesystem or crash the
machine.
Uses of /dev/null
Think of /dev/null as a "black hole." It is the nearest equivalent to a write−only file. Everything
written to it disappears forever. Attempts to read or output from it result in nothing. Nevertheless,
/dev/null can be quite useful from both the command line and in scripts.
Suppressing stdout.
rm $badname 2>/dev/null
# So error messages [stderr] deep−sixed.
Suppressing output from both stdout and stderr.
ln −s /dev/null ~/.netscape/cookies
# All cookies now get sent to a black hole, rather than saved to disk.
Uses of /dev/zero
Like /dev/null, /dev/zero is a pseudo file, but it actually produces a stream of nulls (binary
zeros, not the ASCII kind). Output written to it disappears, and it is fairly difficult to actually read the
#!/bin/bash
# Creating a swapfile.
FILE=/swap
BLOCKSIZE=1024
MINBLOCKS=40
SUCCESS=0
exit $SUCCESS
Another application of /dev/zero is to "zero out" a file of a designated size for a special purpose,
such as mounting a filesystem on a loopback device (see Example 13−8) or "securely" deleting a file
(see Example 12−55).
#!/bin/bash
# ramdisk.sh
MOUNTPT=/mnt/ramdisk
SIZE=2000 # 2K blocks (change as appropriate)
BLOCKSIZE=1024 # 1K (1024 byte) block size
DEVICE=/dev/ram0 # First ram device
username=`id −nu`
if [ "$username" != "$ROOTUSER_NAME" ]
then
echo "Must be root to run \"`basename $0`\"."
exit $E_NON_ROOT_USER
fi
exit 0
In addition to all the above, /dev/zero is needed by ELF binaries.
#!/bin/bash
# ex74.sh
a=37
if [$a −gt 27 ]
then
echo $a
fi
exit 0
Output from script:
#!/bin/bash
# missing−keyword.sh: What error message will this generate?
for a in 1 2 3
do
echo "$a"
# done # Required keyword 'done' commented out in line 7.
exit 0
Output from script:
Note that the error message does not necessarily reference the line in which the error occurs, but the line
where the Bash interpreter finally becomes aware of the error.
Error messages may disregard comment lines in a script when reporting the line number of a syntax error.
What if the script executes, but does not work as expected? This is the all too familiar logic error.
#!/bin/bash
# Try this:
# echo "$badname"
rm "$badname"
exit 0
Try to find out what's wrong with Example 29−3 by uncommenting the echo "$badname" line. Echo
statements are useful for seeing whether what you expect is actually what you get.
In this particular case, rm "$badname" will not give the desired results because $badname should not be
quoted. Placing it in quotes ensures that rm has only one argument (it will match only one filename). A partial
fix is to remove to quotes from $badname and to reset $IFS to contain only a newline, IFS=$'\n'.
However, there are simpler ways of going about it.
1. echo statements at critical points in the script to trace the variables, and otherwise give a snapshot of
what is going on.
DEBUG=on
Whatever=whatnot
debecho $Whatever # whatnot
DEBUG=
Whatever=notwhat
debecho $Whatever # (Will not echo.)
2. using the tee filter to check processes or data flows at critical points.
3. setting option flags −n −v −x
sh −n scriptname checks for syntax errors without actually running the script. This is the
equivalent of inserting set −n or set −o noexec into the script. Note that certain types of
syntax errors can slip past this check.
sh −v scriptname echoes each command before executing it. This is the equivalent of inserting
set −v or set −o verbose in the script.
The −n and −v flags work well together. sh −nv scriptname gives a verbose syntax check.
sh −x scriptname echoes the result each command, but in an abbreviated manner. This is the
equivalent of inserting set −x or set −o xtrace in the script.
Inserting set −u or set −o nounset in the script runs it, but gives an unbound variable error
message at each attempt to use an undeclared variable.
4. Using an "assert" function to test a variable or condition at critical points in a script. (This is an idea
borrowed from C.)
#!/bin/bash
# assert.sh
lineno=$2
if [ ! $1 ]
then
echo "Assertion failed: \"$1\""
echo "File \"$0\", line $lineno"
exit $E_ASSERT_FAILED
# else
# return
# and continue executing script.
fi
}
a=5
b=4
condition="$a −lt $b" # Error message and exit from script.
# Some commands.
# ...
echo "This statement echoes only if the \"assert\" does not fail."
# ...
# Some more commands.
exit 0
5. Using the $LINENO variable and the caller builtin.
6. trapping at exit.
The exit command in a script triggers a signal 0, terminating the process, that is, the script itself. [77]
It is often useful to trap the exit, forcing a "printout" of variables, for example. The trap must be the
first command in the script.
Trapping signals
trap
Specifies an action on receipt of a signal; also useful for debugging.
A signal is simply a message sent to a process, either by the kernel or another process,
telling it to take some specified action (usually to terminate). For example, hitting a
Control−C, sends a user interrupt, an INT signal, to a running program.
trap '' 2
# Ignore interrupt 2 (Control−C), with no action specified.
#!/bin/bash
# Hunting variables with a trap.
a=39
b=36
exit 0
# Note that commenting out the 'exit' command makes no difference,
#!/bin/bash
# logon.sh: A quick 'n dirty script to check whether you are on−line yet.
umask 177 # Make sure temp files are not world readable.
TRUE=1
LOGFILE=/var/log/messages
# Note that $LOGFILE must be readable
#+ (as root, chmod 644 /var/log/messages).
TEMPFILE=temp.$$
# Create a "unique" temp file name, using process id of the script.
# Using 'mktemp' is an alternative.
# For example:
# TEMPFILE=`mktemp temp.XXXXXX`
KEYWORD=address
# At logon, the line "remote IP address xxx.xxx.xxx.xxx"
# appended to /var/log/messages.
ONLINE=22
USER_INTERRUPT=13
CHECK_LINES=100
# How many lines in log file to check.
echo
sleep 1
done
exit 0
while true
do ifconfig ppp0 | grep UP 1> /dev/null && echo "connected" && exit 0
echo −n "." # Prints dots (.....) until connected.
sleep 2
done
CHECK_INTERVAL=1
The DEBUG argument to trap causes a specified action to execute after every command in a script. This
permits tracing variables, for example.
#!/bin/bash
variable=29
exit $?
Output of script:
#!/bin/bash
# parent.sh
# Running multiple processes on an SMP box.
# Author: Tedman Eng
function start_thread() {
if [ $PROCID −le $LIMIT ] ; then
./child.sh $PROCID&
let "PROCID++"
else
echo "Limit reached."
wait
exit
fi
}
while true
do
done
exit 0
#!/bin/bash
temp=$RANDOM
index=$1
shift
let "temp %= 5"
let "temp += 4"
echo "Starting $index Time:$temp" "$@"
sleep ${temp}
echo "Ending $index"
kill −s SIGRTMIN $PPID
exit 0
# ===================================================================== #
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−#
#################################################################
# The following is the original script written by Vernia Damiano.
# Unfortunately, it doesn't work properly.
#################################################################
#!/bin/bash
function avvia() {
local temp
local index
temp=$RANDOM
index=$1
shift
let "temp %= $TEMPO"
let "temp += 1"
echo "Starting $index Time:$temp" "$@"
sleep ${temp}
echo "Ending $index"
kill −s SIGRTMIN $$
}
function parti() {
if [ $INDICE −gt 0 ] ; then
avvia $INDICE "${PARAMETRI[@]}" &
let "INDICE−−"
else
trap : SIGRTMIN
fi
}
wait
trap − SIGRTMIN
exit $?
: <<SCRIPT_AUTHOR_COMMENTS
I had the need to run a program, with specified options, on a number of
different files, using a SMP machine. So I thought [I'd] keep running
a specified number of processes and start a new one each time . . . one
of these terminates.
The "wait" instruction does not help, since it waits for a given process
or *all* process started in background. So I wrote [this] bash script
that can do the job, using the "trap" instruction.
−−Vernia Damiano
SCRIPT_AUTHOR_COMMENTS
trap '' SIGNAL (two adjacent apostrophes) disables SIGNAL for the remainder of the script. trap
SIGNAL restores the functioning of SIGNAL once more. This is useful to protect a critical portion of a
script from an undesirable interrupt.
Version 3 of Bash adds the following special variables for use by the debugger.
1. $BASH_ARGC
2. $BASH_ARGV
3. $BASH_COMMAND
4. $BASH_EXECUTION_STRING
5. $BASH_LINENO
6. $BASH_SOURCE
7. $BASH_SUBSHELL
The set command enables options within a script. At the point in the script where you want the options to take
effect, use set −o option−name or, in short form, set −option−abbrev. These two forms are equivalent.
#!/bin/bash
set −o verbose
# Echoes all commands before executing.
#!/bin/bash
set −v
# Exact same effect as above.
#!/bin/bash
set −o verbose
# Command echoing on.
command
...
command
set +o verbose
# Command echoing off.
command
# Not echoed.
set −v
# Command echoing on.
command
...
command
set +v
# Command echoing off.
command
exit 0
An alternate method of enabling options in a script is to specify them immediately following the #! script
header.
#!/bin/bash −x
#
# Body of script follows.
bash −v script−name
The following is a listing of some useful options. They may be specified in either abbreviated form (preceded
by a single dash) or by complete name (preceded by a double dash or by −o).
var−1=23
# Use 'var_1' instead.
function−whatever () # Error
# Use 'function_whatever ()' instead.
do_something ()
{
echo "This function does something with \"$1\"."
}
do_something=do_something
do_something do_something
#!/bin/bash
if [ "$a" = 273 ]
then
echo "Comparison works."
else
echo "Comparison does not work."
fi # Comparison does not work.
#!/bin/bash
# bad−op.sh: Trying to use a string comparison on integers.
echo
number=1
echo "−−−−−−−−−−−−−−−−−−−−−"
lesser=5
greater=105
echo
exit 0
• Sometimes variables within "test" brackets ([ ]) need to be quoted (double quotes). Failure to do so
may cause unexpected behavior. See Example 7−6, Example 16−5, and Example 9−6.
• Commands issued from a script may fail to execute because the script owner lacks execute permission
for them. If a user cannot invoke a command from the command line, then putting it into a script will
likewise fail. Try changing the attributes of the command in question, perhaps even setting the suid bit
(as root, of course).
• Attempting to use − as a redirection operator (which it is not) will usually result in an unpleasant
surprise.
command1 2> − | command2 # Trying to redirect error output of command1 into a pipe...
# ...will not work.
Thanks, S.C.
• Using Bash version 2+ functionality may cause a bailout with error messages. Older Linux machines
may have version 1.XX of Bash as the default installation.
#!/bin/bash
minimum_version=2
# Since Chet Ramey is constantly adding features to Bash,
# you may set $minimum_version to 2.XX, 3.XX, or whatever is appropriate.
E_BAD_VERSION=80
...
• Using Bash−specific functionality in a Bourne shell script (#!/bin/sh) on a non−Linux machine
may cause unexpected behavior. A Linux system usually aliases sh to bash, but this does not
necessarily hold true for a generic UNIX machine.
• Using undocumented features in Bash turns out to be a dangerous practice. In previous releases of this
book there were several scripts that depended on the "feature" that, although the maximum value of
an exit or return value was 255, that limit did not apply to negative integers. Unfortunately, in version
2.05b and later, that loophole disappeared. See Example 23−9.
• A script with DOS−type newlines (\r\n) will fail to execute, since #!/bin/bash\r\n is not
recognized, not the same as the expected #!/bin/bash\n. The fix is to convert the script to
UNIX−style newlines.
#!/bin/bash
echo "Here"
echo "There"
exit 0
• A shell script headed by #!/bin/sh will not run in full Bash−compatibility mode. Some
Bash−specific functions might be disabled. Scripts that need complete access to all the Bash−specific
extensions should start with #!/bin/bash.
• Putting whitespace in front of the terminating limit string of a here document will cause unexpected
behavior in a script.
•
A script may not export variables back to its parent process, the shell, or to the environment. Just as
we learned in biology, a child process can inherit from a parent, but not vice versa.
WHATEVER=/home/bozo
export WHATEVER
exit 0
bash$ echo $WHATEVER
bash$
Sure enough, back at the command prompt, $WHATEVER remains unset.
• Setting and manipulating variables in a subshell, then attempting to use those same variables outside
the scope of the subshell will result an unpleasant surprise.
#!/bin/bash
# Pitfalls of variables in a subshell.
outer_variable=outer
echo
echo "outer_variable = $outer_variable"
echo
(
# Begin subshell
# End subshell
)
echo
echo "inner_variable outside subshell = $inner_variable" # Unset.
echo "outer_variable outside subshell = $outer_variable" # Unchanged.
echo
exit 0
#!/bin/bash
# badread.sh:
# Attempting to use 'echo and 'read'
#+ to assign variables non−interactively.
a=aaa
b=bbb
c=ccc
echo
echo "a = $a" # a = aaa
echo "b = $b" # b = bbb
echo "c = $c" # c = ccc
# Reassignment failed.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
echo "−−−−−−−"
echo "a = $a" # a = one
echo "b = $b" # b = two
echo "c = $c" # c = three
# Reassignment succeeded.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
echo; echo
echo "one two three" | ( read a b c;
echo "Inside subshell: "; echo "a = $a"; echo "b = $b"; echo "c = $c" )
# a = one
# b = two
# c = three
echo "−−−−−−−−−−−−−−−−−"
echo "Outside subshell: "
echo "a = $a" # a = aaa
echo "b = $b" # b = bbb
echo "c = $c" # c = ccc
echo
exit 0
In fact, as Anthony Richardson points out, piping to any loop can cause a similar problem.
foundone=false
find $HOME −type f −atime +30 −size 100k |
while true
do
read f
echo "$f is over 100KB and has not been accessed in over 30 days"
echo "Consider moving the file to archives."
foundone=true
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
echo "Subshell level = $BASH_SUBSHELL"
# Subshell level = 1
# Yes, we're inside a subshell.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
done
foundone=false
for f in $(find $HOME −type f −atime +30 −size 100k) # No pipe here.
do
echo "$f is over 100KB and has not been accessed in over 30 days"
echo "Consider moving the file to archives."
foundone=true
done
if [ $foundone = false ]
then
echo "No files need archiving."
fi
if ! $foundone
then
echo "No files need archiving."
fi
}
A related problem occurs when trying to write the stdout of a tail −f piped to grep.
So beware −−
Beware.
A.J. Lamb and H.W. Petrie
Herewith are a few stylistic guidelines. This is not intended as an Official Shell Scripting Stylesheet.
#!/bin/bash
#************************************************#
# xyz.sh #
# written by Bozo Bozeman #
# July 05, 2001 #
# #
# Clean up project files. #
#************************************************#
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− #
# cleanup_pfiles () #
# Removes all files in designated directory. #
# Parameter: $target_directory #
# Returns: 0 on success, $E_BADDIR if something went wrong. #
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− #
cleanup_pfiles ()
{
if [ ! −d "$1" ] # Test if target directory exists.
then
echo "$1 is not a directory."
return $E_BADDIR
fi
rm −f "$1"/*
return 0 # Success.
}
cleanup_pfiles $projectdir
exit 0
Be sure to put the #!/bin/bash at the beginning of the first line of the script, preceding any comment
headers.
• Avoid using "magic numbers," [79] that is, "hard−wired" literal constants. Use meaningful variable
names instead. This makes the script easier to understand and permits making changes and updates
# A better way:
LOGFILE=/var/log/messages # Only line that needs to be changed.
if [ −f "$LOGFILE" ]
then
...
fi
• Choose descriptive names for variables and functions.
fl=`ls −al $dirname` # Cryptic.
file_listing=`ls −al $dirname` # Better.
Ender suggests using the exit codes in /usr/include/sysexits.h in shell scripts, though these
are primarily intended for C and C++ programming.
• Use standardized parameter flags for script invocation. Ender proposes the following set of flags.
if COMMAND
...
# More concise (if perhaps not quite as legible).
A shell running a script is always a non−interactive shell. All the same, the script can still access its tty. It is
even possible to emulate an interactive shell in a script.
#!/bin/bash
MY_PROMPT='$ '
while :
do
echo −n "$MY_PROMPT"
read line
eval "$line"
done
exit 0
Init and startup scripts are necessarily non−interactive, since they must run without human intervention. Many
administrative and system maintenance scripts are likewise non−interactive. Unvarying repetitive tasks cry
out for automation by non−interactive scripts.
Non−interactive scripts can run in the background, but interactive ones hang, waiting for input that never
comes. Handle that difficulty by having an expect script or embedded here document feed input to an
interactive script running as a background job. In the simplest case, redirect a file to supply input to a read
statement (read variable <file). These particular workarounds make possible general purpose scripts that run
in either interactive or non−interactive modes.
If a script needs to test whether it is running in an interactive shell, it is simply a matter of finding whether the
prompt variable, $PS1 is set. (If the user is being prompted for input, then the script needs to display a
prompt.)
if [ −z $PS1 ] # no prompt?
then
# non−interactive
...
else
# interactive
...
fi
Alternatively, the script can test for the presence of option "i" in the $− flag.
case $− in
*i*) # interactive shell
;;
*) # non−interactive shell
;;
# (Courtesy of "UNIX F.A.Q.," 1993)
Scripts may be forced to run in interactive mode with the −i option or with a #!/bin/bash −i
header. Be aware that this can cause erratic script behavior or show error messages even when no error is
present.
A "wrapper" is a shell script that embeds a system command or utility, that saves a set of parameters passed to
that command. [80] Wrapping a script around a complex command line simplifies invoking it. This is
expecially useful with sed and awk.
A sed or awk script would normally be invoked from the command line by a sed −e 'commands' or
awk 'commands'. Embedding such a script in a Bash script permits calling it more simply, and makes it
"reusable". This also enables combining the functionality of sed and awk, for example piping the output of a
set of sed commands to awk. As a saved executable file, you can then repeatedly invoke it in its original form
or modified, without the inconvenience of retyping it on the command line.
#!/bin/bash
# Same as
# sed −e '/^$/d' filename
# invoked from the command line.
# Note that this script doesn't actually change the target file.
# If you need to do that, redirect its output.
exit 0
#!/bin/bash
if [ $# −ne "$ARGS" ]
# Test number of arguments to script (always a good idea).
then
echo "Usage: `basename $0` old−pattern new−pattern filename"
exit $E_BADARGS
fi
old_pattern=$1
new_pattern=$2
if [ −f "$3" ]
then
file_name=$3
else
echo "File \"$3\" does not exist."
exit $E_BADARGS
fi
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
sed −e "s/$old_pattern/$new_pattern/g" $file_name
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
#!/bin/bash
# Generic shell wrapper that performs an operation
#+ and logs it.
OPTIONS="$@"
# Log it.
echo "`date` + `whoami` + $OPERATION "$@"" >> $LOGFILE
# Now, do it.
exec $OPERATION "$@"
#!/bin/bash
# pr−ascii.sh: Prints a table of ASCII characters.
exit 0
#!/bin/bash
filename=$1
column_number=$2
# Passing shell variables to the awk part of the script is a bit tricky.
# One method is to strong−quote the Bash−script variable
#+ within the awk script.
# $'$BASH_SCRIPT_VAR'
# ^ ^
# This is done in the embedded awk script below.
# See the awk documentation for more details.
{ total += $'"${column_number}"'
}
END {
print total
}
' "$filename"
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# End awk script.
exit 0
For those scripts needing a single do−it−all tool, a Swiss army knife, there is Perl. Perl combines the
capabilities of sed and awk, and throws in a large subset of C, to boot. It is modular and contains support for
everything ranging from object−oriented programming up to and including the kitchen sink. Short Perl scripts
lend themselves to embedding in shell scripts, and there may even be some substance to the claim that Perl
can totally replace shell scripting (though the author of this document remains skeptical).
echo "==============================================================="
echo "However, the script may also contain shell and system commands."
exit 0
It is even possible to combine a Bash script and Perl script within the same file. Depending on how the script
is invoked, either the Bash part or the Perl part will execute.
#!/bin/bash
# bashandperl.sh
exit 0
# End of Bash part of the script.
# =======================================================
#!/usr/bin/perl
# This part of the script must be invoked with −x option.
a=8
city="New York"
# Again, all of the comparisons below are equivalent.
test "$city" \< Paris && echo "Yes, Paris is greater than $city" # Greater ASCII order.
/bin/test "$city" \< Paris && echo "Yes, Paris is greater than $city"
[ "$city" \< Paris ] && echo "Yes, Paris is greater than $city"
[[ $city < Paris ]] && echo "Yes, Paris is greater than $city" # Need not quote $city.
33.4. Recursion
Can a script recursively call itself? Indeed.
#!/bin/bash
# recurse.sh
RANGE=10
MAXVAL=9
i=$RANDOM
let "i %= $RANGE" # Generate a random number between 0 and $RANGE − 1.
exit 0
# Note:
# −−−−
# This script must have execute permission for it to work properly.
# This is the case even if it is invoked by an "sh" command.
# Explain why.
#!/bin/bash
# pb.sh: phone book
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
Sample "phonebook" datafile:
#!/bin/bash
# usrmnt.sh, written by Anthony Richardson
# Used with permission.
# usage: usrmnt.sh
# description: mount device, invoking user must be listed in the
# MNTUSERS group in the /etc/sudoers file.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# This is a usermount script that reruns itself using sudo.
# A user with the proper permissions only has to type
if [ −z "$SUDO_COMMAND" ]
then
mntusr=$(id −u) grpusr=$(id −g) sudo $0 $*
exit 0
fi
exit 0
Too many levels of recursion can exhaust the script's stack space, causing a segfault.
The ANSI [81] escape sequences set screen attributes, such as bold text, and color of foreground and
background. DOS batch files commonly used ANSI escape codes for color output, and so can Bash scripts.
#!/bin/bash
# ex30a.sh: "Colorized" version of ex30.sh.
# Crude address database
read person
case "$person" in
# Note variable is quoted.
"E" | "e" )
# Accept upper or lowercase input.
echo
echo "Roland Evans"
echo "4321 Floppy Dr."
echo "Hardscrabble, CO 80753"
echo "(303) 734−9874"
echo "(303) 734−9892 fax"
echo "revans@zzy.net"
echo "Business partner & old friend"
;;
"J" | "j" )
echo
echo "Mildred Jones"
echo "249 E. 7th St., Apt. 19"
echo "New York, NY 10009"
echo "(212) 533−2814"
echo "(212) 533−9972 fax"
echo "milliej@loisaida.com"
echo "Girlfriend"
echo "Birthday: Feb. 11"
;;
* )
# Default option.
# Empty input (hitting RETURN) fits here, too.
echo
echo "Not yet in database."
;;
echo
exit 0
#!/bin/bash
# Draw−box.sh: Drawing a box using ASCII characters.
######################################################################
### draw_box function doc ###
draw_box(){
#=============#
HORZ="−"
VERT="|"
CORNER_CHAR="+"
MINARGS=4
E_BADARGS=65
count=1
c=`expr $2 + $BOX_WIDTH`
for (( r=$1; count<=$BOX_HEIGHT; r++)); do
plot_char $r $c $VERT
let count=count+1
done
count=1
echo −e "\E[${P_ROWS};1H"
}
exit 0
# Exercise:
# −−−−−−−−
# Add the option of printing text within the drawn box.
The simplest, and perhaps most useful ANSI escape sequence is bold text, \033[1m ... \033[0m. The \033
represents an escape, the "[1" turns on the bold attribute, while the "[0" switches it off. The "m" terminates
each term of the escape sequence.
A similar escape sequence switches on the underline attribute (on an rxvt and an aterm).
It's usually advisable to set the bold attribute for light−colored foreground text.
Since tput sgr0 fails to restore terminal settings under certain circumstances, echo −ne \E[0m may be a
better choice.
Use the following template for writing colored text on a colored background.
The "\E[" begins the escape sequence. The semicolon−separated numbers "COLOR1" and "COLOR2"
specify a foreground and a background color, according to the table below. (The order of the numbers does
not matter, since the foreground and background numbers fall in non−overlapping ranges.) The "m"
terminates the escape sequence, and the text begins immediately after that.
Note also that single quotes enclose the remainder of the command sequence following the echo −e.
The numbers in the following table work for an rxvt terminal. Results may vary for other terminal emulators.
#!/bin/bash
# color−echo.sh: Echoing text messages in color.
black='\E[30;47m'
red='\E[31;47m'
green='\E[32;47m'
yellow='\E[33;47m'
blue='\E[34;47m'
magenta='\E[35;47m'
cyan='\E[36;47m'
white='\E[37;47m'
cecho () # Color−echo.
# Argument $1 = message
# Argument $2 = color
{
local default_msg="No message passed."
# Doesn't really need to be a local variable.
echo −e "$color"
echo "$message"
Reset # Reset to normal.
return
}
echo
exit 0
# Exercises:
# −−−−−−−−−
# 1) Add the "bold" attribute to the 'cecho ()' function.
# 2) Add options for colored backgrounds.
#!/bin/bash
# horserace.sh: very simple horserace simulation.
# Author: Stefano Palmeri
# Used with permission.
################################################################
# Goals of the script:
# playing with escape sequences and terminal colors.
#
# Exercise:
# Edit the script to make it run less randomly,
#+ set up a fake betting shop . . .
# Um . . . um . . . it's starting to remind me of a movie . . .
E_RUNERR=65
# Set a unique (paranoid) name for the temp directory the script needs.
HORSE_RACE_TMP_DIR=$HOME/.horserace−`date +%s`−`head −c10 /dev/urandom | md5sum | head −c30`
# This function moves the cursor to line $1 column $2 and then prints $3.
# E.g.: "move_and_echo 5 10 linux" is equivalent to
#+ "tput cup 4 9; echo linux", but with one command instead of two.
# Note: "tput cup" defines 0 0 the upper left angle of the terminal,
#+ echo defines 1 1 the upper left angle of the terminal.
move_and_echo() {
echo −ne "\E[${1};${2}H""$3"
}
clear
tput cup 15 0
echo "++++++++++++++++++++++++++++++++++++++\
++++++++++++++++++++++++++++++++++++++++++"
# Calculate odds.
case $HANDICAP in
1) ODDS=`echo $HANDICAP \* 0.25 + 1.25 | bc`
echo $ODDS > odds_${HN}
;;
2 | 3) ODDS=`echo $HANDICAP \* 0.40 + 1.25 | bc`
echo $ODDS > odds_${HN}
;;
4 | 5 | 6) ODDS=`echo $HANDICAP \* 0.55 + 1.25 | bc`
echo $ODDS > odds_${HN}
;;
7 | 8) ODDS=`echo $HANDICAP \* 0.75 + 1.25 | bc`
echo $ODDS > odds_${HN}
;;
9) ODDS=`echo $HANDICAP \* 0.90 + 1.25 | bc`
echo $ODDS > odds_${HN}
esac
done
# Print odds.
print_odds() {
tput cup 6 0
echo −ne '\E[30;42m'
for HN in `seq 9`; do
echo "#$HN odds−>" `cat odds_${HN}`
done
}
print_odds
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
draw_horses
echo −ne '\E[37;47m'
move_and_echo 18 1 $BLANK80
echo −ne '\E[30m'
move_and_echo 18 1 Starting...
sleep 1
MOVE_HORSE=0
ADD_POS=1
# Check if the current position is an handicap position.
if seq 10 7 68 | grep −w $COL &> /dev/null; then
if grep −w $MOVE_HORSE $COL &> /dev/null; then
ADD_POS=0
grep −v −w $MOVE_HORSE $COL > ${COL}_new
rm −f $COL
mv −f ${COL}_new $COL
else ADD_POS=1
fi
else ADD_POS=1
fi
COL=`expr $COL + $ADD_POS`
echo $COL > horse_${MOVE_HORSE}_position # Store new position.
# When all horses have gone beyond field line 15, reprint odds.
touch fieldline15
if [ $COL = 15 ]; then
echo $MOVE_HORSE >> fieldline15
fi
if [ `wc −l fieldline15 | cut −f1 −d " "` = 9 ]; then
print_odds
: > fieldline15
fi
done
# Restore echoing.
stty echo
tput cup 19 0
exit 0
See also Example A−22.
There is, however, a major problem with all this. ANSI escape sequences are emphatically
non−portable. What works fine on some terminal emulators (or the console) may work differently, or
not at all, on others. A "colorized" script that looks stunning on the script author's machine may produce
unreadable output on someone else's. This greatly compromises the usefulness of "colorizing" scripts,
and possibly relegates this technique to the status of a gimmick or even a "toy".
Moshe Jacobson's color utility (http://runslinux.net/projects.html#color) considerably simplifies using ANSI
escape sequences. It substitutes a clean and logical syntax for the clumsy constructs just discussed.
33.6. Optimizations
Most shell scripts are quick 'n dirty solutions to non−complex problems. As such, optimizing them for speed
is not much of an issue. Consider the case, though, where a script carries out an important task, does it well,
but runs too slowly. Rewriting it in a compiled language may not be a palatable option. The simplest fix
would be to rewrite the parts of the script that slow it down. Is it possible to apply principles of code
optimization even to a lowly shell script?
Check the loops in the script. Time consumed by repetitive operations adds up quickly. If at all possible,
remove time−consuming operations from within loops.
Use builtin commands in preference to system commands. Builtins execute faster and usually do not launch a
subshell when invoked.
Use the time and times tools to profile computation−intensive commands. Consider rewriting time−critical
code sections in C, or even in assembler.
Try to minimize file I/O. Bash is not particularly efficient at handling files, so consider using more
appropriate tools for this within the script, such as awk or Perl.
Write your scripts in a structured, coherent form, so they can be reorganized and tightened up as necessary.
Some of the optimization techniques applicable to high−level languages may work for scripts, but others, such
as loop unrolling, are mostly irrelevant. Above all, use common sense.
For an excellent demonstration of how optimization can drastically reduce the execution time of a script, see
Example 12−42.
file=data.txt
title="***This is the title line of data text file***"
# SCRIPT LIBRARY
# −−−−−− −−−−−−−
# Note:
# No "#!" here.
# No "live code" either.
# Functions
case $1 in
*[!a−zA−Z]*|"") return $FAILURE;;
*) return $SUCCESS;;
esac # Thanks, S.C.
}
return $absval
}
return
## Caution.
rm −rf *.zzy ## The "−rf" options to "rm" are very dangerous,
##+ especially with wildcards.
#+ Line continuation.
# This is line 1
#+ of a multi−line comment,
#+ and this is the final line.
#* Note.
#o List item.
#!/bin/bash
COMMENT_BLOCK=
# Try setting the above variable to some value
#+ for an unpleasant surprise.
if [ $COMMENT_BLOCK ]; then
Comment block −−
=================================
This is a comment line.
This is another comment line.
This is yet another comment line.
=================================
exit 0
Compare this with using here documents to comment out code blocks.
• Using the $? exit status variable, a script may test if a parameter contains only digits, so it can be
treated as an integer.
#!/bin/bash
SUCCESS=0
E_BADINPUT=65
if [ $? −ne "$SUCCESS" ]
then
echo "Usage: `basename $0` integer−input"
exit $E_BADINPUT
fi
# Any variable, not just a command line parameter, can be tested this way.
exit 0
• The 0 − 255 range for function return values is a severe limitation. Global variables and other
workarounds are often problematic. An alternative method for a function to communicate a value
back to the main body of the script is to have the function write to stdout (usually with echo) the
"return value," and assign this to a variable. This is actually a variant of command substitution.
#!/bin/bash
# multiplication.sh
local product=1
mult1=15383; mult2=25211
val1=`multiply $mult1 $mult2`
echo "$mult1 X $mult2 = $val1"
# 387820813
exit 0
The same technique also works for alphanumeric strings. This means that a function can "return" a
non−numeric value.
#!/bin/bash
# sum−product.sh
# A function may "return" more than one value.
echo
echo "Enter first number "
read first
echo
echo "Enter second number "
read second
echo
exit 0
• Next in our bag of trick are techniques for passing an array to a function, then "returning" an array
back to the main body of the script.
Passing an array involves loading the space−separated elements of the array into a variable with
command substitution. Getting an array back as the "return value" from a function uses the previously
mentioned strategem of echoing the array in the function, then invoking command substitution and
the ( ... ) operator to assign it to an array.
#!/bin/bash
# array−function.sh: Passing an array to a function and...
# "returning" an array from a function
Pass_Array ()
{
local passed_array # Local variable.
passed_array=( `echo "$1"` )
echo "${passed_array[@]}"
# List all the elements of the new array
#+ declared and set within the function.
}
echo
echo "original_array = ${original_array[@]}"
# List all elements of original array.
echo "============================================================="
echo
exit 0
For a more elaborate example of passing arrays to functions, see Example A−10.
• Using the double parentheses construct, it is possible to use C−like syntax for setting and
incrementing variables and in for and while loops. See Example 10−12 and Example 10−17.
• Setting the path and umask at the beginning of a script makes it more "portable" −− more likely to run
on a "foreign" machine whose user may have bollixed up the $PATH and umask.
#!/bin/bash
PATH=/bin:/usr/bin:/usr/local/bin ; export PATH
umask 022 # Files that the script creates will have 755 permission.
#!/bin/bash
# agram.sh: Playing games with anagrams.
bash$ sh agram.sh
islander
isolate
isolead
isotheral
# Exercises:
# −−−−−−−−−
# Modify this script to take the LETTERSET as a command−line parameter.
# Parameterize the filters in lines 11 − 13 (as with $FILTER),
#+ so that they can be specified by passing arguments to a function.
The dialog family of tools offers a method of calling "dialog" widgets from a shell script. The original
dialog utility works in a text console, but its successors, gdialog, Xdialog, and kdialog use
X−Windows−based widget sets.
#!/bin/bash
# dialog.sh: Using 'gdialog' widgets.
# Must have 'gdialog' installed on your system to run this script.
# Version 1.1 (corrected 04/05/05)
if [ "$?" −eq 0 ]
# It's good practice to check exit status.
then
echo "Executed \"dialog box\" without errors."
else
echo "Error(s) in \"dialog box\" execution."
# Or, clicked on "Cancel", instead of "OK" button.
rm $OUTFILE
exit $E_INPUT
fi
exit $?
For other methods of scripting with widgets, try Tk or wish (Tcl derivatives), PerlTk (Perl with Tk
extensions), tksh (ksh with Tk extensions), XForms4Perl (Perl with XForms extensions), Gtk−Perl
(Perl with Gtk extensions), or PyQt (Python with Qt extensions).
• For doing multiple revisions on a complex script, use the rcs Revision Control System package.
Among other benefits of this is automatically updated ID header tags. The co command in rcs does a
parameter replacement of certain reserved key words, for example, replacing #$Id$ in a script with
something like:
Various researchers at Bell Labs and other sites, including M. Douglas McIlroy, Tom Duff, and Fred Cohen
have investigated the implications of shell script viruses. They conclude that it is all to easy for even a novice,
a "script kiddie", to write one. [82]
Here is yet another reason to learn scripting. Being able to look at and understand scripts may protect your
system from being hacked or damaged.
Unfortunately, according to an article in the October, 2005 Linux Journal, the binary can, in at least some
cases, be decrypted to recover the original script source. Still, this could be a useful method of keeping scripts
secure from all but the most skilled hackers.
As it happens, many of the various shells and scripting languages seem to be converging toward the POSIX
1003.2 standard. Invoking Bash with the −−posix option or inserting a set −o posix at the head of a script
causes Bash to conform very closely to this standard. Another alternative is to use a
#!/bin/sh
header in the script, rather than
#!/bin/bash
Note that /bin/sh is a link to /bin/bash in Linux and certain other flavors of UNIX, and a script
invoked this way disables extended Bash functionality.
Most Bash scripts will run as−is under ksh, and vice−versa, since Chet Ramey has been busily porting ksh
features to the latest versions of Bash.
On a commercial UNIX machine, scripts using GNU−specific features of standard commands may not work.
This has become less of a problem in the last few years, as the GNU utilities have pretty much displaced their
proprietary counterparts even on "big−iron" UNIX. Caldera's release of the source to many of the original
UNIX utilities has accelerated the trend.
Bash has certain features that the traditional Bourne shell lacks. Among these are:
There have been intimations that a future release of Windows will contain Bash−like command line scripting
capabilities, but that remains to be seen.
The current version of Bash, the one you have running on your machine, is version 2.xx.y or 3.xx.y.
The version 2 update of the classic Bash scripting language added array variables, [83] string and parameter
expansion, and a better method of indirect variable references, among other features.
#!/bin/bash
# String expansion.
# Introduced with version 2 of Bash.
exit 0
#!/bin/bash
a=letter_of_alphabet
letter_of_alphabet=z
echo
t=table_cell_3
table_cell_3=24
echo "t = ${!t}" # t = 24
table_cell_3=387
echo "Value of t changed to ${!t}" # 387
exit 0
#!/bin/bash
# resistor−inventory.sh
# Simple database application using indirect variable referencing.
# ============================================================== #
# Data
B1723_value=470 # Ohms
B1723_powerdissip=.25 # Watts
B1723_colorcode="yellow−violet−brown" # Color bands
B1723_loc=173 # Where they are
B1723_inventory=78 # How many
B1724_value=1000
B1724_powerdissip=.25
B1724_colorcode="brown−black−red"
B1724_loc=24N
B1724_inventory=243
B1725_value=10000
B1725_powerdissip=.25
B1725_colorcode="brown−black−orange"
B1725_loc=24N
B1725_inventory=89
# ============================================================== #
echo
echo
echo
echo "Catalog number $catalog_number:"
echo "There are ${!Inv} of [${!Val} ohm / ${!Pdissip} watt] resistors in stock."
echo "These are located in bin # ${!Loc}."
echo "Their color code is \"${!Ccode}\"."
break
done
# Exercises:
# −−−−−−−−−
# 1) Rewrite this script to read its data from an external file.
# 2) Rewrite this script to use arrays,
#+ rather than indirect variable referencing.
# Which method is more straightforward and intuitive?
# Notes:
# −−−−−
# Shell scripts are inappropriate for anything except the most simple
#+ database applications, and even then it involves workarounds and kludges.
# Much better is to use a language with native support for data structures,
#+ such as C++ or Java (or even Perl).
exit 0
Example 34−4. Using arrays and other miscellaneous trickery to deal four random hands from a deck
of cards
#!/bin/bash
# Cards:
# Deals four random hands from a deck of cards.
UNPICKED=0
PICKED=1
DUPE_CARD=99
LOWER_LIMIT=0
UPPER_LIMIT=51
CARDS_IN_SUIT=13
CARDS=52
declare −a Deck
declare −a Suits
declare −a Cards
# It would have been easier to implement and more intuitive
#+ with a single, 3−dimensional array.
# Perhaps a future version of Bash will support multidimensional arrays.
initialize_Deck ()
{
i=$LOWER_LIMIT
until [ "$i" −gt $UPPER_LIMIT ]
do
Deck[i]=$UNPICKED # Set each card of "Deck" as unpicked.
let "i += 1"
done
echo
}
initialize_Suits ()
{
Suits[0]=C #Clubs
Suits[1]=D #Diamonds
Suits[2]=H #Hearts
initialize_Cards ()
{
Cards=(2 3 4 5 6 7 8 9 10 J Q K A)
# Alternate method of initializing an array.
}
pick_a_card ()
{
card_number=$RANDOM
let "card_number %= $CARDS"
if [ "${Deck[card_number]}" −eq $UNPICKED ]
then
Deck[card_number]=$PICKED
return $card_number
else
return $DUPE_CARD
fi
}
parse_card ()
{
number=$1
let "suit_number = number / CARDS_IN_SUIT"
suit=${Suits[suit_number]}
echo −n "$suit−"
let "card_no = number % CARDS_IN_SUIT"
Card=${Cards[card_no]}
printf %−4s $Card
# Print cards in neat columns.
}
deal_cards ()
{
echo
cards_picked=0
while [ "$cards_picked" −le $UPPER_LIMIT ]
do
pick_a_card
t=$?
u=$cards_picked+1
# Change back to 1−based indexing (temporarily). Why?
let "u %= $CARDS_IN_SUIT"
if [ "$u" −eq 0 ] # Nested if/then condition test.
then
echo
return 0
}
# Structured programming:
# Entire program logic modularized in functions.
#================
seed_random
initialize_Deck
initialize_Suits
initialize_Cards
deal_cards
#================
exit 0
# Exercise 1:
# Add comments to thoroughly document this script.
# Exercise 2:
# Add a routine (function) to print out each hand sorted in suits.
# You may add other bells and whistles if you like.
# Exercise 3:
# Simplify and streamline the logic of the script.
On July 27, 2004, Chet Ramey released version 3 of Bash. This update fixes quite a number of bug in Bash
and adds some new features.
for i in {1..10}
# Simpler and more straightforward than
#+ for i in $(seq 10)
do
echo −n "$i "
done
echo
# 1 2 3 4 5 6 7 8 9 10
• The ${!array[@]} operator, which expands to all the indices of a given array.
#!/bin/bash
echo ${!Array[@]} # 0 1 2 3
# All the indices of Array.
for i in ${!Array[@]}
do
echo ${Array[i]} # element−zero
# element−one
# element−two
# element−three
#
# All the elements in Array.
done
•
The =~ Regular Expression matching operator within a double brackets test expression. (Perl has a
similar operator.)
#!/bin/bash
echo "$variable"
if [[ "$variable" =~ "T*fin*es*" ]]
# Regex matching with =~ operator within [[ double brackets ]].
then
echo "match found"
# match found
fi
Or, more usefully:
#!/bin/bash
input=$1
if [[ "$input" =~ "[1−9][0−9][0−9]−[0−9][0−9]−[0−9][0−9][0−9][0−9]" ]]
# NNN−NN−NNNN
# Where each N is a digit.
# But, initial digit must not be 0.
then
echo "Social Security number."
# Process SSN.
else
echo "Not a Social Security number!"
# Or, ask for corrected input.
fi
For additional examples of using the =~ operator, see Example A−28 and Example 17−14.
The update to version 3 of Bash breaks a few scripts that worked under earlier versions. Test
critical legacy scripts to make sure they still work!
As it happens, a couple of the scripts in the Advanced Bash Scripting Guide had to be fixed up (see
Example A−20 and Example 9−4, for instance).
• The += operator is now permitted in in places where previously only the = assignment operator was
recognized.
a=1
echo $a # 1
a+=Hello
echo $a # 15Hello
Here, += functions as a string concatenation operator. Note that its behavior in this particular context
is different than within a let construct.
a=1
echo $a # 1
This reminds me of the apocryphal story about a mad professor. Crazy as a loon, the fellow was. At the sight
of a book, any book −− at the library, at a bookstore, anywhere −− he would become totally obsessed with the
idea that he could have written it, should have written it −− and done a better job of it to boot. He would
thereupon rush home and proceed to do just that, write a book with the very same title. When he died some
years later, he allegedly had several thousand books to his credit, probably putting even Asimov to shame.
The books might not have been any good −− who knows −− but does that really matter? Here's a fellow who
lived his dream, even if he was obsessed by it, driven by it . . . and somehow I can't help admiring the old
coot.
The author claims no credentials or special qualifications, other than a compulsion to write. [85] This book is
somewhat of a departure from his other major work, HOW−2 Meet Women: The Shy Man's Guide to
Relationships. He has also written the Software−Building HOWTO. Lately, he has been trying his hand at
short fiction.
A Linux user since 1995 (Slackware 2.2, kernel 1.2.1), the author has emitted a few software truffles,
including the cruft one−time pad encryption utility, the mcalc mortgage calculator, the judge Scrabble®
adjudicator, and the yawl word gaming list package. He got his start in programming using FORTRAN IV on
a CDC 3800, but is not the least bit nostalgic for those days.
Living in a secluded desert community with wife and dog, he cherishes human frailty.
If you need assistance with a schoolwork assignment, read the pertinent sections of this and other reference
works. Do your best to solve the problem using your own wits and resources. Please do not waste the author's
time. You will get neither help nor sympathy.
Update: upgraded to a 770Z Thinkpad (P2−366, 192 meg RAM) running FC3. Anyone feel like donating a
later−model laptop to a starving writer <g>?
35.5. Credits
Community participation made this project possible. The author gratefully acknowledges that writing this
book would have been an impossible task without help and feedback from all you people out there.
Philippe Martin translated the first version (0.1) of this document into DocBook/SGML. While not on the job
at a small French company as a software developer, he enjoys working on GNU/Linux documentation and
software, reading literature, playing music, and, for his peace of mind, making merry with friends. You may
run across him somewhere in France or in the Basque Country, or you can email him at feloy@free.fr.
Philippe Martin also pointed out that positional parameters past $9 are possible using {bracket} notation. (See
Example 4−5).
Stéphane Chazelas sent a long list of corrections, additions, and example scripts. More than a contributor, he
had, in effect, for a while taken on the role of editor for this document. Merci beaucoup!
Paulo Marcel Coelho Aragao offered many corrections, both major and minor, and contributed quite a number
of helpful suggestions.
I would like to especially thank Patrick Callahan, Mike Novak, and Pal Domokos for catching bugs, pointing
out ambiguities, and for suggesting clarifications and changes. Their lively discussion of shell scripting and
general documentation issues inspired me to try to make this document more readable.
I'm grateful to Jim Van Zandt for pointing out errors and omissions in version 0.2 of this document. He also
contributed an instructive example script.
Many thanks to Jordi Sanfeliu for giving permission to use his fine tree script (Example A−17), and to Rick
Boivie for revising it.
Likewise, thanks to Michel Charpentier for permission to use his dc factoring script (Example 12−47).
Kudos to Noah Friedman for permission to use his string function script (Example A−18).
Emmanuel Rouat suggested corrections and additions on command substitution and aliases. He also
contributed a very nice sample .bashrc file (Appendix K).
Heiner Steven kindly gave permission to use his base conversion script, Example 12−43. He also made a
number of corrections and many helpful suggestions. Special thanks.
Rick Boivie contributed the delightfully recursive pb.sh script (Example 33−9), revised the tree.sh script
(Example A−17), and suggested performance improvements for the monthlypmt.sh script (Example 12−42).
Florian Wisser enlightened me on some of the fine points of testing strings (see Example 7−6), and on other
matters.
Michael Zick extended the empty array example to demonstrate some surprising array properties. He also
contributed the isspammer scripts (Example 12−37 and Example A−27).
Hyun Jin Cha found several typos in the document in the process of doing a Korean translation. Thanks for
pointing these out.
Andreas Abraham sent in a long list of typographical errors and other corrections. Special thanks!
Others contributing scripts, making helpful suggestions, and pointing out errors were Gabor Kiss, Leopold
Toetsch, Peter Tillier, Marcus Berglof, Tony Richardson, Nick Drage (script ideas!), Rich Bartell, Jess
Thrysoee, Adam Lazur, Bram Moolenaar, Baris Cicek, Greg Keraunen, Keith Matthews, Sandro Magi, Albert
Reiner, Dim Segebart, Rory Winston, Lee Bigelow, Wayne Pollock, "jipe," "bojster," "nyal," "Hobbit,"
"Ender," "Little Monster" (Alexis), "Mark," Emilio Conti, Ian. D. Allen, Arun Giridhar, Dennis Leeuw, Dan
Jacobson, Aurelio Marinho Jargas, Edward Scholtz, Jean Helou, Chris Martin, Lee Maschmeyer, Bruno
Haible, Wilbert Berendsen, Sebastien Godard, Bjön Eriksson, John MacDonald, Joshua Tschida, Troy Engel,
Manfred Schwarb, Amit Singh, Bill Gradwohl, David Lombard, Jason Parker, Steve Parker, Bruce W. Clare,
William Park, Vernia Damiano, Mihai Maties, Jeremy Impson, Ken Fuchs, Frank Wang, Sylvain Fourmanoit,
Matthew Walker, Kenny Stauffer, Filip Moritz, Andrzej Stefanski, Daniel Albers, Stefano Palmeri, Nils
Radtke, Jeroen Domburg, Alfredo Pironti, Phil Braham, Bruno de Oliveira Schneider, Stefano Falsetto, Chris
Morgan, Walter Dnes, Linc Fessenden, Michael Iatrou, Pharis Monalo, Jesse Gough, Fabian Kreutz, Mark
Norman, Harald Koenig, Peter Knowles, Francisco Lobo, Mariusz Gniazdowski, Tedman Eng, Achmed
Darwish, Andreas Kühne, and David Lawyer (himself an author of four HOWTOs).
My gratitude to Chet Ramey and Brian Fox for writing Bash, and building into it elegant and powerful
scripting capabilities.
Very special thanks to the hard−working volunteers at the Linux Documentation Project. The LDP hosts a
repository of Linux knowledge and lore, and has, to a large extent, enabled the publication of this book.
Thanks and appreciation to IBM, Novell, Red Hat, the Free Software Foundation, and all the good people
fighting the good fight to keep Open Source software free and open.
Thanks most of all to my wife, Anita, for her encouragement and emotional support.
Edited by Peter Denning, Computers Under Attack: Intruders, Worms, and Viruses, ACM Press, 1990,
0−201−53067−8.
Ken Burtch, Linux Shell Scripting with Bash, 1st edition, Sams Publishing (Pearson), 2004, 0672326426.
Covers much of the same material as this guide. Dead tree media does have its advantages, though.
Dale Dougherty and Arnold Robbins, Sed and Awk, 2nd edition, O'Reilly and Associates, 1997,
1−156592−225−5.
To unfold the full power of shell scripting, you need at least a passing familiarity with sed and awk. This is
the standard tutorial. It includes an excellent introduction to "regular expressions". Read this book.
Jeffrey Friedl, Mastering Regular Expressions, O'Reilly and Associates, 2002, 0−596−00289−0.
Aeleen Frisch, Essential System Administration, 3rd edition, O'Reilly and Associates, 2002, 0−596−00343−9.
This excellent sys admin manual has a decent introduction to shell scripting for sys administrators and does a
nice job of explaining the startup and initialization scripts. The long overdue third edition of this classic has
finally been released.
Stephen Kochan and Patrick Woods, Unix Shell Programming, Hayden, 1990, 067248448X.
Bibliography 454
Advanced Bash−Scripting Guide
Neil Matthew and Richard Stones, Beginning Linux Programming, Wrox Press, 1996, 1874416680.
Good in−depth coverage of various programming languages available for Linux, including a fairly strong
chapter on shell scripting.
Herbert Mayer, Advanced C Programming on the IBM PC, Windcrest Books, 1989, 0830693637.
Good info on shell scripting, with examples, and a short intro to Tcl and Perl.
Cameron Newham and Bill Rosenblatt, Learning the Bash Shell, 2nd edition, O'Reilly and Associates, 1998,
1−56592−347−2.
This is a valiant effort at a decent shell primer, but somewhat deficient in coverage on programming topics
and lacking sufficient examples.
Anatole Olczak, Bourne Shell Quick Reference Guide, ASP, Inc., 1991, 093573922X.
Jerry Peek, Tim O'Reilly, and Mike Loukides, Unix Power Tools, 2nd edition, O'Reilly and Associates,
Random House, 1997, 1−56592−260−3.
Contains a couple of sections of very informative in−depth articles on shell programming, but falls short of
being a tutorial. It reproduces much of the regular expressions tutorial from the Dougherty and Robbins book,
above.
Bibliography 455
Advanced Bash−Scripting Guide
Clifford Pickover, Computers, Pattern, Chaos, and Beauty, St. Martin's Press, 1990, 0−312−04123−3.
A treasure trove of ideas and recipes for computer−based exploration of mathematical oddities.
George Polya, How To Solve It, Princeton University Press, 1973, 0−691−02356−5.
Chet Ramey and Brian Fox, The GNU Bash Reference Manual, Network Theory Ltd, 2003, 0−9541617−7−7.
This manual is the definitive reference for GNU Bash. The authors of this manual, Chet Ramey and Brian
Fox, are the original developers of GNU Bash. For each copy sold the publisher donates $1 to the Free
Software Foundation.
Excellent Bash pocket reference (don't leave home without it). A bargain at $4.95, but also available for free
download on−line in pdf format.
Arnold Robbins, Effective Awk Programming, Free Software Foundation / O'Reilly and Associates, 2000,
1−882114−26−4.
The absolute best awk tutorial and reference. The free electronic version of this book is part of the awk
documentation, and printed copies of the latest version are available from O'Reilly and Associates.
This book has served as an inspiration for the author of this document.
Bill Rosenblatt, Learning the Korn Shell, O'Reilly and Associates, 1993, 1−56592−054−6.
Paul Sheer, LINUX: Rute User's Tutorial and Exposition, 1st edition, , 2002, 0−13−033351−4.
Bibliography 456
Advanced Bash−Scripting Guide
Ellen Siever and the staff of O'Reilly and Associates, Linux in a Nutshell, 2nd edition, O'Reilly and
Associates, 1999, 1−56592−585−8.
The all−around best Linux command reference, even has a Bash section.
Dave Taylor, Wicked Cool Shell Scripts: 101 Scripts for Linux, Mac OS X, and Unix Systems, 1st edition, No
Starch Press, 2004, 1−59327−012−7.
The UNIX CD Bookshelf, 3rd edition, O'Reilly and Associates, 2003, 0−596−00392−7.
An array of seven UNIX books on CD ROM, including UNIX Power Tools, Sed and Awk, and Learning the
Korn Shell. A complete set of all the UNIX references and tutorials you would ever need at about $130. Buy
this one, even if it means going into debt and not paying the rent.
−−−
Fioretti, Marco, "Scripting for X Productivity," Linux Journal, Issue 113, September, 2003, pp. 86−9.
Ben Okopnik's well−written introductory Bash scripting articles in issues 53, 54, 55, 57, and 59 of the Linux
Gazette, and his explanation of "The Deep, Dark Secrets of Bash" in issue 56.
Chet Ramey's bash − The GNU Shell, a two−part series published in issues 3 and 4 of the Linux Journal,
July−August 1994.
Bibliography 457
Advanced Bash−Scripting Guide
Chet Ramey's Bash F.A.Q.
Very nice sed, awk, and regular expression tutorials at The UNIX Grymoire.
The GNU gawk reference manual (gawk is the extended GNU version of awk available on Linux and BSD
systems).
Bibliography 458
Advanced Bash−Scripting Guide
The Linux USB subsystem (helpful in writing scripts affecting USB peripherals).
There is some nice material on I/O redirection in chapter 10 of the textutils documentation at the University
of Alberta site.
Rick Hohensee has written the osimpa i386 assembler entirely as Bash scripts.
Aurelio Marinho Jargas has written a Regular expression wizard. He has also written an informative book on
Regular Expressions, in Portuguese.
Ben Tomkins has created the Bash Navigator directory management tool.
William Park has been working on a project to incorporate certain Awk and Python features into Bash.
Among these is a gdbm interface. He has released bashdiff on Freshmeat.net. He has an article in the
November, 2004 issue of the Linux Gazette on adding string functions to Bash, with a followup article in the
December issue, and yet another in the January, 2005 issue.
Peter Knowles has written an elaborate Bash script that generates a book list on the Sony Librie e−book
reader. This useful tool permits loading non−DRM user content on the Librie.
Of historical interest are Colin Needham's original International Movie Database (IMDB) reader polling
scripts, which nicely illustrate the use of awk for string parsing.
−−−
The excellent Bash Reference Manual, by Chet Ramey and Brian Fox, distributed as part of the "bash−2−doc"
package (available as an rpm). See especially the instructive example scripts in this package.
The manpages for bash and bash2, date, expect, expr, find, grep, gzip, ln, patch, tar, tr, bc, xargs. The
texinfo documentation on bash, dd, m4, gawk, and sed.
Bibliography 459
Advanced Bash−Scripting Guide
Bibliography 460
Appendix A. Contributed Scripts
These scripts, while not fitting into the text of this document, do illustrate some interesting shell programming
techniques. They are useful, too. Have fun analyzing and running them.
#!/bin/bash
# mail−format.sh (ver. 1.1): Format e−mail messages.
# Gets rid of carets, tabs, and also folds excessively long lines.
# =================================================================
# Standard Check for Script Argument(s)
ARGS=1
E_BADARGS=65
E_NOFILE=66
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# A variable can hold a sed script.
sedscript='s/^>//
s/^ *>//
s/^ *//
s/ *//'
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
exit 0
#! /bin/bash
#
# Very simpleminded filename "rename" utility (based on "lowercase.sh").
#
# The "ren" utility, by Vladimir Lanin (lanin@csd2.nyu.edu),
#+ does a much better job of this.
ARGS=2
E_BADARGS=65
ONE=1 # For getting singular/plural right (see below).
if [ $# −ne "$ARGS" ]
then
echo "Usage: `basename $0` old−pattern new−pattern"
# As in "rn gif jpg", which renames all gif files in working directory to jpg.
exit $E_BADARGS
fi
exit 0
# Exercises:
# −−−−−−−−−
# What type of files will this not work on?
# How can this be fixed?
#
# Rewrite this script to process all the files in a directory
#+ containing spaces in their names, and to rename them,
#+ substituting an underscore for each space.
exit 0
Example A−4. encryptedpw: Uploading to an ftp site, using a locally encrypted password
#!/bin/bash
E_BADARGS=65
if [ −z "$1" ]
then
echo "Usage: `basename $0` filename"
exit $E_BADARGS
fi
Server="XXX"
Directory="YYY" # Change above to actual server name & directory.
exit 0
#!/bin/bash
# copy−cd.sh: copying a data CD
echo "Do you want to erase the image file (y/n)? " # Probably a huge file.
read answer
case "$answer" in
[yY]) rm −f $OF
echo "$OF erased."
echo
# Exercise:
# Change the above "case" statement to also accept "yes" and "Yes" as input.
exit 0
#!/bin/bash
# collatz.sh
MAX_ITERATIONS=200
# For large seed numbers (>32000), increase MAX_ITERATIONS.
h=${1:−$$} # Seed
# Use $PID as seed,
#+ if not specified as command−line arg.
echo
echo "C($h) −−− $MAX_ITERATIONS Iterations"
echo
done
echo
exit 0
#!/bin/bash
# days−between.sh: Number of days between two dates.
# Usage: ./days−between.sh [M]M/[D]D/YYYY [M]M/[D]D/YYYY
#
# Note: Script modified to account for changes in Bash 2.05b
#+ that closed the loophole permitting large negative
#+ integer return values.
day=$1
month=$2
year=$3
echo $Days
Parse_Date $1
check_date $day $month $year # See if valid date.
Parse_Date $2
check_date $day $month $year
strip_leading_zero $day
day=$?
strip_leading_zero $month
month=$?
echo $diff
exit 0
# Compare this script with
#+ the implementation of Gauss' Formula in a C program at:
#+ http://buschencrew.hypermart.net/software/datedif
#!/bin/bash
# makedict.sh [make dictionary]
E_BADARGS=65
exit 0
#!/bin/bash
# soundex.sh: Calculate "soundex" code for names
# =======================================================
# Soundex script
# by
# Mendel Cooper
# thegrendel@theriver.com
# 23 January, 2002
#
# Placed in the Public Domain.
#
# A slightly different version of this script appeared in
#+ Ed Schaefer's July, 2002 "Shell Corner" column
#+ in "Unix Review" on−line,
#+ http://www.unixreview.com/documents/uni1026336632258/
# =======================================================
if [ $# −ne "$ARGCOUNT" ]
then
echo "Usage: `basename $0` name"
exit $E_WRONGARGS
fi
val1=bfpv # 'b,f,p,v' = 1
val2=cgjkqsxz # 'c,g,j,k,q,s,x,z' = 2
val3=dt # etc.
val4=l
val5=mn
val6=r
input_name="$1"
echo
echo "Name = $input_name"
assign_value $name
s1=$value
assign_value $name1
s2=$value
assign_value $char1
s3=$value
s3=9$s3 # If first letter of name is a vowel
#+ or 'w' or 'h',
#+ then its "value" will be null (unset).
#+ Therefore, set it to 9, an otherwise
#+ unused value, which can be tested for.
echo
# Examples:
# Smith and Smythe both have a "S−530" soundex.
# Harrison = H−625
# Hargison = H−622
# Harriman = H−655
# This works out fairly well in practice, but there are numerous anomalies.
#
#
# The U.S. Census and certain other governmental agencies use soundex,
# as do genealogical researchers.
#
# For more information,
#+ see the "National Archives and Records Administration home page",
#+ http://www.nara.gov/genealogy/soundex/soundex.html
# Exercise:
# −−−−−−−−
# Simplify the "Exception Patch" section of this script.
exit 0
#!/bin/bash
# life.sh: "Life in the Slow Lane"
# Version 2: Patched by Daniel Albers
#+ to allow non−square grids as input.
# ##################################################################### #
# This is the Bash script version of John Conway's "Game of Life". #
# "Life" is a simple implementation of cellular automata. #
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− #
# On a rectangular grid, let each "cell" be either "living" or "dead". #
# Designate a living cell with a dot, and a dead one with a blank space.#
# Begin with an arbitrarily drawn dot−and−blank grid, #
#+ and let this be the starting generation, "generation 0". #
# Determine each successive generation by the following rules: #
# 1) Each cell has 8 neighbors, the adjoining cells #
#+ left, right, top, bottom, and the 4 diagonals. #
# 123 #
# 4*5 #
# 678 #
# #
# 2) A living cell with either 2 or 3 living neighbors remains alive. #
# 3) A dead cell with 3 living neighbors becomes alive (a "birth"). #
SURVIVE=2 #
BIRTH=3 #
# 4) All other cases result in a dead cell for the next generation. #
# ##################################################################### #
############################################
# Abort script if "startfile" not specified
#+ AND
#+ "gen0" not present.
E_NOSTARTFILE=68
if [ ! −e "$startfile" ]
then
echo "Startfile \""$startfile"\" missing!"
exit $E_NOSTARTFILE
fi
############################################
ALIVE1=.
DEAD1=_
# Represent living and "dead" cells in the start−up file.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− #
# =================================================================
display ()
{
declare −a arr
arr=( `echo "$1"` ) # Convert passed arg to array.
element_count=${#arr[*]}
local i
local rowcheck
cell=${arr[i]}
if [ "$cell" = . ]
then
let "alive += 1"
fi
return
local row
local lower_limit=0 # Disallow negative coordinate.
local upper_limit
local left
local right
row=$2
let "left = $row * $COLS" # Left limit.
let "right = $left + $COLS − 1" # Right limit.
done
if [ ${array[$cell_number]} = "$ALIVE1" ]
then
let "count −= 1" # Make sure value of tested cell itself
fi #+ is not counted.
return $count
local array
local i=0
if [ "$alive" −eq 0 ]
then
echo
echo "Premature exit: no more cells alive!"
exit $NONE_ALIVE # No point in continuing
fi #+ if no live cells.
# =========================================================
# main ()
echo # Title
echo "======================="
echo " $GENERATIONS generations"
echo " of"
echo "\"Life in the Slow Lane\""
echo "======================="
echo
exit 0 # END
The following two scripts are by Mark Moraes of the University of Toronto. See the enclosed file
"Moraes−COPYRIGHT" for permissions and restrictions.
#! /bin/sh
# Strips off the header from a mail/News message i.e. till the first
# empty line
# Mark Moraes, University of Toronto
if [ $# −eq 0 ]; then
# ==> If no command line args present, then works on file redirected to stdin.
sed −e '1,/^$/d' −e '/^[ ]*$/d'
# −−> Delete empty lines and all lines until
# −−> first one beginning with white space.
else
# ==> If command line args present, then work on files named.
for i do
sed −e '1,/^$/d' −e '/^[ ]*$/d' $i
# −−> Ditto, as above.
done
fi
#! /bin/sh
# $Id: ftpget,v 1.2 91/05/07 21:15:43 moraes Exp $
# Script to perform batch anonymous ftp. Essentially converts a list of
# of command line arguments into input to ftp.
# ==> This script is nothing but a shell wrapper around "ftp" . . .
# Simple, and quick − written as a companion to ftplist
# −h specifies the remote host (default prep.ai.mit.edu)
# −d specifies the remote directory to cd to − you can provide a sequence
# of −d options − they will be cd'ed to in turn. If the paths are relative,
# make sure you get the sequence right. Be careful with relative paths −
# there are far too many symlinks nowadays.
# (default is the ftp login directory)
# −v turns on the verbose option of ftp, and shows all responses from the
# ftp server.
# −f remotefile[:localfile] gets the remote file into localfile
# −m pattern does an mget with the specified pattern. Remember to quote
# shell characters.
# −c does a local cd to the specified directory
# For example,
# ftpget −h expo.lcs.mit.edu −d contrib −f xplaces.shar:xplaces.sh \
# −d ../pub/R3/fixes −c ~/fixes −m 'fix*'
# will get xplaces.shar from ~ftp/contrib on expo.lcs.mit.edu, and put it in
# xplaces.sh in the current working directory, and get all fixes from
# ~ftp/pub/R3/fixes and put them in the ~/fixes directory.
# Obviously, the sequence of the options is important, since the equivalent
# commands are executed by ftp in corresponding order
#
# Mark Moraes <moraes@csri.toronto.edu>, Feb 1, 1989
#
# PATH=/local/bin:/usr/ucb:/usr/bin:/bin
# export PATH
# ==> Above 2 lines from original script probably superfluous.
E_BADARGS=65
TMPFILE=/tmp/ftp.$$
# ==> Creates temp file, using process id of script ($$)
# ==> to construct filename.
SITE=`domainname`.toronto.edu
# ==> 'domainname' similar to 'hostname'
# ==> May rewrite this to parameterize this for general use.
rm −f ${TMPFILE}
# ==> Finally, tempfile deleted (you may wish to copy it to a logfile).
# ==> Exercises:
# ==> −−−−−−−−−
# ==> 1) Add error checking.
# ==> 2) Add bells & whistles.
+
Antek Sawicki contributed the following script, which makes very clever use of the parameter substitution
operators discussed in Section 9.3.
#!/bin/bash
# May need to be invoked with #!/bin/bash2 on older machines.
#
# Random password generator for Bash 2.x by Antek Sawicki <tenox@tenox.tc>,
# who generously gave permission to the document author to use it here.
MATRIX="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
# ==> Password will consist of alphanumeric characters.
LENGTH="8"
# ==> May change 'LENGTH' for longer password.
# ==> ${MATRIX:$(($RANDOM%${#MATRIX})):1}
# ==> returns expansion of MATRIX at random position, by length 1.
# ==> See {var:pos:len} parameter substitution in Chapter 9.
# ==> and the associated examples.
# ==> PASS=... simply pastes this result onto previous PASS (concatenation).
let n+=1
# ==> Increment 'n' for next pass.
done
exit 0
+
James R. Van Zandt contributed this script, which uses named pipes and, in his words, "really exercises
quoting and escaping".
#!/bin/bash
# ==> Script by James R. Van Zandt, and used here with his permission.
# ==> The end result is this backs up the main directories, from / on down.
exit 0
+
Stéphane Chazelas contributed the following script to demonstrate that generating prime numbers does not
require arrays.
#!/bin/bash
# primes.sh: Generate prime numbers, without using arrays.
# Script contributed by Stephane Chazelas.
Primes()
{
(( n = $1 + 1 )) # Bump to next integer.
shift # Next parameter in list.
# echo "_n=$n i=$i_"
if (( n == LIMIT ))
then echo $*
return
fi
Primes 1
exit 0
#!/bin/bash
# tree.sh
search () {
for dir in `echo *`
# ==> `echo *` lists all the files in current working directory,
#+ ==> without line breaks.
# ==> Similar effect to for dir in *
# ==> but "dir in `echo *`" will not handle filenames with blanks.
do
if [ −d "$dir" ] ; then # ==> If it is a directory (−d)...
zz=0 # ==> Temp variable, keeping track of directory level.
while [ $zz != $1 ] # Keep track of inner nested loop.
do
echo −n "| " # ==> Display vertical connector symbol,
# ==> with 2 spaces & no line feed in order to indent.
zz=`expr $zz + 1` # ==> Increment zz.
done
if [ $# != 0 ] ; then
cd $1 # move to indicated directory.
#else # stay in current directory
fi
search 0
echo "Total directories = $numdirs"
exit 0
Noah Friedman gave permission to use his string function script, which essentially reproduces some of the
C−library string manipulation functions.
#!/bin/bash
# Commentary:
# Code:
#:docstring strcat:
# Usage: strcat s1 s2
#
# Strcat appends the value of variable s2 to variable s1.
#
# Example:
# a="foo"
# b="bar"
# strcat a b
# echo $a
# => foobar
#
#:end docstring:
#:docstring strncat:
# Usage: strncat s1 s2 $n
#
# Line strcat, but strncat appends a maximum of n characters from the value
# of variable s2. It copies fewer if the value of variabl s2 is shorter
# than n characters. Echoes result on stdout.
#
# Example:
# a=foo
# b=barbaz
# strncat a b 3
# echo $a
# => foobar
#
#:end docstring:
###;;;autoload
function strncat ()
{
local s1="$1"
local s2="$2"
local −i n="$3"
local s1_val s2_val
eval "$s1"=\'"${s1_val}${s2_val}"\'
# ==> eval $1='${s1_val}${s2_val}' avoids problems,
# ==> if one of the variables contains a single quote.
}
#:docstring strcmp:
# Usage: strcmp $s1 $s2
#
# Strcmp compares its arguments and returns an integer less than, equal to,
# or greater than zero, depending on whether string s1 is lexicographically
# less than, equal to, or greater than string s2.
#:end docstring:
###;;;autoload
function strcmp ()
{
[ "$1" = "$2" ] && return 0
return 1
}
#:docstring strncmp:
# Usage: strncmp $s1 $s2 $n
###;;;autoload
function strncmp ()
{
if [ −z "${3}" −o "${3}" −le "0" ]; then
return 0
fi
#:docstring strlen:
# Usage: strlen s
#
# Strlen returns the number of characters in string literal s.
#:end docstring:
###;;;autoload
function strlen ()
{
eval echo "\${#${1}}"
# ==> Returns the length of the value of the variable
# ==> whose name is passed as an argument.
}
#:docstring strspn:
# Usage: strspn $s1 $s2
#
# Strspn returns the length of the maximum initial segment of string s1,
# which consists entirely of characters from string s2.
#:end docstring:
###;;;autoload
function strspn ()
{
# Unsetting IFS allows whitespace to be handled as normal chars.
local IFS=
local result="${1%%[!${2}]*}"
echo ${#result}
}
#:docstring strcspn:
# Usage: strcspn $s1 $s2
#
# Strcspn returns the length of the maximum initial segment of string s1,
# which consists entirely of characters not from string s2.
#:end docstring:
###;;;autoload
echo ${#result}
}
#:docstring strstr:
# Usage: strstr s1 s2
#
# Strstr echoes a substring starting at the first occurrence of string s2 in
# string s1, or nothing if s2 does not occur in the string. If s2 points to
# a string of zero length, strstr echoes s1.
#:end docstring:
###;;;autoload
function strstr ()
{
# if s2 points to a string of zero length, strstr echoes s1
[ ${#2} −eq 0 ] && { echo "$1" ; return 0; }
# use the pattern matching code to strip off the match and everything
# following it
first=${1/$2*/}
#:docstring strtok:
# Usage: strtok s1 s2
#
# Strtok considers the string s1 to consist of a sequence of zero or more
# text tokens separated by spans of one or more characters from the
# separator string s2. The first call (with a non−empty string s1
# specified) echoes a string consisting of the first token on stdout. The
# function keeps track of its position in the string s1 between separate
# calls, so that subsequent calls made with the first argument an empty
# string will work through the string immediately following that token. In
# this way subsequent calls will work through the string s1 until no tokens
# remain. The separator string s2 may be different from call to call.
# When no token remains in s1, an empty value is echoed on stdout.
#:end docstring:
###;;;autoload
function strtok ()
{
:
}
#:docstring strtrunc:
# Usage: strtrunc $n $s1 {$s2} {$...}
#
# Used by many functions like strncmp to truncate arguments for comparison.
###;;;autoload
function strtrunc ()
{
n=$1 ; shift
for z; do
echo "${z:0:$n}"
done
}
# provide string
# ========================================================================== #
# ==> Everything below here added by the document author.
# strcat
string0=one
string1=two
echo
echo "Testing \"strcat\" function:"
echo "Original \"string0\" = $string0"
echo "\"string1\" = $string1"
strcat string0 string1
echo "New \"string0\" = $string0"
echo
# strlen
echo
echo "Testing \"strlen\" function:"
str=123456789
echo "\"str\" = $str"
echo −n "Length of \"str\" = "
strlen str
echo
# Exercise:
# −−−−−−−−
# Add code to test all the other string functions above.
exit 0
Michael Zick's complex array example uses the md5sum check sum command to encode directory
information.
#! /bin/bash
# directory−info.sh
# Parses and lists directory information.
# Controls
# If overridden by command arguments, they must be in the order:
# Arg1: "Descriptor Directory"
# Arg2: "Exclude Paths"
# Arg3: "Exclude Directories"
#
# Environment Settings override Defaults.
# Command arguments override Environment Settings.
Unless it is formatted:
inode permissions hard−links owner group ...
266705 crw−rw−−−− 1 root uucp
ListDirectory()
{
local −a T
local −i of=0 # Default return in variable
# OLD_IFS=$IFS # Using BASH default ' \t\n'
case "$#" in
3) case "$1" in
−of) of=1 ; shift ;;
* ) return 1 ;;
esac ;;
2) : ;; # Poor man's "continue"
*) return 1 ;;
esac
case $of in
# Assign T back to the array whose name was passed as $2
0) eval $2=\( \"\$\{T\[@\]\}\" \) ;;
# Write T into filename passed as $2
1) echo "${T[@]}" > "$2" ;;
esac
return 0
}
IsNumber()
{
local −i int
if [ $# −eq 0 ]
then
return 1
else
(let int=$1) 2>/dev/null
return $? # Exit status of the let thread
fi
}
: <<IndexListDoc
Walk an array of directory fields produced by ListDirectory
Each line gets two index entries, the first element of each line
(inode) and the element that holds the pathname of the file.
The following index pairs (if any) hold element indexes into
the Field−Array−Name per:
Index−Array−Name[Line−Number * 2] : The "inode" field element.
NOTE: This distance may be either +11 or +12 elements.
Index−Array−Name[(Line−Number * 2) + 1] : The "pathname" element.
NOTE: This distance may be a variable number of elements.
Next line index pair for Line−Number+1.
IndexListDoc
else
((Lidx+=1))
fi
done
case "$of" in
0) eval $2=\( \"\$\{INDEX\[@\]\}\" \) ;;
1) echo "${INDEX[@]}" > "$2" ;;
esac
return 0 # What could go wrong?
}
The key (no pun intended) to a Unified Content File System (UCFS)
is to distinguish the files in the system based on their content.
Distinguishing files by their name is just, so, 20th Century.
DigestFilesDoc
DigestFile()
{
local if=0 # Default, variable name
local −a T1 T2
case "$#" in
3) case "$1" in
−if) if=1 ; shift ;;
* ) return 1 ;;
esac ;;
2) : ;; # Poor man's "continue"
*) return 1 ;;
esac
case ${#T2[@]} in
0) return 1 ;;
1) return 1 ;;
2) case ${T2[1]:0:1} in # SanScrit−2.0.5
\*) T2[${#T2[@]}]=${T2[1]:1}
T2[1]=\*
;;
*) T2[${#T2[@]}]=${T2[1]}
T2[1]=" "
;;
esac
;;
3) : ;; # Assume it worked
*) return 1 ;;
esac
local −i len=${#T2[0]}
if [ $len −ne 32 ] ; then return 1 ; fi
eval $2=\( \"\$\{T2\[@\]\}\" \)
}
# # # # # Locate File # # # # #
#
# LocateFile [−l] FileName Location−Array−Name
# or
# LocateFile [−l] −of FileName Location−Array−FileName
# # # # #
−*−*− Per:
Return code: 0
Size of array: 11
Contents of array
Element 0: /home/mszick
Element 1: 0
Element 2: 0
Element 3: 255
Element 4: ef53
Element 5: 2581445
Element 6: 2277180
Element 7: 2146050
Element 8: 4096
Element 9: 1311552
Element 10: 1276425
LocateFile()
{
local −a LOC LOC1 LOC2
local lk="" of=0
case "$#" in
0) return 1 ;;
1) return 1 ;;
2) : ;;
*) while (( "$#" > 2 ))
do
case "$1" in
−l) lk=−1 ;;
−of) of=1 ;;
*) return 1 ;;
esac
shift
done ;;
esac
# More Sanscrit−2.0.5
# LOC1=( $(stat −t $lk $1) )
# LOC2=( $(stat −tf $lk $1) )
# Uncomment above two lines if system has "stat" command installed.
LOC=( ${LOC1[@]:0:1} ${LOC1[@]:3:11}
${LOC2[@]:1:2} ${LOC2[@]:4:1} )
case "$of" in
0) eval $2=\( \"\$\{LOC\[@\]\}\" \) ;;
1) echo "${LOC[@]}" > "$2" ;;
esac
return 0
# Which yields (if you are lucky, and have "stat" installed)
# −*−*− Location Discriptor −*−*−
# Return code: 0
# Size of array: 15
# Contents of array
# Element 0: /home/mszick 20th Century name
# Element 1: 41e8 Type and Permissions
# Element 2: 500 User
# Element 3: 500 Group
# Element 4: 303 Device
# Element 5: 32385 inode
# Element 6: 22 Link count
# Element 7: 0 Device Major
# Element 8: 0 Device Minor
# Element 9: 1051224608 Last Access
# Element 10: 1051214068 Last Modify
# Element 11: 1051214068 Last Status
# Element 12: 0 UUID (to be)
# Element 13: 0 Volume Label (to be)
# Element 14: ef53 Filesystem type
}
declare −a CUR_DIR
# For small arrays
ListDirectory "${PWD}" CUR_DIR
ListArray CUR_DIR
declare −a DIR_DIG
DigestFile CUR_DIR DIR_DIG
echo "The new \"name\" (checksum) for ${CUR_DIR[9]} is ${DIR_DIG[0]}"
declare −a DIR_ENT
# BIG_DIR # For really big arrays − use a temporary file in ramdisk
# BIG−DIR # ListDirectory −of "${CUR_DIR[11]}/*" "/tmpfs/junk2"
ListDirectory "${CUR_DIR[11]}/*" DIR_ENT
declare −a DIR_IDX
# BIG−DIR # IndexList −if "/tmpfs/junk2" DIR_IDX
IndexList DIR_ENT DIR_IDX
declare −a IDX_DIG
# BIG−DIR # DIR_ENT=( $(cat /tmpfs/junk2) )
# BIG−DIR # DigestFile −if /tmpfs/junk2 IDX_DIG
DigestFile DIR_ENT IDX_DIG
# Small (should) be able to parallize IndexList & DigestFile
# Large (should) be able to parallize IndexList & DigestFile & the assignment
echo "The \"name\" (checksum) for the contents of ${PWD} is ${IDX_DIG[0]}"
declare −a FILE_LOC
LocateFile ${PWD} FILE_LOC
ListArray FILE_LOC
exit 0
Stéphane Chazelas demonstrates object−oriented programming in a Bash script.
#!/bin/bash
# obj−oriented.sh: Object−oriented programming in a shell script.
# Script by Stephane Chazelas.
# Important Note:
# −−−−−−−−− −−−−
# If running this script under version 3 or later of Bash,
eval "$obj_name.set_name() {
eval \"$obj_name.get_name() {
echo \$1
}\"
}"
eval "$obj_name.set_firstname() {
eval \"$obj_name.get_firstname() {
echo \$1
}\"
}"
eval "$obj_name.set_birthdate() {
eval \"$obj_name.get_birthdate() {
echo \$1
}\"
eval \"$obj_name.show_birthdate() {
echo \$(date −d \"1/1/1970 0:0:\$1 GMT\")
}\"
eval \"$obj_name.get_age() {
echo \$(( (\$(date +%s) − \$1) / 3600 / 24 / 365 ))
}\"
}"
$obj_name.set_name $name
$obj_name.set_firstname $firstname
$obj_name.set_birthdate $birthdate
}
echo
self.get_firstname # Bozo
self.get_name # Bozeman
self.get_age # 28
self.get_birthdate # 101272413
self.show_birthdate # Sat Mar 17 20:13:33 MST 1973
echo
# typeset −f
#+ to see the created functions (careful, it scrolls off the page).
exit 0
Mariusz Gniazdowski contributes a hash library for use in scripts.
# Hash:
# Hash function library
# Limitations:
# * Only global variables are supported.
# * Each hash instance generates one global variable per value.
# * Variable names collisions are possible
#+ if you define variable like __hash__hashname_key
# * Keys must use chars that can be part of a Bash variable name
#+ (no dashes, periods, etc.).
# * The hash is created as a variable:
# ... hashname_keyname
# So if somone will create hashes like:
# myhash_ + mykey = myhash__mykey
# myhash + _mykey = myhash__mykey
# Then there will be a collision.
# (This should not pose a major problem.)
Hash_config_varname_prefix=__hash__
# Emulates: hash[key]=value
#
# Params:
# 1 − hash
# 2 − key
# 3 − value
function hash_set {
eval "${Hash_config_varname_prefix}${1}_${2}=\"${3}\""
}
# Emulates: value=hash[key]
#
# Params:
# 1 − hash
# 2 − key
# 3 − value (name of global variable to set)
function hash_get_into {
eval "$3=\"\$${Hash_config_varname_prefix}${1}_${2}\""
}
# Emulates: hash1[key1]=hash2[key2]
#
# Params:
# 1 − hash1
# Emulates: hash[keyN−1]=hash[key2]=...hash[key1]
#
# Copies first key to rest of keys.
#
# Params:
# 1 − hash1
# 2 − key1
# 3 − key2
# . . .
# N − keyN
function hash_dup {
local hashName="$1" keyName="$2"
shift 2
until [ ${#} −le 0 ]; do
eval "${Hash_config_varname_prefix}${hashName}_${1}=\"\$${Hash_config_varname_pre
shift;
done;
}
#!/bin/bash
# hash−example.sh: Colorizing text.
# Author: Mariusz Gniazdowski <mgniazd−at−gmail.com>
# $1 − keyname
# $2 − value
try_colors() {
echo −en "$2"
echo "This line is $1."
}
hash_foreach colors try_colors
hash_echo colors reset_color −en
echo −e '\nLet us delete them and try colors once more . . .\n'
exit $?
# This code is free software covered by GNU GPL license version 2 or above.
# Please refer to http://www.gnu.org/ for the full license text.
#
# Some code lifted from usb−mount by Michael Hamilton's usb−mount (LGPL)
#+ see http://users.actrix.co.nz/michael/usbmount.html
#
# INSTALL
# −−−−−−−
# Put this in /etc/hotplug/usb/diskonkey.
# Then look in /etc/hotplug/usb.distmap, and copy all usb−storage entries
#+ into /etc/hotplug/usb.usermap, substituting "usb−storage" for "diskonkey".
# Otherwise this code is only run during the kernel module invocation/removal
#+ (at least in my tests), which defeats the purpose.
#
# TODO
# −−−−
# Handle more than one diskonkey device at one time (e.g. /dev/diskonkey1
#+ and /mnt/diskonkey1), etc. The biggest problem here is the handling in
#+ devlabel, which I haven't yet tried.
#
# AUTHOR and SUPPORT
# −−−−−−−−−−−−−−−−−−
# Konstantin Riabitsev, <icon linux duke edu>.
# Send any problem reports to my email address at the moment.
#
# ==> Comments added by ABS Guide author.
SYMLINKDEV=/dev/diskonkey
MOUNTPOINT=/mnt/diskonkey
DEVLABEL=/sbin/devlabel
DEVLABELCONFIG=/etc/sysconfig/devlabel
IAM=$0
##
# Functions lifted near−verbatim from usb−mount code.
#
function allAttachedScsiUsb {
find /proc/scsi/ −path '/proc/scsi/usb−storage*' −type f | xargs grep −l 'Attached: Yes'
}
function scsiDevFromScsiUsb {
echo $1 | awk −F"[−/]" '{ n=$(NF−1); print "/dev/sd" substr("abcdefghijklmnopqrstuvwxyz", n+
1) }'
}
exit 0
Here is something to warm the hearts of webmasters and mistresses everywhere: a script that saves weblogs.
#!/bin/bash
# archiveweblogs.sh v1.0
PROBLEM=66
# Are we root?
USER=`$ID −u`
if [ "X$USER" != "X0" ]; then
echo "PANIC: Only root can run this script!"
exit 0
How do you keep the shell from expanding and reinterpreting strings?
#! /bin/bash
# protect_literal.sh
# set −vx
:<<−'_Protect_Literal_String_Doc'
Usage:
_protect_literal_str 'Whatever string meets your ${fancy}'
Just echos the argument to standard out, hard quotes
restored.
Notes:
The strange names (_*) are used to avoid trampling on
the user's chosen names when this is sourced as a
library.
_Protect_Literal_String_Doc
_protect_literal_str() {
# :<<−'_Protect_Literal_String_Test'
# # # Remove the above "# " to disable this code. # # #
# Which yields:
# − − Test One − −
# 'Hello $user' is 13 long.
# 'Hello "${username}"' is 21 long.
# Which yields:
# − − Test Two − −
# Element 0: zero is: 4 long. # Our marker element
# Element 1: 'Hello ${Me}' is: 13 long. # Our "$(_pls '...' )"
# Element 2: Hello ${You} is: 12 long. # Quotes are missing
# Element 3: \'Pass: \' is: 10 long. # ${pw} expanded to nothing
# Which yields:
# − − Test Three − −
# Element 0: zero is: 4 long. # Our marker element.
# Element 1: Hello ${Me} is: 11 long. # Intended result.
# Element 2: Hello is: 5 long. # ${You} expanded to nothing.
# Element 3: 'Pass: is: 6 long. # Split on the whitespace.
# Element 4: ' is: 1 long. # The end quote is here now.
# Our Element 1 has had its leading and trailing hard quotes stripped.
# Although not shown, leading and trailing whitespace is also stripped.
# Now that the string contents are set, Bash will always, internally,
#+ hard quote the contents as required during its operations.
# Why?
# Considering our "$(_pls 'Hello ${Me}')" construction:
# " ... " −> Expansion required, strip the quotes.
# $( ... ) −> Replace with the result of..., strip this.
# _pls ' ... ' −> called with literal arguments, strip the quotes.
# The result returned includes hard quotes; BUT the above processing
#+ has already been done, so they become part of the value assigned.
#
# Similarly, during further usage of the string variable, the ${Me}
#+ is part of the contents (result) and survives any operations
# (Until explicitly told to evaluate the string).
# Hint: See what happens when the hard quotes ($'\x27') are replaced
#+ with soft quotes ($'\x22') in the above procedures.
# Interesting also is to remove the addition of any quoting.
# _Protect_Literal_String_Test
# # # Remove the above "# " to disable this code. # # #
exit 0
What if you want the shell to expand and reinterpret strings?
# set −vx
:<<−'_UnProtect_Literal_String_Doc'
Usage:
Complement of the "$(_pls 'Literal String')" function.
(See the protect_literal.sh example.)
StringVar=$(_upls ProtectedSringVariable)
Does:
When used on the right−hand−side of an assignment statement;
makes the substitions embedded in the protected string.
Notes:
The strange names (_*) are used to avoid trampling on
the user's chosen names when this is sourced as a
library.
_UnProtect_Literal_String_Doc
_upls() {
local IFS=$'x1B' # \ESC character (not required)
eval echo $@ # Substitution on the glob.
}
# :<<−'_UnProtect_Literal_String_Test'
# # # Remove the above "# " to disable this code. # # #
_pls() {
local IFS=$'x1B' # \ESC character (not required)
echo $'\x27'$@$'\x27' # Hard quoted parameter glob
}
# set −vx
# _UnProtect_Literal_String_Test
# # # Remove the above "# " to disable this code. # # #
exit 0
This powerful script helps hunt down spammers.
#!/bin/bash
#######################################################
# Documentation
# See also "Quickstart" at end of script.
#######################################################
:<<−'__is_spammer_Doc_'
Impatient?
Application code: goto "# # # Hunt the Spammer' program code # # #"
Example output: ":<<−'_is_spammer_outputs_'"
How to use: Enter script name without arguments.
Or goto "Quickstart" at end of script.
Provides
Given a domain name or IP(v4) address as input:
Requires
A working Internet connection.
(Exercise: Add check and/or abort if not on−line when running script.)
Bash with arrays (2.05b+).
Usage
Script requires a single argument, which may be:
See also, the Quickstart at the end of this script (after 'exit').
Return Codes
0 − All OK
1 − Script failure
2 − Something is Blacklisted
SPAMMER_DATA
If set to a writable file, script will dump its
discovered data in the form of GraphViz file.
See: http://www.research.att.com/sw/tools/graphviz
SPAMMER_LIMIT
Limits the depth of resource tracing.
Default is 2 levels.
Additional documentation
Download the archived set of scripts
explaining and illustrating the function contained within this script.
http://personal.riverusers.com/mszick_clf.tar.bz2
Study notes
This script uses a large number of functions.
Nearly all general functions have their own example script.
Each of the example scripts have tutorial level comments.
Scripting project
Add support for IP(v6) addresses.
IP(v6) addresses are recognized but not processed.
Advanced project
Add the reverse lookup detail to the discovered information.
__is_spammer_Doc_
#######################################################
pend_mark() {
pend_func pend_stop_mark
}
# Recursion is depth−first−by−name.
# The expand_input_address maintains this list
#+ to prohibit looking up addresses twice during
#+ domain name recursion.
declare −a been_there_addr
been_there_addr=( '127.0.0.1' ) # Whitelist localhost
if [ ${#known_name[@]} −gt 0 ]
then
echo >>${_dot_file}
echo '# Known domain name nodes' >>${_dot_file}
_dd_cnt=${#known_name[@]}
for (( _dd = 0 ; _dd < _dd_cnt ; _dd++ ))
do
printf ' N%04u [label="%s"] ;\n' \
"${_dd}" "${known_name[${_dd}]}" >>${_dot_file}
done
fi
if [ ${#known_address[@]} −gt 0 ]
then
echo >>${_dot_file}
echo '# Known address nodes' >>${_dot_file}
_dd_cnt=${#known_address[@]}
for (( _dd = 0 ; _dd < _dd_cnt ; _dd++ ))
do
printf ' A%04u [label="%s"] ;\n' \
"${_dd}" "${known_address[${_dd}]}" >>${_dot_file}
done
fi
echo >>${_dot_file}
echo '/*' >>${_dot_file}
echo ' * Known relationships :: User conversion to' >>${_dot_file}
echo ' * graphic form by hand or program required.' >>${_dot_file}
echo ' *' >>${_dot_file}
if [ ${#auth_chain[@]} −gt 0 ]
then
echo >>${_dot_file}
echo '# Authority reference edges followed and field source.' >>${_dot_file}
dump_to_dot auth_chain AC
fi
if [ ${#ref_chain[@]} −gt 0 ]
then
echo >>${_dot_file}
echo '# Name reference edges followed and field source.' >>${_dot_file}
if [ ${#name_address[@]} −gt 0 ]
then
echo >>${_dot_file}
echo '# Known name−>address edges' >>${_dot_file}
dump_to_dot name_address NA
fi
if [ ${#name_srvc[@]} −gt 0 ]
then
echo >>${_dot_file}
echo '# Known name−>service edges' >>${_dot_file}
dump_to_dot name_srvc NS
fi
if [ ${#name_resource[@]} −gt 0 ]
then
echo >>${_dot_file}
echo '# Known name−>resource edges' >>${_dot_file}
dump_to_dot name_resource NR
fi
if [ ${#parent_child[@]} −gt 0 ]
then
echo >>${_dot_file}
echo '# Known parent−>child edges' >>${_dot_file}
dump_to_dot parent_child PC
fi
if [ ${#list_server[@]} −gt 0 ]
then
echo >>${_dot_file}
echo '# Known Blacklist nodes' >>${_dot_file}
_dd_cnt=${#list_server[@]}
for (( _dd = 0 ; _dd < _dd_cnt ; _dd++ ))
do
printf ' LS%04u [label="%s"] ;\n' \
"${_dd}" "${list_server[${_dd}]}" >>${_dot_file}
done
fi
# Recursion limiter
# limit_chk() <next_level>
limit_chk() {
local −i _lc_lmt
# Check indirection limit.
if [ ${indirect} −eq 0 ] || [ $# −eq 0 ]
then
# The 'do−forever' choice
echo 1 # Any value will do.
return 0 # OK to continue.
else
# Limiting is in effect.
if [ ${indirect} −lt ${1} ]
then
echo ${1} # Whatever.
return 1 # Stop here.
else
_lc_lmt=${1}+1 # Bump the given limit.
echo ${_lc_lmt} # Echo it.
return 0 # OK to continue.
fi
fi
if ! _ein_cnt=$(limit_chk ${1})
then
return 0
fi
if [ ${#auth_chain[@]} −gt 0 ]
then
echo
echo 'Known chain of authority.'
col_print auth_chain 2 5 30 55
fi
if [ ${#reverse_pair[@]} −gt 0 ]
then
echo
echo 'Known reverse pairs.'
col_print reverse_pair 2 5 55
fi
return 0
}
_usage_statement_
}
declare −a default_servers
# See: http://www.spamhaus.org (Conservative, well maintained)
default_servers[0]='sbl−xbl.spamhaus.org'
# See: http://ordb.org (Open mail relays)
default_servers[1]='relays.ordb.org'
# See: http://www.spamcop.net/ (You can report spammers here)
default_servers[2]='bl.spamcop.net'
# See: http://www.spews.org (An 'early detect' system)
default_servers[3]='l2.spews.dnsbl.sorbs.net'
# See: http://www.dnsbl.us.sorbs.net/using.shtml
default_servers[4]='dnsbl.sorbs.net'
# See: http://dsbl.org/usage (Various mail relay lists)
default_servers[5]='list.dsbl.org'
default_servers[6]='multihop.dsbl.org'
default_servers[7]='unconfirmed.dsbl.org'
local −a _la_lines
set −f
local IFS=${NO_WSP}
eval _la_lines=\(\ \$\{$1\[@\]\}\ \)
echo
echo "Element count "${#_la_lines[@]}" array "${1}
local _ln_cnt=${#_la_lines[@]}
##############################
# Example output from script #
##############################
:<<−'_is_spammer_outputs_'
./is_spammer.bash 0 web4.alojamentos7.com
_is_spammer_outputs_
exit ${_hs_RC}
####################################################
# The script ignores everything from here on down #
#+ because of the 'exit' command, just above. #
####################################################
Quickstart
==========
Prerequisites
Optional Prerequisites
Quick Start
Usage Details
i. export SPAMMER_LIMIT=1
or whatever limit you want.
N0000 [label="guardproof.info."] ;
N0002 [label="third.guardproof.info."] ;
A0000 [label="61.141.32.197"] ;
/*
*/
N0000 [label="guardproof.info."] ;
N0002 [label="third.guardproof.info."] ;
A0000 [label="61.141.32.197"] ;
N0000−>N0002 ;
N0002−>A0000 ;
/*
*/
Process that with the 'dot' program, and you have your
first network diagram.
# End Quickstart.
Additional Note
========== ====
#!/bin/bash
# whx.sh: "whois" spammer lookup
# Author: Walter Dnes
# Slight revisions (first section) by ABS Guide author.
# Used in ABS Guide with permission.
if [ −e "$OUTFILE" ]
then
rm −f "$OUTFILE"
echo "Stale output file \"$OUTFILE\" removed."; echo
fi
# Sanity checks.
# (This section needs more work.)
# ===============================
if [ −z "$IPADDR" ]
# No response.
then
if [[ "$IPADDR" =~ "^[;;]" ]]
# ;; connection timed out; no servers could be reached
then
echo "Host lookup timed out!"
exit $E_TIMEOUT # Bail out.
fi
if [[ "$IPADDR" =~ "[(NXDOMAIN)]$" ]]
# Host xxxxxxxxx.xxx not found: 3(NXDOMAIN)
then
echo "Host not found!"
exit $E_NOHOST # Bail out.
fi
if [[ "$IPADDR" =~ "[(SERVFAIL)]$" ]]
# Host xxxxxxxxx.xxx not found: 2(SERVFAIL)
then
echo "Host not found!"
exit $E_NOHOST # Bail out.
fi
AFRINICquery() {
# Define the function that queries AFRINIC. Echo a notification to the
#+ screen, and then run the actual query, redirecting output to $OUTFILE.
APNICquery() {
echo "Searching for $IPADDR in whois.apnic.net"
whois −h whois.apnic.net "$IPADDR" > $OUTFILE
ARINquery() {
echo "Searching for $IPADDR in whois.arin.net"
whois −h whois.arin.net "$IPADDR" > $OUTFILE
LACNICquery() {
echo "Searching for $IPADDR in whois.lacnic.net"
whois −h whois.lacnic.net "$IPADDR" > $OUTFILE
RIPEquery() {
echo "Searching for $IPADDR in whois.ripe.net"
whois −h whois.ripe.net "$IPADDR" > $OUTFILE
}
if [ $slash8 == 0 ]; then
echo $IPADDR is '"This Network"' space\; Not querying
elif [ $slash8 == 10 ]; then
echo $IPADDR is RFC1918 space\; Not querying
elif [ $slash8 == 14 ]; then
echo $IPADDR is '"Public Data Network"' space\; Not querying
elif [ $slash8 == 127 ]; then
echo $IPADDR is loopback space\; Not querying
elif [ $slash16 == 169.254 ]; then
echo $IPADDR is link−local space\; Not querying
elif [ $slash8 == 172 ] && [ $octet2 −ge 16 ] && [ $octet2 −le 31 ];then
echo $IPADDR is RFC1918 space\; Not querying
elif [ $slash16 == 192.168 ]; then
echo $IPADDR is RFC1918 space\; Not querying
elif [ $slash8 −ge 224 ]; then
echo $IPADDR is either Multicast or reserved space\; Not querying
elif [ $slash8 −ge 200 ] && [ $slash8 −le 201 ]; then LACNICquery "$IPADDR"
elif [ $slash8 −ge 202 ] && [ $slash8 −le 203 ]; then APNICquery "$IPADDR"
elif [ $slash8 −ge 210 ] && [ $slash8 −le 211 ]; then APNICquery "$IPADDR"
elif [ $slash8 −ge 218 ] && [ $slash8 −le 223 ]; then APNICquery "$IPADDR"
else
ARINquery "$IPADDR"
if grep "whois.afrinic.net" "$OUTFILE"; then
AFRINICquery "$IPADDR"
elif grep −E "^OrgID:[ ]+RIPE$" "$OUTFILE"; then
RIPEquery "$IPADDR"
elif grep −E "^OrgID:[ ]+APNIC$" "$OUTFILE"; then
APNICquery "$IPADDR"
elif grep −E "^OrgID:[ ]+LACNIC$" "$OUTFILE"; then
LACNICquery "$IPADDR"
fi
fi
#@ −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Try also:
# wget http://logi.cc/nw/whois.php3?ACTION=doQuery&DOMAIN=$IPADDR
#@ −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
cat $OUTFILE
# Or "less $OUTFILE" . . .
exit 0
#!/bin/bash
# wgetter2.bash
# This is wgetter2 −−
#+ a Bash script to make wget a bit more friendly, and save typing.
# =======================================================================
# changelog:
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Set some other variables and explain them.
pattern=" −A .jpg,.JPG,.jpeg,.JPEG,.gif,.GIF,.htm,.html,.shtml,.php"
# wget's option to only get certain types of file.
# comment out if not using
today=`date +%F` # Used for a filename.
home=$HOME # Set HOME to an internal variable.
# In case some other path is used, change it here.
depthDefault=3 # Set a sensible default recursion.
Depth=$depthDefault # Otherwise user feedback doesn't tie in properly.
RefA="" # Set blank referring page.
Flag="" # Default to not saving anything,
#+ or whatever else might be wanted in future.
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# added added added added added added added added added added added added
if [ ! −e "$Cookie_List" ]; then
# Set up a list of cookie files, if there isn't one.
echo "Hunting for cookies . . ."
find −name cookies.txt >> $Cookie_List # Create the list of cookie files.
fi # Isolate this in its own 'if' statement,
#+ in case we got interrupted while searching.
# end added section end added section end added section end added section end
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# Another variable.
# This one may or may not be subject to variation.
# A bit like the small print.
CookiesON=$Cookie
# echo "cookie file is $CookiesON" # For debugging.
# echo "home is ${home}" # For debugging. Got caught with this one!
wopts()
{
echo "Enter options to pass to wget."
echo "It is assumed you know what you're doing."
echo
echo "You can pass their arguments here too."
# That is to say, everything passed here is passed to wget.
read Wopts
# Read in the options to be passed to wget.
Woptions=" $Wopts"
# Assign to another variable.
# Just for fun, or something . . .
return
}
save_func()
{
echo "Settings will be saved."
if [ ! −d $savePath ]; then # See if directory exists.
mkdir $savePath # Create the directory to save things in
#+ if it isn't already there.
fi
Flag=S
# Tell the final bit of code what to do.
# Set a flag since stuff is done in main.
list_func() # Gives the user the option to use the −i option to wget,
#+ and a list of URLs.
{
while [ 1 ]; do
echo "Enter the name of the file containing URL's (press q to change your
mind)."
read urlfile
if [ ! −e "$urlfile" ] && [ "$urlfile" != q ]; then
# Look for a file, or the quit option.
echo "That file does not exist!"
elif [ "$urlfile" = q ]; then # Check quit option.
echo "Not using a url list."
return
else
echo "using $urlfile."
echo "If you gave me url's on the command line, I'll use those first."
# Report wget standard behaviour to the user.
lister=" −i $urlfile" # This is what we want to pass to wget.
return
fi
done
}
cookie_func() # Give the user the option to use a different cookie file.
{
while [ 1 ]; do
echo "Change the cookies file. Press return if you don't want to change
it."
read Cookies
# NB: this is not the same as Cookie, earlier.
run_func()
{
if [ −z "$OPTARG" ]; then
# Test to see if we used the in−line option or the query one.
if [ ! −d "$savePath" ]; then # In case directory doesn't exist . . .
echo "$savePath does not appear to exist."
echo "Please supply path and filename of saved wget commands:"
read newFile
until [ −f "$newFile" ]; do # Keep going till we get something.
echo "Sorry, that file does not exist. Please try again."
# Try really hard to get something.
read newFile
done
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# if [ −z ( grep wget ${newfile} ) ]; then
# Assume they haven't got the right file and bail out.
# echo "Sorry, that file does not contain wget commands. Aborting."
# exit
# fi
#
# This is bogus code.
# It doesn't actually work.
# If anyone wants to fix it, feel free!
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
filePath="${newFile}"
else
echo "Save path is $savePath"
echo "Please enter name of the file which you want to use."
echo "You have a choice of:"
ls $savePath # Give them a choice.
read inFile
until [ −f "$savePath/$inFile" ]; do # Keep going till we get something.
if [ ! −f "${savePath}/${inFile}" ]; then # If file doesn't exist.
echo "Sorry, that file does not exist. Please choose from:"
ls $savePath # If a mistake is made.
read inFile
fi
done
filePath="${savePath}/${inFile}" # Make one variable . . .
fi
else filePath="${savePath}/${OPTARG}" # Which can be many things . . .
fi
exit
}
URLS=" $@"
# Use this so that URL list can be changed if we stay in the option loop.
while [ 1 ]; do
# This is where we ask for the most used options.
# (Mostly unchanged from version 1 of wgetter)
if [ −z $curDepth ]; then
Current=""
else Current=" Current value is $curDepth"
fi
echo "How deep should I go? (integer: Default is $depthDefault.$Current)"
read Depth # Recursion −− how far should we go?
inputB="" # Reset this to blank on each pass of the loop.
echo "Enter the name of the referring page (default is none)."
echo "Do you want to have the output logged to the terminal"
echo "(y/n, default is yes)?"
read noHide # Otherwise wget will just log it to a file.
if [ ! −z $inputB ]; then
RefA=" −−referer=$inputB" # Option to use referring page.
fi
WGETTER="${CommandA}${pattern}${hide}${RefA}${Recurse}${CookiesON}${lister}${Woptions}${URLS}"
# Just string the whole lot together . . .
# NB: no embedded spaces.
# They are in the individual elements so that if any are empty,
#+ we don't get an extra space.
echo ""
read
case $REPLY in # Need to change this to a 'trap' clause.
q|Q ) exit $E_USER_EXIT;; # Exercise for the reader?
* ) URLS=" $REPLY";;
esac
echo ""
done
exit 0
#!/bin/bash
# bashpodder.sh:
# By Linc 10/1/2004
# Find the latest script at http://linc.homeunix.org:8080/scripts/bashpodder
# Last revision 12/14/2004 − Many Contributors!
# If you use this and have made improvements or have comments
# drop me an email at linc dot fessenden at gmail dot com
# I'd appreciate it!
# ==> ################################################################
#
# ==> What is "podcasting"?
# ==> ################################################################
# Read the bp.conf file and wget any url not already in the podcast.log file:
while read podcast
do # ==> Main action follows.
file=$(wget −q $podcast −O − | tr '\r' '\n' | tr \' \" | sed −n 's/.*url="\([^"]*\)".*/\1
for url in $file
do
echo $url >> temp.log
if ! grep "$url" podcast.log > /dev/null
then
wget −q −P $datadir "$url"
fi
done
done < bp.conf
exit 0
#################################################
For a different scripting approach to Podcasting,
see Phil Salkie's article,
"Internet Radio to Podcast with Shell Tools"
in the September, 2005 issue of LINUX JOURNAL,
http://www.linuxjournal.com/article/8171
#################################################
To end this section, a review of the basics . . . and more.
#!/bin/bash
# basics−reviewed.bash
# This script tested under Bash versions 2.04, 2.05a and 2.05b.
# It may not work with earlier versions.
# This demonstration script generates one −−intentional−−
#+ "command not found" error message. See line 394.
# The current Bash maintainer, Chet Ramey, has fixed the items noted
#+ for an upcoming version of Bash.
###−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−###
### Pipe the output of this script to 'more' ###
###+ else it will scroll off the page. ###
### ###
### You may also redirect its output ###
###+ to a file for examination. ###
###−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−###
# Glob−Pattern references
echo $* # All parameters to script or function
echo ${*} # Same
# All−Elements−Of references
echo $@ # Same as above
echo ${@} # Same as above
echo
echo '− − Within double−quotes − Default IFS of space−tab−newline − −'
IFS=$'\x20'$'\x09'$'\x0A' # These three bytes,
#+ in exactly this order.
echo
echo '− − Within double−quotes − First character of IFS is ^ − −'
# Any printing, non−whitespace character should do the same.
IFS='^'$IFS # ^ + space tab newline
###
printf %q "${ArrayVar[*]}" # Glob−Pattern All−Elements−Of
echo
echo 'echo command:'"${ArrayVar[*]}"
###
printf %q "${ArrayVar[@]}" # All−Elements−Of
echo
echo 'echo command:'"${ArrayVar[@]}"
echo
echo '− − Within double−quotes − Without whitespace in IFS − −'
IFS='^:%!'
###
printf %q "${ArrayVar[*]}" # Glob−Pattern All−Elements−Of
echo
echo 'echo command:'"${ArrayVar[*]}"
###
printf %q "${ArrayVar[@]}" # All−Elements−Of
echo
echo 'echo command:'"${ArrayVar[@]}"
echo
echo '− − Within double−quotes − IFS set and empty − −'
IFS=''
###
printf %q "${ArrayVar[*]}" # Glob−Pattern All−Elements−Of
echo
echo 'echo command:'"${ArrayVar[*]}"
###
printf %q "${ArrayVar[@]}" # All−Elements−Of
echo
echo 'echo command:'"${ArrayVar[@]}"
echo
echo '− − Within double−quotes − IFS undefined − −'
unset IFS
###
printf %q "${ArrayVar[*]}" # Glob−Pattern All−Elements−Of
echo
echo 'echo command:'"${ArrayVar[*]}"
###
printf %q "${ArrayVar[@]}" # All−Elements−Of
echo
echo 'echo command:'"${ArrayVar[@]}"
# Recall:
# Parameters are similar to arrays and have the similar behaviors.
###
# The above examples demonstrate the possible variations.
# To retain the shape of a sparse array, additional script
#+ programming is required.
###
# The source code of Bash has a routine to output the
#+ [subscript]=value array assignment format.
# As of version 2.05b, that routine is not used,
#+ but that might change in future releases.
###
echo
###
# Function variables
# −−−−−−−−−−−−−−−−−−
echo
echo '− − Function variables − −'
# A variable may represent a signed integer, a string or an array.
# A string may be used like a function name with optional arguments.
# Delayed replacement
# −−−−−−−−−−−−−−−−−−−
echo
echo '− − Delayed replacement − −'
funcVar="$(_print '$VarSomething')" # No replacement, single Bash−Word.
eval $funcVar # $VarSomething replaced HERE.
echo
VarSomething='NewThing'
eval $funcVar # $VarSomething replaced HERE.
echo
# REVIEW:
# −−−−−−
echo
echo '− − Test (but not change) − −'
echo '− null reference −'
echo −n ${VarNull−'NotSet'}' ' # NotSet
echo ${VarNull} # NewLine only
echo −n ${VarNull:−'NotSet'}' ' # NotSet
echo ${VarNull} # Newline only
# ASCII−Art time
# State Y==yes, N==no
# − :−
# Unset Y Y ${# ... } == 0
# Empty N Y ${# ... } == 0
# Contents N N ${# ... } > 0
echo
echo '− − Test and Change − −'
IFS=$'\x20'$'\x09'$'\x0A'
printf %q "${ArraySparse[*]}"
echo
# Note that the output does not distinguish between "null content"
#+ and "null reference".
# Both print as escaped whitespace.
###
# Note also that the output does NOT contain escaped whitespace
#+ for the "null reference(s)" prior to the first defined element.
###
# This behavior of 2.04, 2.05a and 2.05b has been reported
#+ and may change in a future version of Bash.
unset ArraySparse
echo
echo '− − Conditional alternate (But not change)− −'
echo '− No alternate if null reference −'
echo −n ${VarNull+'NotSet'}' '
echo ${VarNull}
unset VarNull
# Alternate literal
echo −n ${VarSomething+'Content'}' ' # Content Literal
echo ${VarSomething}
# Invoke function
echo −n ${VarSomething:+ $(_simple) }' ' # SimpleFunc Literal
echo ${VarSomething}
echo
declare −i t
_incT() {
t=$t+1
}
# Note:
# This is the same test used in the sparse array
#+ listing code fragment.
# ${name?err_msg} ${name:?err_msg}
# These follow the same rules but always exit afterwards
#+ if an action is specified following the question mark.
# The action following the question mark may be a literal
#+ or a function result.
###
# ${name?} ${name:?} are test−only, the return can be tested.
# Element operations
# −−−−−−−−−−−−−−−−−−
echo
echo '− − Trailing sub−element selection − −'
echo
echo '− All after −'
echo ${VarSomething:1} # all non−null after character[0]
echo ${ArrayVar[@]:1} # all after element[0] with content
echo
echo '− Range after −'
echo ${VarSomething:4:3} # ral
# Three characters after
# character[3]
echo
echo ' − − Victim string − −'$stringZ'− − '
echo ' − − Victim array − −'${arrayZ[@]}'− − '
echo ' − − Sparse array − −'${sparseZ[@]}'− − '
echo ' − [0]==null ref, [2]==null ref, [4]==null content − '
echo ' − [1]=abcabc [3]=ABCABC [5]=123123 − '
echo ' − non−null−reference count: '${#sparseZ[@]}' elements'
echo
echo '− − Prefix sub−element removal − −'
echo '− − Glob−Pattern match must include the first character. − −'
echo '− − Glob−Pattern may be a literal or a function result. − −'
echo
echo
echo '− Longest prefix −'
echo ${stringZ##1*3} # Unchanged (not a prefix)
echo ${stringZ##a*C} # abc
echo ${arrayZ[@]##a*c} # ABCABC 123123 ABCABC
echo
echo '− − Suffix sub−element removal − −'
echo '− − Glob−Pattern match must include the last character. − −'
echo '− − Glob−Pattern may be a literal or a function result. − −'
echo
echo '− Shortest suffix −'
echo ${stringZ%1*3} # Unchanged (not a suffix).
echo ${stringZ%$(_abc)} # abcABC123ABC
echo ${arrayZ[@]%abc} # Applied to each element.
echo
echo '− Longest suffix −'
echo ${stringZ%%1*3} # Unchanged (not a suffix)
echo ${stringZ%%b*c} # a
echo ${arrayZ[@]%%b*c} # a ABCABC 123123 ABCABC a
echo
echo '− − Sub−element replacement − −'
echo '− − Sub−element at any location in string. − −'
echo '− − First specification is a Glob−Pattern − −'
echo '− − Glob−Pattern may be a literal or Glob−Pattern function result. − −'
echo '− − Second specification may be a literal or function result. − −'
echo '− − Second specification may be unspecified. Pronounce that'
echo ' as: Replace−With−Nothing (Delete) − −'
echo
echo
echo '− Delete first occurrence −'
echo ${stringZ/$(_123)/}
echo
echo '− Replace all occurrences −'
echo ${stringZ//[b2]/X} # X−out b's and 2's
echo ${stringZ//abc/xyz} # xyzABC123ABCxyz
echo ${arrayZ[@]//abc/xyz} # Applied to each element.
echo ${sparseZ[@]//abc/xyz} # Works as expected.
echo
echo '− Delete all occurrences −'
echo ${stringZ//[b2]/}
echo ${stringZ//abc/}
echo ${arrayZ[@]//abc/}
echo ${sparseZ[@]//abc/}
echo
echo '− − Prefix sub−element replacement − −'
echo '− − Match must include the first character. − −'
echo
echo
echo '− Delete prefix occurrences −'
echo ${stringZ/#[b2]/}
echo ${stringZ/#$(_abc)/}
echo ${arrayZ[@]/#abc/}
echo ${sparseZ[@]/#abc/}
echo
echo '− − Suffix sub−element replacement − −'
echo '− − Match must include the last character. − −'
echo
echo
echo '− Delete suffix occurrences −'
echo ${stringZ/%[b2]/}
echo ${stringZ/%$(_abc)/}
echo ${arrayZ[@]/%abc/}
echo ${sparseZ[@]/%abc/}
echo
echo '− − Special cases of null Glob−Pattern − −'
echo
echo
echo '− Suffix all −'
# null substring pattern means 'suffix'
echo ${stringZ/%/NEW} # abcABC123ABCabcNEW
echo ${arrayZ[@]/%/NEW} # Applied to each element.
echo ${sparseZ[@]/%/NEW} # Applied to null−content also.
# That seems reasonable.
echo
echo '− − Special case For−Each Glob−Pattern − −'
echo '− − − − This is a nice−to−have dream − − − −'
echo
_GenFunc() {
echo −n ${0} # Illustration only.
# Actually, that would be an arbitrary computation.
}
exit 0
############################################################################
#
# cdll
# by Phil Braham
#
# ############################################
# Latest version of this script available from
# http://freshmeat.net/projects/cd/
# ############################################
#
cd_hm ()
{
${PRINTF} "%s" "cd [dir] [0−9] [@[s|h] [−g [<dir>]] [−d] [−D] [−r<n>] [dir|0−9] [−R<n>] [
[−s<n>] [−S<n>] [−u] [−U] [−f] [−F] [−h] [−H] [−v]
<dir> Go to directory
0−n Goto previous directory (0 is previous, 1 is last but 1 etc)
n is up to max history (default is 50)
@ List history and special entries
@h List history entries
@s List special entries
−g [<dir>] Go to literal name (bypass special names)
This is to allow access to dirs called '0','1','−h' etc
−d Change default action − verbose. (See note)
−D Change default action − silent. (See note)
−s<n> Go to the special entry <n>*
−S<n> Go to the special entry <n> and replace it with the current dir*
−r<n> [<dir>] Go to directory <dir> and then put it on special entry <n>*
−R<n> [<dir>] Go to directory <dir> and put current dir on special entry <n>*
−a<n> Alternative suggested directory. See note below.
−f [<file>] File entries to <file>.
−u [<file>] Update entries from <file>.
If no filename supplied then default file (${CDPath}${2:−"$CDFile"}) is used
−F and −U are silent versions
−v Print version number
*The special entries (0 − 9) are held until log off, replaced by another entry
or updated with the −u command
Note that commands R,r,S and s may be used without a number and refer to 0:
$ cd −s Go to special entry 0
$ cd −S Go to special entry 0 and make special entry 0 current dir
$ cd −r 1 Go to history entry 1 and put it on special entry 0
$ cd −r Go to history entry 0 and put it on special entry 0
"
if ${TEST} "$CD_MODE" = "PREV"
then
${PRINTF} "$cd_mnset"
else
${PRINTF} "$cd_mset"
fi
}
cd_Hm ()
{
cd_hm
${PRINTF} "%s" "
The previous directories (0−$cd_maxhistory) are stored in the
environment variables CD[0] − CD[$cd_maxhistory]
Similarly the special directories S0 − $cd_maxspecial are in
the environment variable CDS[0] − CDS[$cd_maxspecial]
and may be accessed from the command line
cd_version ()
{
printf "Version: ${VERSION_MAJOR}.${VERSION_MINOR} Date: ${VERSION_DATE}\n"
}
#
# Truncate right.
#
# params:
# p1 − string
# p2 − length to truncate to
#
# returns string in tcd
#
cd_right_trunc ()
{
local tlen=${2}
local plen=${#1}
local str="${1}"
local diff
local filler="<−−"
if ${TEST} ${plen} −le ${tlen}
then
tcd="${str}"
else
let diff=${plen}−${tlen}
elen=3
if ${TEST} ${diff} −le 2
then
let elen=${diff}
fi
tlen=−${tlen}
let tlen=${tlen}+${elen}
tcd=${filler:0:elen}${str:tlen}
fi
}
#
# Three versions of do history:
# cd_dohistory − packs history and specials side by side
# cd_dohistoryH − Shows only hstory
# cd_dohistoryS − Shows only specials
#
cd_dohistory ()
{
cd_getrc
${PRINTF} "History:\n"
local −i count=${cd_histcount}
while ${TEST} ${count} −ge 0
do
cd_right_trunc "${CD[count]}" ${cd_lchar}
${PRINTF} "%2d %−${cd_lchar}.${cd_lchar}s " ${count} "${tcd}"
cd_dohistoryH ()
{
cd_getrc
${PRINTF} "History:\n"
local −i count=${cd_maxhistory}
while ${TEST} ${count} −ge 0
do
${PRINTF} "${count} %−${cd_flchar}.${cd_flchar}s\n" ${CD[$count]}
count=${count}−1
done
}
cd_dohistoryS ()
{
cd_getrc
${PRINTF} "Specials:\n"
local −i count=${cd_maxspecial}
while ${TEST} ${count} −ge 0
do
${PRINTF} "S${count} %−${cd_flchar}.${cd_flchar}s\n" ${CDS[$count]}
count=${count}−1
done
}
cd_getrc ()
{
cd_flchar=$(stty −a | awk −F \; '/rows/ { print $2 $3 }' | awk −F \ '{ print $4 }')
if ${TEST} ${cd_flchar} −ne 0
then
cd_lchar=${cd_flchar}/2−5
cd_rchar=${cd_flchar}/2−5
cd_flchar=${cd_flchar}−5
else
cd_flchar=${FLCHAR:=75} # cd_flchar is used for for the @s & @h history
cd_lchar=${LCHAR:=35}
cd_rchar=${RCHAR:=35}
fi
}
cd_doselection ()
{
local −i nm=0
cd_doflag="TRUE"
if ${TEST} "${CD_MODE}" = "PREV"
then
if ${TEST} −z "$cd_npwd"
then
cd_npwd=0
fi
fi
tm=$(echo "${cd_npwd}" | cut −b 1)
if ${TEST} "${tm}" = "−"
then
pm=$(echo "${cd_npwd}" | cut −b 2)
nm=$(echo "${cd_npwd}" | cut −d $pm −f2)
case "${pm}" in
a) cd_npwd=${cd_sugg[$nm]} ;;
s) cd_npwd="${CDS[$nm]}" ;;
S) cd_npwd="${CDS[$nm]}" ; CDS[$nm]=`pwd` ;;
r) cd_npwd="$2" ; cd_specDir=$nm ; cd_doselection "$1" "$2";;
cd_chdefm ()
{
if ${TEST} "${CD_MODE}" = "PREV"
then
CD_MODE=""
if ${TEST} $1 −eq 1
then
${PRINTF} "${cd_mset}"
fi
else
CD_MODE="PREV"
if ${TEST} $1 −eq 1
then
${PRINTF} "${cd_mnset}"
fi
fi
}
cd_fsave ()
{
local sfile=${CDPath}${2:−"$CDFile"}
if ${TEST} "$1" = "SHOW"
then
${PRINTF} "Saved to %s\n" $sfile
fi
${RM} −f ${sfile}
local −i count=0
while ${TEST} ${count} −le ${cd_maxhistory}
do
echo "CD[$count]=\"${CD[$count]}\"" >> ${sfile}
count=${count}+1
done
cd_upload ()
{
local sfile=${CDPath}${2:−"$CDFile"}
if ${TEST} "${1}" = "SHOW"
then
${PRINTF} "Loading from %s\n" ${sfile}
fi
. ${sfile}
}
cd_new ()
{
local −i count
local −i choose=0
cd_npwd="${1}"
cd_specDir=−1
cd_doselection "${1}" "${2}"
if ${TEST} ! −z "${CDL_PROMPTLEN}"
then
cd_right_trunc "${PWD}" ${CDL_PROMPTLEN}
cd_rp=${CDL_PROMPT_PRE}${tcd}${CDL_PROMPT_POST}
export PS1="$(echo −ne ${cd_rp})"
fi
}
#################################################################################
# #
# Initialisation here #
# #
#################################################################################
#
VERSION_MAJOR="1"
VERSION_MINOR="2.1"
VERSION_DATE="24−MAY−2003"
#
alias cd=cd_new
#
# Set up commands
RM=/bin/rm
TEST=test
PRINTF=printf # Use builtin printf
#################################################################################
# #
# Change this to modify the default pre− and post prompt strings. #
# These only come into effect if CDL_PROMPTLEN is set. #
# #
#################################################################################
if ${TEST} ${EUID} −eq 0
then
# CDL_PROMPT_PRE=${CDL_PROMPT_PRE:="$HOSTNAME@"}
CDL_PROMPT_PRE=${CDL_PROMPT_PRE:="\\[\\e[01;31m\\]"} # Root is in red
CDL_PROMPT_POST=${CDL_PROMPT_POST:="\\[\\e[00m\\]#"}
else
CDL_PROMPT_PRE=${CDL_PROMPT_PRE:="\\[\\e[01;34m\\]"} # Users in blue
CDL_PROMPT_POST=${CDL_PROMPT_POST:="\\[\\e[00m\\]$"}
fi
#################################################################################
#
# cd_maxhistory defines the max number of history entries allowed.
typeset −i cd_maxhistory=50
#################################################################################
#
# cd_maxspecial defines the number of special entries.
typeset −i cd_maxspecial=9
#
#
#################################################################################
#
# cd_histcount defines the number of entries displayed in the history command.
typeset −i cd_histcount=9
#
#################################################################################
export CDPath=${HOME}/
# Change these to use a different #
# ==================================================================== #
: <<DOCUMENTATION
realtime@mpx.com.au
===============================================================================
This version of cdll has been tested on Linux using Bash. It will work
on most Linux versions but will probably not work on other shells without
modification.
Introduction
============
cdll allows easy moving about between directories. When changing to a new
directory the current one is automatically put onto a stack. By default
50 entries are kept, but this is configurable. Special directories can be
kept for easy access − by default up to 10, but this is configurable. The
most recent stack entries and the special entries can be easily viewed.
The directory stack and special entries can be saved to, and loaded from,
a file. This allows them to be set up on login, saved before logging out
or changed when moving project to project.
Setting up cdll
===============
Copy the file cdfile to your home directory. It will require read and
write access. This a default file that contains a directory stack and
special entries.
/etc/profile
~/.bash_profile
~/.bash_login
~/.profile
~/.bashrc
/etc/bash.bashrc.local
To setup your login, ~/.bashrc is recommended, for global (and root) setup
add the commands to /etc/bash.bashrc.local
If you want to use this instead of the buitin cd command then add:
alias cd='cd_new'
We would also recommend the following commands:
alias @='cd_new @'
cd −U
cd −D
If you want to use cdll's prompt facilty then add the following:
CDL_PROMPTLEN=nn
Where nn is a number described below. Initially 99 would be suitable
number.
######################################################################
# CD Setup
######################################################################
CDL_PROMPTLEN=21 # Allow a prompt length of up to 21 characters
. /usr/bin/cdll # Initialise cdll
alias cd='cd_new' # Replace the built in cd command
alias @='cd_new @' # Allow @ at the prompt to display history
cd −U # Upload directories
cd −D # Set default action to non−posix
######################################################################
cd .
Note that if the previous entry on the stack is the current directory
then the stack is not updated.
Usage
=====
cd [dir] [0−9] [@[s|h] [−g <dir>] [−d] [−D] [−r<n>] [dir|0−9] [−R<n>]
[<dir>|0−9] [−s<n>] [−S<n>] [−u] [−U] [−f] [−F] [−h] [−H] [−v]
<dir> Go to directory
0−n Goto previous directory (0 is previous, 1 is last but 1, etc.)
n is up to max history (default is 50)
@ List history and special entries (Usually available as $ @)
@h List history entries
@s List special entries
−g [<dir>] Go to literal name (bypass special names)
This is to allow access to dirs called '0','1','−h' etc
−d Change default action − verbose. (See note)
−D Change default action − silent. (See note)
−s<n> Go to the special entry <n>
−S<n> Go to the special entry <n> and replace it with the current dir
−r<n> [<dir>] Go to directory <dir> and then put it on special entry <n>
−R<n> [<dir>] Go to directory <dir> and put current dir on special entry <n>
−a<n> Alternative suggested directory. See note below.
−f [<file>] File entries to <file>.
−u [<file>] Update entries from <file>.
If no filename supplied then default file (~/cdfile) is used
−F and −U are silent versions
−v Print version number
−h Help
−H Detailed help
Examples
========
To go to a history entry:
To go to a special entry:
Note that commands R,r,S and s may be used without a number and
refer to 0:
$ cd −s Go to special entry 0
$ cd −S Go to special entry 0 and make special entry 0
current dir
$ cd −r 1 Go to history entry 1 and put it on special entry 0
$ cd −r Go to history entry 0 and put it on special entry 0
ls −l ${CDS[3]}
cat ${CD[8]}/file.txt
Configuration
=============
Note:
CDL_PROMPT_PRE & _POST only t
There are three variables defined in the file cdll which control the
number of entries stored or displayed. They are in the section labeled
'Initialisation here' towards the end of the file.
DOCUMENTATION
Variable Meaning
$0 Name of script
$1 Positional parameter #1
$2 − $9 Positional parameters #2 − #9
${10} Positional parameter #10
$# Number of positional parameters
"$*" All the positional parameters (as a single word)
*
"$@" All the positional parameters (as separate
strings)
${#*} Number of command line parameters passed to
script
${#@} Number of command line parameters passed to
script
$? Return value
$$ Process ID (PID) of script
$− Flags passed to script (using set)
$_ Last argument of previous command
$! Process ID (PID) of last job run in background
* Must be quoted, otherwise it defaults to "$@".
−N File modified since it was last read F1 −nt F2 File F1 is newer than F2 *
−O You own the file F1 −ot F2 File F1 is older than F2 *
−G Group id of file same as yours F1 −ef F2 Files F1 and F2 are hard links to
the same file *
Expression Meaning
${var} Value of var, same as $var
Expression Meaning
${#string} Length of $string
Expression Interpretation
Brackets
if [ CONDITION ] Test construct
if [[ CONDITION ]] Extended test construct
Array[1]=element1 Array initialization
[a−z] Range of characters within a Regular Expression
Curly Brackets
${variable} Parameter substitution
${!variable} Indirect variable reference
{ command1; command2; . . . commandN; Block of code
}
{string1,string2,string3,...} Brace expansion
Parentheses
( command1; command2 ) Command group executed within a subshell
Array=(element1 element2 element3) Array initialization
result=$(COMMAND) Execute command in subshell and assign result to
variable
>(COMMAND) Process substitution
<(COMMAND) Process substitution
Double Parentheses
(( var = 78 )) Integer arithmetic
var=$(( 20 + 5 )) Integer arithmetic, with variable assignment
Quoting
"$variable" "Weak" quoting
'string' "Strong" quoting
Back Quotes
result=`COMMAND` Execute command in subshell and assign result to
variable
This is a very brief introduction to the sed and awk text processing utilities. We will deal with only a few
basic commands here, but that will suffice for understanding simple sed and awk constructs within shell
scripts.
For all their differences, the two utilities share a similar invocation syntax, both use regular expressions , both
read input by default from stdin, and both output to stdout. These are well−behaved UNIX tools, and
they work together well. The output from one can be piped into the other, and their combined capabilities give
shell scripts some of the power of Perl.
One important difference between the utilities is that while shell scripts can easily pass arguments to sed,
it is more complicated for awk (see Example 33−5 and Example 9−24).
C.1. Sed
Sed is a non−interactive line editor. It receives text input, whether from stdin or from a file, performs
certain operations on specified lines of the input, one line at a time, then outputs the result to stdout or to a
file. Within a shell script, sed is usually one of several tool components in a pipe.
Sed determines which lines of its input that it will operate on from the address range passed to it. [87] Specify
this address range either by line number or by a pattern to match. For example, 3d signals sed to delete line 3
of the input, and /windows/d tells sed that you want every line of the input containing a match to
"windows" deleted.
Of all the operations in the sed toolkit, we will focus primarily on the three most commonly used ones. These
are printing (to stdout), deletion, and substitution.
address−range (equivalent
of tr)
g global Operate on every pattern match
within each matched line of
input
Unless the g (global) operator is appended to a substitute command, the substitution operates only on the
first instance of a pattern match within each line.
From the command line and in a shell script, a sed operation may require quoting and certain options.
filename=file1.txt
pattern=BEGIN
Sed uses the −e option to specify that the following string is an instruction or set of instructions. If
there is only a single instruction contained in the string, then this option may be omitted.
Notation Effect
8d Delete 8th line of input.
/^$/d Delete all blank lines.
1,/^$/d Delete from beginning of input up to, and including first blank line.
/Jones/p Print only lines containing "Jones" (with −n option).
s/Windows/Linux/ Substitute "Linux" for first instance of "Windows" found in each input line.
s/BSOD/stability/g Substitute "stability" for every instance of "BSOD" found in each input line.
s/ *$// Delete all spaces at the end of every line.
s/00*/0/g Compress all consecutive sequences of zeroes into a single zero.
/GUI/d Delete all lines containing "GUI".
s/GUI//g Delete all instances of "GUI", leaving the remainder of each line intact.
Substituting a zero−length string for another is equivalent to deleting that string within a line of input. This
leaves the remainder of the line intact. Applying s/GUI// to the line
The most important parts of any application are its GUI and sound effects
results in
The most important parts of any application are its and sound effects
A backslash forces the sed replacement command to continue on to the next line. This has the effect of using
the newline at the end of the first line as the replacement string.
s/^ */\
/g
This substitution replaces line−beginning spaces with a newline. The net result is to replace paragraph indents
with a blank line between paragraphs.
An address range followed by one or more operations may require open and closed curly brackets, with
appropriate newlines.
/[0−9A−Za−z]/,/^$/{
/^$/d
}
This deletes only the first of each set of consecutive blank lines. That might be useful for single−spacing a
text file, but retaining the blank line(s) between paragraphs.
1. Example 33−1
2. Example 33−2
3. Example 12−3
4. Example A−2
5. Example 12−15
6. Example 12−24
7. Example A−12
8. Example A−17
9. Example 12−29
10. Example 10−9
11. Example 12−43
12. Example A−1
13. Example 12−13
14. Example 12−11
15. Example A−10
16. Example 17−12
17. Example 12−16
18. Example A−28
For a more extensive treatment of sed, check the appropriate references in the Bibliography.
C.2. Awk
Awk is a full−featured text processing language with a syntax reminiscent of C. While it possesses an
extensive set of operators and capabilities, we will cover only a couple of these here − the ones most useful
for shell scripting.
Awk breaks each line of input passed to it into fields. By default, a field is a string of consecutive characters
separated by whitespace, though there are options for changing the delimiter. Awk parses and operates on
each separate field. This makes awk ideal for handling structured text files −− especially tables −− data
organized into consistent chunks, such as rows and columns.
Strong quoting (single quotes) and curly brackets enclose segments of awk code within a shell script.
{ total += ${column_number} }
This adds the value of column_number to the running total of "total". Finally, to print "total", there is an END
command block, executed after the script has processed all its input.
END { print total }
Corresponding to the END, there is a BEGIN, for a code block to be performed before awk starts processing
its input.
The following example illustrates how awk can add text−parsing tools to a shell script.
#! /bin/sh
# letter−count2.sh: Counting letter occurrences in a text file.
#
# Script by nyal [nyal@voila.fr].
# Used with permission.
# Recommented by document author.
# Version 1.1: Modified to work with gawk 3.1.3.
# (Will still work with earlier versions.)
INIT_TAB_AWK=""
# Parameter to initialize awk script.
count_case=0
FILE_PARSE=$1
E_PARAMERR=65
usage()
{
echo "Usage: letter−count.sh file letters" 2>&1
# For example: ./letter−count2.sh filename.txt a b c
exit $E_PARAMERR # Not enough arguments passed to script.
}
if [ ! −f "$1" ] ; then
echo "$1: No such file." 2>&1
usage # Print usage message and exit.
fi
if [ −z "$2" ] ; then
echo "$2: No letters specified." 2>&1
usage
fi
# DEBUG:
# echo $INIT_TAB_AWK;
cat $FILE_PARSE |
# Pipe the target file to the following awk script.
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Earlier version of script used:
# awk −v tab_search=0 −v final_tab=0 −v tab=0 −v nb_letter=0 −v chara=0 −v chara2=0 \
awk \
"BEGIN { $INIT_TAB_AWK } \
{ split(\$0, tab, \"\"); \
for (chara in tab) \
{ for (chara2 in tab_search) \
{ if (tab_search[chara2] == tab[chara]) { final_tab[chara2]++ } } } } \
END { for (chara in final_tab) \
{ print tab_search[chara] \" => \" final_tab[chara] } }"
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Nothing all that complicated, just . . .
#+ for−loops, if−tests, and a couple of specialized functions.
exit $?
1. Example 11−12
2. Example 16−8
3. Example 12−29
4. Example 33−5
5. Example 9−24
That's all the awk we'll cover here, folks, but there's lots more to learn. See the appropriate references in the
Bibliography.
There has been an attempt to systematize exit status numbers (see /usr/include/sysexits.h), but this
is intended for C and C++ programmers. A similar standard for scripting might be appropriate. The author of
this document proposes restricting user−defined exit codes to the range 64 − 113 (in addition to 0, for
success), to conform with the C/C++ standard. This would allot 50 valid codes, and make troubleshooting
scripts more straightforward.
All user−defined exit codes in the accompanying examples to this document now conform to this standard,
except where overriding circumstances exist, as in Example 9−2.
Issuing a $? from the command line after a shell script exits gives results consistent with the table above
only from the Bash or sh prompt. Running the C−shell or tcsh may give different values in some cases.
A command expects the first three file descriptors to be available. The first, fd 0 (standard input, stdin), is
for reading. The other two (fd 1, stdout and fd 2, stderr) are for writing.
There is a stdin, stdout, and a stderr associated with each command. ls 2>&1 means temporarily
connecting the stderr of the ls command to the same "resource" as the shell's stdout.
By convention, a command reads its input from fd 0 (stdin), prints normal output to fd 1 (stdout), and
error ouput to fd 2 (stderr). If one of those three fd's is not open, you may encounter problems:
For example, when xterm runs, it first initializes itself. Before running the user's shell, xterm opens the
terminal device (/dev/pts/<n> or something similar) three times.
At this point, Bash inherits these three file descriptors, and each command (child process) run by Bash inherits
them in turn, except when you redirect the command. Redirection means reassigning one of the file
descriptors to another file (or a pipe, or anything permissible). File descriptors may be reassigned locally (for
a command, a command group, a subshell, a while or if or case or for loop...), or globally, for the remainder of
the shell (using exec).
#! /usr/bin/env bash
exec 3>&1
(
(
(
while read a; do echo "FIFO2: $a"; done < /tmp/fifo2 | tee /dev/stderr | tee /dev/fd/4 | tee /
exec 3> /tmp/fifo2
) 4>&1 >&3 3>&− | while read a; do echo "FD4: $a"; done 1>&3 5>&− 6>&−
) 5>&1 >&3 | while read a; do echo "FD5: $a"; done 1>&3 6>&−
) 6>&1 >&3 | while read a; do echo "FD6: $a"; done 3>&−
rm −f /tmp/fifo1 /tmp/fifo2
# For each command and subshell, figure out which fd points to what.
exit 0
Traditionally, UNIX command−line options consist of a dash, followed by one or more lowercase letters. The
GNU utilities added a double−dash, followed by a complete word or compound word.
• −h
−−help
−−version
• −a
−−all
−−list
Output filename
• −q
−−quiet
−R
−−recursive
−−verbose
−−compress
However:
−f
−−file
−f
−−force
Many UNIX and Linux utilities deviate from this "standard," so it is dangerous to assume that a given
option will behave in a standard way. Always check the man page for the command in question when in
doubt.
A complete table of recommended options for the GNU utilities is available at
http://www.gnu.org/prep/standards_19.html.
• −c
Read commands from the following string and assign any arguments to the positional parameters.
• −r
−−restricted
End of options. Anything further on the command line is an argument, not an option.
logout file
$HOME/.bash_logout
user−specific instruction file, found in each user's home directory. Upon exit from a login (Bash)
shell, the commands in this file execute.
• /bin
System binaries. Basic system administrative programs and utilities (such as fsck).
• /usr/sbin
Of particular interest are the /etc/fstab (filesystem table), /etc/mtab (mounted filesystem
table), and the /etc/inittab files.
• /etc/rc.d
Device directory. Entries (but not mount points) for physical and virtual devices. See Chapter 27.
• /proc
Process directory. Contains information and statistics about running processes and kernel parameters.
See Chapter 27.
• /sys
Systemwide device directory. Contains information and statistics about device and device names. This
is newly added to Linux with the 2.6.X kernels.
• /mnt
Mount. Directory for mounting hard drive partitions, such as /mnt/dos, and physical devices. In
newer Linux distros, the /media directory has taken over as the preferred mount point for I/O
devices.
• /media
In newer Linux distros, the preferred mount point for I/O devices, such as CD ROMs or USB flash
drives.
• /var
Variable (changeable) system files. This is a catchall "scratchpad" directory for data generated while
a Linux/UNIX machine is running.
• /var/log
System boot directory. The kernel, module links, system map, and boot manager reside here.
A localized shell script echoes its text output in the language defined as the system's locale. A Linux user in
Berlin, Germany, would get script output in German, whereas his cousin in Berlin, Maryland, would get
output from the same script in English.
To create a localized script, use the following template to write all messages to the user (error messages,
prompts, etc.).
#!/bin/bash
# localized.sh
# Script by Stéphane Chazelas,
#+ modified by Bruno Haible, bugfixed by Alfredo Pironti.
. gettext.sh
E_CDERROR=65
error()
{
printf "$@" >&2
exit $E_CDERROR
}
# −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Alfredo Pironti comments:
# This script has been modified to not use the $"..." syntax in
#+ favor of the "`gettext \"...\"`" syntax.
# This is ok, but with the new localized.sh program, the commands
#+ "bash −D filename" and "bash −−dump−po−string filename"
#+ will produce no output
#+ (because those command are only searching for the $"..." strings)!
# The ONLY way to extract strings from the new file is to use the
# 'xgettext' program. However, the xgettext program is buggy.
1. understands the gettext and eval_gettext commands (whereas bash −−dump−po−strings understands
only its deprecated $"..." syntax)
2. can extract comments placed by the programmer, intended to be read by the translator.
This shell code is then not specific to Bash any more; it works the same way with Bash 1.x and other
/bin/sh implementations.
Now, build a language.po file for each language that the script will be translated into, specifying the
msgstr. Alfredo Pironti gives the following example:
fr.po:
#: a:6
msgid "Can't cd to $var."
msgstr "Impossible de se positionner dans le repertoire $var."
#: a:7
msgid "Enter the value: "
msgstr "Entrez la valeur : "
# The string are dumped with the variable names, not with the %s syntax,
#+ similar to C programs.
#+ This is a very cool feature if the programmer uses
#+ variable names that make sense!
Then, run msgfmt.
TEXTDOMAINDIR=/usr/local/share/locale
TEXTDOMAIN=localized.sh
If a user on a French system runs the script, she will get French messages.
With older versions of Bash or other shells, localization requires gettext, using the −s option. In this
case, the script becomes:
#!/bin/bash
# localized.sh
E_CDERROR=65
error() {
local format=$1
shift
printf "$(gettext −s "$format")" "$@" >&2
exit $E_CDERROR
}
cd $var || error "Can't cd to %s." "$var"
read −p "$(gettext −s "Enter the value: ")" var
# ...
The TEXTDOMAIN and TEXTDOMAINDIR variables need to be set and exported to the environment. This
should be done within the script itself.
−−−
This appendix written by Stéphane Chazelas, with modifications suggested by Alfredo Pironti, and by Bruno
Haible, maintainer of GNU gettext.
1. history
2. fc
bash$ history
1 mount /mnt/cdrom
2 cd /mnt/cdrom
3 ls
...
1. $HISTCMD
2. $HISTCONTROL
3. $HISTIGNORE
4. $HISTFILE
5. $HISTFILESIZE
6. $HISTSIZE
7. $HISTTIMEFORMAT (Bash, ver. 3.0 or later)
8. !!
9. !$
10. !#
11. !N
12. !−N
13. !STRING
14. !?STRING?
15. ^STRING^string^
#!/bin/bash
# history.sh
# Attempt to use 'history' command in a script.
history
bash$ ./history.sh
(no output)
The Advancing in the Bash Shell site gives a good introduction to the use of history commands in Bash.
Emmanuel Rouat contributed the following very elaborate .bashrc file, written for a Linux system. He
welcomes reader feedback on it.
Study the file carefully, and feel free to reuse code snippets and functions from it in your own .bashrc file
or even in your scripts.
#===============================================================
#
# PERSONAL $HOME/.bashrc FILE for bash−2.05a (or later)
#
# Last modified: Tue Apr 15 20:32:34 CEST 2003
#
# This file is read (normally) by interactive shells only.
# Here is the place to define your aliases, functions and
# other interactive features like your prompt.
#
# This file was designed (originally) for Solaris but based
# on Redhat's default .bashrc file
# −−> Modified for Linux.
# The majority of the code you'll find here is based on code found
# on Usenet (or internet).
# This bashrc file is a bit overcrowded − remember it is just
# just an example. Tailor it to your needs
#
#
#===============================================================
#−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Source global definitions (if any)
#−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
if [ −f /etc/bashrc ]; then
. /etc/bashrc # −−> Read /etc/bashrc, if present.
fi
#−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Automatic setting of $DISPLAY (if not set already)
# This works for linux − your mileage may vary....
# The problem is that different types of terminals give
# different answers to 'who am i'......
# I have not found a 'universal' method yet
#−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
function get_xserver ()
{
case $TERM in
xterm )
XSERVER=${XSERVER%%:*}
;;
aterm | rxvt)
# find some code that works here.....
;;
esac
}
if [ −z ${DISPLAY:=""} ]; then
get_xserver
if [[ −z ${XSERVER} || ${XSERVER} == $(hostname) || ${XSERVER} == "unix" ]]; then
DISPLAY=":0.0" # Display on local host
else
DISPLAY=${XSERVER}:0.0 # Display on remote host
fi
fi
export DISPLAY
#−−−−−−−−−−−−−−−
# Some settings
#−−−−−−−−−−−−−−−
# Enable options:
shopt −s cdspell
shopt −s cdable_vars
shopt −s checkhash
shopt −s checkwinsize
shopt −s mailwarn
shopt −s sourcepath
shopt −s no_empty_cmd_completion # bash>=2.04 only
shopt −s cmdhist
shopt −s histappend histreedit histverify
shopt −s extglob # necessary for programmable completion
# Disable options:
shopt −u mailwarn
unset MAILCHECK # I don't want my shell to warn me of incoming mail
#−−−−−−−−−−−−−−−−−−−−−−−
# Greeting, motd etc...
#−−−−−−−−−−−−−−−−−−−−−−−
#−−−−−−−−−−−−−−−
# Shell Prompt
#−−−−−−−−−−−−−−−
function fastprompt()
{
unset PROMPT_COMMAND
case $TERM in
*term | rxvt )
PS1="${HILIT}[\h]$NC \W > \[\033]0;\${TERM} [\u@\h] \w\007\]" ;;
linux )
PS1="${HILIT}[\h]$NC \W > " ;;
*)
PS1="[\h] \W > " ;;
esac
}
function powerprompt()
{
_powerprompt()
{
LOAD=$(uptime|sed −e "s/.*: \([^,]*\).*/\1/" −e "s/ //g")
}
PROMPT_COMMAND=_powerprompt
case $TERM in
*term | rxvt )
PS1="${HILIT}[\A \$LOAD]$NC\n[\h \#] \W > \[\033]0;\${TERM} [\u@\h] \w\007\]" ;;
linux )
#===============================================================
#
# ALIASES AND FUNCTIONS
#
# Arguably, some functions defined here are quite big
# (ie 'lowercase') but my workstation has 512Meg of RAM, so .....
# If you want to make this file smaller, these functions can
# be converted into scripts.
#
# Many functions were taken (almost) straight from the bash−2.04
# examples.
#
#===============================================================
#−−−−−−−−−−−−−−−−−−−
# Personnal Aliases
#−−−−−−−−−−−−−−−−−−−
alias h='history'
alias j='jobs −l'
alias r='rlogin'
alias which='type −all'
alias ..='cd ..'
alias path='echo −e ${PATH//:/\\n}'
alias print='/usr/bin/lp −o nobanner −d $LPDEST' # Assumes LPDEST is defined
alias pjet='enscript −h −G −fCourier9 −d $LPDEST' # Pretty−print using enscript
alias background='xv −root −quit −max −rmode 5' # Put a picture in the background
alias du='du −kh'
alias df='df −kTh'
# The 'ls' family (this assumes you use the GNU ls)
alias la='ls −Al' # show hidden files
alias ls='ls −hF −−color' # add colors for filetype recognition
alias lx='ls −lXB' # sort by extension
alias lk='ls −lSr' # sort by size
alias lc='ls −lcr' # sort by change time
alias lu='ls −lur' # sort by access time
alias lr='ls −lR' # recursive ls
alias lt='ls −ltr' # sort by date
alias lm='ls −al |more' # pipe through 'more'
alias tree='tree −Csu' # nice alternative to 'ls'
# tailoring 'less'
alias more='less'
export PAGER=less
export LESSCHARSET='latin1'
export LESSOPEN='|/usr/bin/lesspipe.sh %s 2>&−' # Use this if lesspipe.sh exists
#−−−−−−−−−−−−−−−−
# a few fun ones
#−−−−−−−−−−−−−−−−
function xtitle ()
{
case "$TERM" in
*term | rxvt)
echo −n −e "\033]0;$*\007" ;;
*)
;;
esac
}
# aliases...
alias top='xtitle Processes on $HOST && top'
alias make='xtitle Making $(basename $PWD) ; make'
alias ncftp="xtitle ncFTP ; ncftp"
# .. and functions
function man ()
{
for i ; do
xtitle The $(basename $1|tr −d .[:digit:]) manual
command man −F −a "$i"
done
}
function ll(){ ls −l "$@"| egrep "^d" ; ls −lXB "$@" 2>&−| egrep −v "^d|total "; }
function te() # wrapper around xemacs/gnuserv
{
if [ "$(gnuclient −batch −eval t 2>&−)" == "t" ]; then
gnuclient −q "$@";
else
( xemacs "$@" &);
fi
}
#−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# File & strings related functions:
#−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
#−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Process/system related functions:
#−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# Misc utilities:
function ask()
{
echo −n "$@" '[y/n] ' ; read ans
case "$ans" in
y*|Y*) return 0 ;;
*) return 1 ;;
esac
}
#=========================================================================
#
# PROGRAMMABLE COMPLETION − ONLY SINCE BASH−2.04
# Most are taken from the bash 2.05 documentation and from Ian McDonalds
# 'Bash completion' package (http://www.caliban.org/bash/index.shtml#completion)
# You will in fact need bash−2.05a for some features
# Compression
complete −f −o default −X '*.+(zip|ZIP)' zip
complete −f −o default −X '!*.+(zip|ZIP)' unzip
complete −f −o default −X '*.+(z|Z)' compress
complete −f −o default −X '!*.+(z|Z)' uncompress
complete −f −o default −X '*.+(gz|GZ)' gzip
complete −f −o default −X '!*.+(gz|GZ)' gunzip
complete −f −o default −X '*.+(bz2|BZ2)' bzip2
complete −f −o default −X '!*.+(bz2|BZ2)' bunzip2
# Postscript,pdf,dvi.....
complete −f −o default −X '!*.ps' gs ghostview ps2pdf ps2ascii
complete −f −o default −X '!*.dvi' dvips dvipdf xdvi dviselect dvitype
complete −f −o default −X '!*.pdf' acroread pdf2ps
complete −f −o default −X '!*.+(pdf|ps)' gv
complete −f −o default −X '!*.texi*' makeinfo texi2dvi texi2html texi2pdf
complete −f −o default −X '!*.tex' tex latex slitex
complete −f −o default −X '!*.lyx' lyx
complete −f −o default −X '!*.+(htm*|HTM*)' lynx html2ps
# Multimedia
complete −f −o default −X '!*.+(jp*g|gif|xpm|png|bmp)' xv gimp
complete −f −o default −X '!*.+(mp3|MP3)' mpg123 mpg321
complete −f −o default −X '!*.+(ogg|OGG)' ogg123
_get_longopts ()
{
$1 −−help | sed −e '/−−/!d' −e 's/.*−−\([^[:space:].,]*\).*/−−\1/'| \
grep ^"$2" |sort −u ;
}
_longopts_func ()
{
case "${2:−*}" in
−*) ;;
*) return ;;
esac
case "$1" in
\~*) eval cmd="$1" ;;
*) cmd="$1" ;;
esac
COMPREPLY=( $(_get_longopts ${1} ${2} ) )
}
complete −o default −F _longopts_func configure bash
complete −o default −F _longopts_func wget id info a2ps ls recode
_make_targets ()
{
local mdef makef gcmd cur prev i
COMPREPLY=()
cur=${COMP_WORDS[COMP_CWORD]}
prev=${COMP_WORDS[COMP_CWORD−1]}
# cvs(1) completion
_cvs ()
{
local cur prev
COMPREPLY=()
cur=${COMP_WORDS[COMP_CWORD]}
prev=${COMP_WORDS[COMP_CWORD−1]}
_killall ()
{
local cur prev
COMPREPLY=()
cur=${COMP_WORDS[COMP_CWORD]}
return 0
}
_my_command()
{
local cur func cline cspec
COMPREPLY=()
cur=${COMP_WORDS[COMP_CWORD]}
if [ $COMP_CWORD = 1 ]; then
COMPREPLY=( $( compgen −c $cur ) )
elif complete −p ${COMP_WORDS[1]} &>/dev/null; then
cspec=$( complete −p ${COMP_WORDS[1]} )
complete −o default −F _my_command nohup exec eval trace truss strace sotruss gdb
complete −o default −F _my_command command type which man nice
# Local Variables:
# mode:shell−script
# sh−shell:bash
# End:
Quite a number of programmers learned scripting on a PC running DOS. Even the crippled DOS batch file
language allowed writing some fairly powerful scripts and applications, though they often required extensive
kludges and workarounds. Occasionally, the need still arises to convert an old DOS batch file to a UNIX shell
script. This is generally not difficult, as DOS batch file operators are only a limited subset of the equivalent
shell scripting ones.
Table L−1. Batch file keywords / variables / operators, and their shell equivalents
Virtually all UNIX and shell operators and commands have many more options and enhancements than
their DOS and batch file equivalents. Many DOS batch files rely on auxiliary utilities, such as ask.com,
a crippled counterpart to read.
DOS supports a very limited and incompatible subset of filename wildcard expansion, recognizing only
the * and ? characters.
Converting a DOS batch file into a shell script is generally straightforward, and the result ofttimes reads better
than the original.
REM VIEWDATA
@ECHO OFF
:VIEWDATA
TYPE C:\BOZO\BOOKLIST.TXT | MORE
REM SHOW ENTIRE FILE, 1 PAGE AT A TIME.
:EXIT0
The script conversion is somewhat of an improvement.
#!/bin/bash
# viewdata.sh
# Conversion of VIEWDATA.BAT to shell script.
DATAFILE=/home/bozo/datafiles/book−collection.data
ARGNO=1
exit 0 # :EXIT0
#!/bin/bash
MAX=10000
break # What happens when you comment out this line? Why?
done
exit 0
−−−
Explain what the following script does. It is really just a parameterized command−line pipe.
#!/bin/bash
DIRNAME=/usr/bin
FILETYPE="shell script"
LOGFILE=logfile
exit 0
−−−
−−−
Analyze Example A−10, and reorganize it in a simplified and more logical style. See how many of the
variables can be eliminated, and try to optimize the script to speed up its execution time.
Alter the script so that it accepts any ordinary ASCII text file as input for its initial "generation". The script
will read the first $ROW*$COL characters, and set the occurrences of vowels as "living" cells. Hint: be sure to
translate the spaces in the input file to underscore characters.
EASY
Having already done the "heavy lifting", now convert the loops in the example to until loops.
Changing the line spacing of a text file
Write a script that reads each line of a target file, then writes the line back to stdout, but with an
extra blank line following. This has the effect of double−spacing the file.
Include all necessary code to check whether the script gets the necessary command line argument (a
filename), and whether the specified file exists.
When the script runs correctly, modify it to triple−space the target file.
Finally, write a script to remove all blank lines from the target file, single−spacing it.
Backwards Listing
Write a script that echoes itself to stdout, but backwards.
Automatically Decompressing Files
Given a list of filenames as input, this script queries each target file (parsing the output of the file
command) for the type of compression used on it. Then the script automatically invokes the
appropriate decompression command (gunzip, bunzip2, unzip, uncompress, or whatever). If a target
file is not compressed, the script emits a warning message, but takes no other action on that particular
file.
INTERMEDIATE
Integer or String
Write a script function that determines if an argument passed to it is an integer or a string. The
function will return TRUE (0) if passed an integer, and FALSE (1) if passed a string.
Hint: What does the following expression return when $1 is not an integer?
expr $1 + 0
Managing Disk Space
List, one at a time, all files larger than 100K in the /home/username directory tree. Give the user
the option to delete or compress the file, then proceed to show the next one. Write to a logfile the
names of all deleted files and the deletion times.
Removing Inactive Accounts
Inactive accounts on a network waste disk space and may become a security risk. Write an
administrative script (to be invoked by root or the cron daemon) that checks for and deletes user
accounts that have not been accessed within the last 90 days.
Enforcing Disk Quotas
Write a script for a multi−user system that checks users' disk usage. If a user surpasses the preset limit
(100 MB, for example) in her /home/username directory, then the script will automatically send
her a warning e−mail.
The script will use the du and mail commands. As an option, it will allow setting and enforcing quotas
using the quota and setquota commands.
Logged in User Information
For all logged in users, show their real names and the time and date of their last login.
Making Change
What is the most efficient way to make change for $1.68, using only coins in common circulations
(up to 25c)? It's 6 quarters, 1 dime, a nickel, and three cents.
Given any arbitrary command line input in dollars and cents ($*.??), calculate the change, using the
minimum number of coins. If your home country is not the United States, you may use your local
currency units instead. The script will need to parse the command line input, then change it to
multiples of the smallest monetary unit (cents or whatever). Hint: look at Example 23−8.
Quadratic Equations
Solve a "quadratic" equation of the form Ax^2 + Bx + C = 0. Have a script take as arguments the
coefficients, A, B, and C, and return the solutions to four decimal places.
Hint: pipe the coefficients to bc, using the well−known formula, x = ( −B +/− sqrt( B^2 − 4AC ) ) /
2A.
Sum of Matching Numbers
Find the sum of all five−digit numbers (in the range 10000 − 99999) containing exactly two out of the
following set of digits: { 4, 5, 6 }. These may repeat within the same number, and if so, they count
once for each occurrence.
DIFFICULT
Testing Passwords
Write a script to check and validate passwords. The object is to flag "weak" or easily guessed
password candidates.
A trial password will be input to the script as a command line parameter. To be considered acceptable,
a password must meet the following minimum qualifications:
Optional: Write a script that searches through a batch of e−mail messages and deletes the spam
according to specified filters.
Creating man pages
Write a script that automates the process of creating man pages.
Given a text file which contains information to be formatted into a man page, the script will read the
file, then invoke the appropriate groff commands to output the corresponding man page to stdout.
The text file contains blocks of information under the standard man page headings, i.e., "NAME,"
"SYNOPSIS," "DESCRIPTION," etc.
Optional: Write a script that solves word−find puzzles. To keep this from becoming too difficult, the
solution script will find only horizontal and vertical words. (Hint: Treat each row and column as a
string, and search for substrings.)
Anagramming
Anagram 4−letter input. For example, the anagrams of word are: do or rod row word. You may use
/usr/share/dict/linux.words as the reference list.
"Word Ladders"
A "word ladder" is a sequence of words, with each successive word in the sequence differing from the
previous one by a single letter.
mark −−> park −−> part −−> past −−> vast −−> vase
Write a script that solves "word ladder" puzzles. Given a starting and an ending word, the script will
list all intermediate steps in the "ladder". Note that all words in the sequence must be "legal."
Fog Index
The "fog index" of a passage of text estimates its reading difficulty, as a number corresponding
roughly to a school grade level. For example, a passage with a fog index of 12 should be
comprehensible to anyone with 12 years of schooling.
The Gunning version of the fog index uses the following algorithm.
A strict interpretation of the Gunning fog index does not count compound words and proper nouns as
"difficult" words, but this would enormously complicate the script.
Calculating PI using Buffon's Needle
The Eighteenth Century French mathematician de Buffon came up with a novel experiment.
Repeatedly drop a needle of length "n" onto a wooden floor composed of long and narrow parallel
boards. The cracks separating the equal−width floorboards are a fixed distance "d" apart. Keep track
of the total drops and the number of times the needle intersects a crack on the floor. The ratio of these
two quantities turns out to be a fractional multiple of PI.
In the spirit of Example 12−45, write a script that runs a Monte Carlo simulation of Buffon's Needle.
To simplify matters, set the needle length equal to the distance between the cracks, n = d.
Hint: there are actually two critical variables: the distance from the center of the needle to the crack
nearest to it, and the angle of the needle to that crack. You may use bc to handle the calculations.
Playfair Cipher
Implement the Playfair (Wheatstone) Cipher in a script.
The Playfair Cipher encrypts text by substitution of "digrams" (2−letter groupings). It is traditional to
use a 5 x 5 letter scrambled−alphabet key square for the encryption and decryption.
C O D E S
A B F G H
I K L M N
P Q R T U
V W X Y Z
Each letter of the alphabet appears once, except "I" also represents
"J". The arbitrarily chosen key word, "CODES" comes first, then all
the rest of the alphabet, in order from left to right, skipping letters
already used.
TH IS IS AT OP SE CR ET ME SA GE
or
or
3) Both letters will form the corners of a rectangle within the key
square. For each letter, substitute the one on the other corner the
rectangle which lies on the same row.
T −−> U
H −−> G
=========================================================================
−−
Please do not send the author your solutions to these exercises. There are better ways to impress him with
your cleverness, such as submitting bugfixes and suggestions for improving this book.
The main mirror site for this document is the Linux Documentation Project, which maintains many other
Guides and HOWTOs as well.
#!/bin/bash
# May have to change the location for your site.
# (At the ISP's servers, Bash may not be in the usual place.)
# Other places: /usr/bin or /usr/local/bin
# Might even try it without any path in sha−bang.
# test−cgi.sh
# by Michael Zick
# Used with permission
exit 0
_test_CGI_
Any volunteers?
This blanket copyright recognizes and protects the rights of all contributors to this document.
This document may only be distributed subject to the terms and conditions set forth in the Open Publication
License (version 1.0 or later), http://www.opencontent.org/openpub/. The following license options also
apply.
Without explicit written permission from the author, distributors and publishers (including on−line publishers)
are prohibited from imposing any additional conditions, strictures, or provisions on this document or any
previous version of it. As of this update, the author asserts that he has not entered into any contractual
obligations that would alter the foregoing declarations.
Essentially, you may freely distribute this book in unaltered electronic form. You must obtain the author's
permission to distribute a substantially modified version or derivative work. The purpose of this restriction is
to preserve the artistic integrity of this document and to prevent "forking."
If you display or distribute this document or any previous version thereof under any license except the one
above, then you are required to obtain the author's written permission. Failure to do so may terminate your
distribution rights.
These are very liberal terms, and they should not hinder any legitimate distribution or use of this book. The
author especially encourages the use of this book for classroom and instructional purposes.
Certain of the scripts contained in this document are, where noted, released into the Public Domain.
These scripts are exempt from the foregoing license and copyright restrictions.
The commercial print and other rights to this book are available. Please contact the author if interested.
The author produced this book in a manner consistent with the spirit of the LDP Manifesto.
All other commercial trademarks mentioned in the body of this work are registered to their respective
owners.
Hyun Jin Cha has done a Korean translation of version 1.0.11 of this book. Spanish, Portuguese, French,
(another French), German, Italian, Russian, Czech, Chinese, and Dutch translations are also available or in
progress. If you wish to translate this document into another language, please feel free to do so, subject to the
terms stated above. The author wishes to be notified of such efforts.
Notes
If, in fact, the script includes an extra #! line, then bash will interpret it as a comment.
#!/bin/bash
#!/bin/bash
# This does *not* launch a new script.
#!/bin/rm
# Self−deleting script.
# Nothing much seems to happen when you run this... except that the file disappears.
WHATEVER=65
exit $WHATEVER # Doesn't matter. The script will not exit here.
Also, try starting a README file with a #!/bin/more, and making it executable. The result is a
self−listing documentation file. (A here document using cat is possibly a better alternative −− see
Example 17−3).
[7] Portable Operating System Interface, an attempt to standardize UNIX−like OSes. The POSIX
specifications are listed on the Open Group site.
[8] If Bash is your default shell, then the #! isn't necessary at the beginning of a script. However, if
launching a script from a different shell, such as tcsh, then you will need the #!.
[9] Caution: invoking a Bash script by sh scriptname turns off Bash−specific extensions, and the
script may therefore fail to execute.
[10] A script needs read, as well as execute permission for it to run, since the shell needs to be able to read
it.
[11] Why not simply invoke the script with scriptname? If the directory you are in ($PWD) is where
scriptname is located, why doesn't this work? This fails because, for security reasons, the current
directory is not by default included in a user's $PATH. It is therefore necessary to explicitly invoke the
script in the current directory with a ./scriptname.
[12] A PID, or process ID, is a number assigned to a running process. The PIDs of running processes may
be viewed with a ps command.
[13] The shell does the brace expansion. The command itself acts upon the result of the expansion.
[14] Exception: a code block in braces as part of a pipe may run as a subshell.
# Thanks, S.C.
[15] A linefeed ("newline") is also a whitespace character. This explains why a blank line, consisting only of
a linefeed, is considered whitespace.
[16] The process calling the script sets the $0 parameter. By convention, this parameter is the name of the
script. See the manpage for execv.
[17] Unless there is a file named first in the current working directory. Yet another reason to quote.
(Thank you, Harald Koenig, for pointing this out.
[18] It also has side−effects on the value of the variable (see below)
[19] Encapsulating "!" within double quotes gives an error when used from the command line. This is
interpreted as a history command. Within a script, though, this problem does not occur, since the Bash
history mechanism is disabled then.
A property of a pseurandom number series is the length of the cycle before it starts repeating itself. A
good pseurandom generator will produce series with very long cycles.
[30] These are shell builtins, whereas other loop commands, such as while and case, are keywords.
[31] An exception to this is the time command, listed in the official Bash documentation as a keyword.
[32] A option is an argument that acts as a flag, switching script behaviors on or off. The argument
associated with a particular option indicates the behavior that the option (flag) switches on or off.
[33] Unless the exec is used to reassign file descriptors.
[34]
Hashing is a method of creating lookup keys for data stored in a table. The data items themselves are
"scrambled" to create keys, using one of a number of simple mathematical algorithms.
An advantage of hashing is that it is fast. A disadvantage is that "collisions" −− where a single key
maps to more than one data item −− are possible.
The word "daemon" means ghost in Greek, and there is certainly something mysterious, almost
supernatural, about the way UNIX daemons silently wander about behind the scenes, carrying out their
appointed tasks.
[46] This is actually a script adapted from the Debian Linux distribution.
[47] The print queue is the group of jobs "waiting in line" to be printed.
[48] For an excellent overview of this topic, see Andy Vaught's article, Introduction to Named Pipes, in the
September, 1997 issue of Linux Journal.
[49] EBCDIC (pronounced "ebb−sid−ick") is an acronym for Extended Binary Coded Decimal Interchange
Code. This is an IBM data format no longer in much use. A bizarre application of the conv=ebcdic
option of dd is as a quick 'n easy, but not very secure text file encoder.
[51] This is the case on a Linux machine or a UNIX system with disk quotas.
[52] The userdel command will fail if the particular user being deleted is still logged on.
[53] For more detail on burning CDRs, see Alex Withers' article, Creating CDs, in the October, 1999 issue
of Linux Journal.
[54] The −c option to mke2fs also invokes a check for bad blocks.
[55] Since only root has write permission in the /var/lock directory, a user script cannot set a lock file
there.
[56] Operators of single−user Linux systems generally prefer something simpler for backups, such as tar.
[57] NAND is the logical not−and operator. Its effect is somewhat similar to subtraction.
[58] For purposes of command substitution, a command may be an external system command, an internal
scripting builtin, or even a script function.
[59] In a more technically correct sense, command substitution extracts the stdout of a command, then
assigns it to a variable using the = operator.
[60] In fact, nesting with backticks is also possible, but only by escaping the inner backticks, as John Default
points out.
#!/bin/bash
echo
# Thanks, S.C.
exit 0
[64]
Filename expansion means expanding filename patterns or templates containing special characters. For
example, example.??? might expand to example.001 and/or example.txt.
[65] Filename expansion can match dotfiles, but only if the pattern explicitly includes the dot as a literal
character.
# Thanks, S.C.
[66] This has the same effect as a named pipe (temp file), and, in fact, named pipes were at one time used in
process substitution.
[67] The return command is a Bash builtin.
[68] Herbert Mayer defines recursion as ". . . expressing an algorithm by using a simpler version of that
same algorithm . . ." A recursive function is one that calls itself.
[69] Too many levels of recursion may crash a script with a segfault.
#!/bin/bash
recursive_function ()
{
echo "$1" # Makes the function do something, and hastens the segfault.
(( $1 < $2 )) && recursive_function $(( $1 + 1 )) $2;
# As long as 1st parameter is less than 2nd,
#+ increment 1st and recurse.
}
Some devices, such as /dev/null, /dev/zero, and /dev/urandom are virtual. They are not
actual physical devices and exist only in software.
[72]
A block device reads and/or writes data in chunks, or blocks, in contrast to a character device, which
acesses data in character units. Examples of block devices are a hard drive and CD ROM drive. An
example of a character device is a keyboard.
[73] Of course, the mount point /mnt/flashdrive must exist. If not, then, as root, mkdir
/mnt/flashdrive.
To actually mount the drive, use the following command: mount /mnt/flashdrive
This likewise accounts for the split between /sbin and /usr/sbin, /lib and /usr/lib, etc.