Advanced Bash Scripting Guide
Advanced Bash Scripting Guide
6.0.05
24 March 2009
Revision History
Revision 5.5 23 Nov 2008 Revised by: mc
'FARKLEBERRY' release: Minor Update.
Revision 5.6 26 Jan 2009 Revised by: mc
'WORCESTERBERRY' release: Minor Update.
Revision 6.0 23 Mar 2009 Revised by: mc
'THIMBLEBERRY' 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 nuggets 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.
Dedication
For Anita, the source of all the magic
Advanced Bash-Scripting Guide
Table of Contents
Chapter 1. Shell Programming!.........................................................................................................................1
Part 2. Basics.......................................................................................................................................................7
Chapter 5. Quoting...........................................................................................................................................40
5.1. Quoting Variables...........................................................................................................................40
5.2. Escaping..........................................................................................................................................42
Chapter 7. Tests................................................................................................................................................50
7.1. Test Constructs...............................................................................................................................50
7.2. File test operators............................................................................................................................57
7.3. Other Comparison Operators..........................................................................................................60
7.4. Nested if/then Condition Tests.......................................................................................................65
7.5. Testing Your Knowledge of Tests..................................................................................................66
i
Advanced Bash-Scripting Guide
Table of Contents
Chapter 10. Loops and Branches..................................................................................................................131
10.1. Loops..........................................................................................................................................131
10.2. Nested Loops..............................................................................................................................144
10.3. Loop Control...............................................................................................................................145
10.4. Testing and Branching................................................................................................................148
Part 4. Commands..........................................................................................................................................165
ii
Advanced Bash-Scripting Guide
Table of Contents
Chapter 21. Restricted Shells.........................................................................................................................376
iii
Advanced Bash-Scripting Guide
Table of Contents
Chapter 34. Bash, versions 2, 3, and 4..........................................................................................................515
34.1. Bash, version 2............................................................................................................................515
34.2. Bash, version 3............................................................................................................................519
34.2.1. Bash, version 3.1...............................................................................................................522
34.2.2. Bash, version 3.2...............................................................................................................522
34.3. Bash, version 4............................................................................................................................523
Bibliography....................................................................................................................................................534
Appendix I. Localization................................................................................................................................756
iv
Advanced Bash-Scripting Guide
Table of Contents
Appendix M. Exercises...................................................................................................................................779
M.1. Analyzing Scripts........................................................................................................................779
M.2. Writing Scripts............................................................................................................................781
Appendix P. To Do List..................................................................................................................................795
Appendix Q. Copyright..................................................................................................................................797
v
Chapter 1. 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.
The craft of scripting is not hard to master, 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"
governing their use. 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 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, Perl, or Python.
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.
According to Herbert Mayer, "a useful language needs arrays, pointers, and a generic mechanism for building
data structures." By these criteria, shell scripting falls somewhat short of being "useful." Or, perhaps not. . . .
• Resource-intensive tasks, especially where speed is a factor (sorting, hashing, recursion [2] ...)
• Procedures involving heavy-duty math operations, especially floating point arithmetic, arbitrary
precision calculations, or complex numbers (use C++ or FORTRAN instead)
• Cross-platform portability required (use C or Java instead)
• Complex applications, where structured programming is a necessity (type-checking of variables,
function prototypes, etc.)
• Mission-critical applications upon which you are betting the future of the company
• Situations where security is important, where you need to guarantee the integrity of your system and
protect against intrusion, cracking, and vandalism
• Project consists of subcomponents with interlocking dependencies
• Extensive file operations required (Bash is limited to serial file access, and that only in a
particularly clumsy and inefficient line-by-line fashion.)
If any of the above applies, consider a more powerful scripting language -- perhaps Perl, Tcl, Python, Ruby
-- or possibly a 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 most 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, [3] 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), [4] 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 or pdf 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.
--Edmund Spenser
--Larry Wall
In the simplest case, a script is nothing more than a list of system commands stored in a file. At the very least,
this saves the effort of retyping that particular sequence of commands each time it is invoked.
# 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 have been invoked one by one
from the command-line on the console or in a terminal window. The advantages of placing the commands in a
script go far beyond not having to retype them time and again. The script becomes a program -- a tool -- and it
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:
# -------
LOG_DIR=/var/log
ROOT_UID=0 # Only users with $UID 0 have root privileges.
LINES=50 # Default number of lines saved.
E_XCD=86 # Can't change directory?
E_NOTROOT=87 # Non-root exit error.
if [ -n "$1" ]
# Test whether command-line argument is present (non-empty).
then
lines=$1
else
lines=$LINES # Default, if not specified on command-line.
fi
cd $LOG_DIR
tail -n $lines messages > mesg.temp # Save 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 fine-tuning previously written scripts for
increased effectiveness.
***
The sha-bang ( #!) [5] 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 [6] 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 (the line following the
sha-bang line), and ignoring comments. [7]
#!/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. [8] 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 [9] 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. [10]
#! 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. [11] 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 will 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=85
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. 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) [13]
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. [14] 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 systemwide executable. The script
could then be invoked by simply typing scriptname [ENTER] from the command-line.
Part 2. Basics 7
Chapter 3. Special Characters
What makes a character special? If it has a meaning beyond its literal meaning, a meta-meaning, then we refer
to it as a special character.
#
Comments. Lines beginning with a # (with the exception of #!) are comments and will not be
executed.
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" ;;
xyz) echo "\$variable = xyz" ;;
esac
;;&, ;&
Terminators in a case option (version 4+ of Bash).
.
"dot" command [period]. Equivalent to source (see Example 14-23). This is a bash builtin.
.
"dot", as a component of a filename. When working with filenames, a leading 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, in this context
meaning current directory.
# /bin/ipcalc
# /usr/bin/kcalc
# /usr/bin/oidcalc
# /usr/bin/oocalc
\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 # Or 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 18-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 :.
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.
** A double asterisk can represent the exponentiation operator or extended file-match globbing.
?
test operator. Within certain expressions, the ? indicates a test for a condition.
In a double-parentheses construct, the ? can serve as an element of a C-style trinary operator, ?:.
(( var0 = var1<98?9:21 ))
# ^ ^
# if [ "$var1" -lt 98 ]
# then
# var0=9
# else
# var0=21
# fi
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
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. [17] Filename
expansion (globbing) applies to the file specs between the braces.
No spaces allowed within the braces unless the spaces are quoted or escaped.
echo {a..z} # 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 z
# Echoes characters between a and z.
echo {0..3} # 0 1 2 3
# Echoes characters between 0 and 3.
The {a..z} extended brace expansion construction is a feature introduced in version 3 of Bash.
{}
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
SUCCESS=0
E_NOARGS=65
if [ -z "$1" ]
then
echo "Usage: `basename $0` rpm-file"
exit $E_NOARGS
fi
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.
Test expression between [[ ]]. More flexible than the single-bracket [ ] test, this is a shell keyword.
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 15-9.
<<
redirection used in a here document.
<<<
redirection used in a here string.
<, >
ASCII comparison.
veg1=carrots
veg2=tomatoes
pipe. Passes the output (stdout of a previous command to the input (stdin) 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. [19]
For an interesting note on the complexity of using UNIX pipes, see the UNIX FAQ, Part 3.
The output of a command or commands may be piped to a script.
#!/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.
# ======================================================
# Occasionally also:
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
# (The first 'echo' doesn't execute. Why?)
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. Prefix for a default
parameter in parameter substitution.
COMMAND -[Option1][Option2][...]
ls -al
param2=${param1:-$DEFAULTVAL}
# ^
--
sort --ignore-leading-blanks
Used with a Bash builtin, it means the end of options to that particular command.
This provides a handy means of removing files whose names begin with a dash.
bash$ ls -l
-rw-r--r-- 1 bozo bozo 0 Nov 25 12:29 -badname
bash$ rm -- -badname
bash$ ls -l
total 0
The double-dash is also used in conjunction with set.
bash$ cat -
abc
abc
...
Ctl-D
As expected, cat - echoes stdin, in this case keyboarded user input, to stdout. But, does I/O
redirection using - have real-world applications?
# 1) cd /source/directory
# Source directory, where the files to be moved are.
# 2) &&
# "And-list": if the 'cd' operation successful,
# then execute the next command.
# 3) tar cf - .
# The 'c' option 'tar' archiving command creates a new archive,
# the 'f' (file) option, followed by '-' designates the target file
# as stdout, and do it in current directory tree ('.').
# 4) |
# Piped to ...
# 5) ( ... )
# a subshell
# 6) cd /dest/directory
# Change to the destination directory.
# 7) &&
# "And-list", as above
# 8) tar xpvf -
# Unarchive ('x'), preserve ownership and file permissions ('p'),
# and send verbose messages to stdout ('v'),
# reading data from stdin ('f' followed by '-').
#
# Note that 'x' is a command, and 'p', 'v', 'f' are options.
#
# Whew!
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 -
#!/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. In
parameter substitution, the + prefixes an alternate value that a variable expands to.
%
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.
^, ^^
Uppercase conversion in parameter substitution (added in version 4 of Bash).
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
hexadecimal notation, following an escape.
◊ Ctl-A
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-E
BEL. On some old-time teletype terminals, this would actually ring a bell. In an xterm it
might beep.
◊
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.
◊ Ctl-L
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.
read -n 1 -s -p \
###
exit 0
◊ Ctl-N
Erases a line of text recalled from history buffer [20] (on the command-line).
◊ Ctl-O
Resume (XON).
Suspend (XOFF).
◊ Ctl-T
Reverses the position of the character the cursor is on with the previous character (on the
command-line).
◊ Ctl-U
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-X
In certain word processing programs, Cuts highlighted text and copies to clipboard.
◊ Ctl-Y
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. It defaults to whitespace.
UNIX filters can target and operate on whitespace using the POSIX character class [:space:].
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. [22]
bash$ variable1=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 form of ${variable}. In contexts where the
$variable syntax causes an error, the longer form may work (see Section 9.3, below).
#!/bin/bash
# ex9.sh
a=375
hello=$a
# "VARIABLE =value"
# ^
#% Script tries to run "VARIABLE" command with one argument, "=value".
# "VARIABLE= value"
# ^
#% Script tries to run "value" command with
#+ the environmental variable "VARIABLE" set to "".
#-------------------------------------------------------------------------
# Quoting . . .
echo "$hello" # 375
echo "${hello}" # 375
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.
# Why?
# =======================================
# 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!).
if [ -z "$unassigned" ]
then
echo "\$unassigned is NULL."
fi # $unassigned is NULL.
Using a variable before assigning a value to it may cause problems. It is nevertheless
possible to perform arithmetic operations on an uninitialized variable.
# 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,
#+ and should not be used in a script.
See also Example 14-24.
Do not confuse this with = and -eq, which test, rather than assign!
#!/bin/bash
# Naked variables
echo
# Assignment
a=879
echo "The value of \"a\" is $a."
echo
echo
echo
echo
exit 0
#!/bin/bash
exit 0
Variable assignment using the $(...) mechanism (a newer method than backquotes). This is actually a
form of command substitution.
# 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 arithmetic operations and
comparisons on variables. The determining factor is whether the value of a variable contains only digits.
#!/bin/bash
# int-or-string.sh
a=2334 # Integer.
let "a += 1"
echo "a = $a " # a = 2335
echo # Integer, still.
c=BB34
exit $?
Untyped variables are both a blessing and a curse. They permit more flexibility in scripting and make it easier
to grind out lines of code (and give you enough rope to hang yourself!). However, they likewise permit subtle
errors to creep in and encourage sloppy programming habits.
To lighten the burden of keeping track of variable types in a script, Bash does permit declaring variables.
In a more general context, each process has an "environment", that is, a group of
variables 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.
$0 is the name of the script itself, $1 is the first argument, $2 the second, $3 the third, and so forth.
[24] After $9, the arguments must be enclosed in brackets, for example, ${10}, ${11}, ${12}.
#!/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
# shft.sh: Using 'shift' to step through all the positional parameters.
exit
#!/bin/bash
# shift-past.sh
echo "$1"
exit 0
# ======================== #
$ sh shift-past.sh 1 2 3 4 5
4
The shift command works in a similar fashion on parameters passed to a function. See
Example 33-16.
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. For example, the asterisk * represents a wild card character in
globbing and Regular Expressions).
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. [27] An argument enclosed in double quotes presents itself as a
single word, even if it contains whitespace separators.
Chapter 5. Quoting 40
Advanced Bash-Scripting Guide
echo "---"
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.
echo
var="'(]\\{}\$\""
echo $var # '(]\{}$"
echo "$var" # '(]\{}$" Doesn't make a difference.
echo
IFS='\'
echo $var # '(] {}$" \ converted to space. Why?
echo "$var" # '(]\{}$"
Chapter 5. Quoting 41
Advanced Bash-Scripting Guide
echo
var2="\\\\\""
echo $var2 # "
echo "$var2" # \\"
echo
# But ... var2="\\\\"" is illegal. Why?
var3='\\\\'
echo "$var3" # \\\\
# Strong quoting works, though.
exit
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.
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.
Chapter 5. Quoting 42
Advanced Bash-Scripting Guide
means alert (beep or flash)
\0xx
translates to the octal ASCII equivalent of 0nn, where nn is a string of digits
#!/bin/bash
# escaped.sh: escaped characters
echo; echo
# Escaping a newline.
# ------------------
echo ""
echo; 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 43
Advanced Bash-Scripting Guide
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 . . .
# However . . .
Chapter 5. Quoting 44
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.
Chapter 5. Quoting 45
Advanced Bash-Scripting Guide
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.
echo "-------------------------------------------------------------------------"
The escape also provides a means of writing a multi-line command. Normally, each separate line constitutes a
different command, but an escape at the end of a line escapes the newline character, and the command
sequence continues on to the next line.
# 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
Chapter 5. Quoting 46
Advanced Bash-Scripting Guide
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.
#foo\
#bar
Chapter 5. Quoting 47
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 terminates 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 or exit code). A successful
command returns a 0, while an unsuccessful one returns a non-zero value that usually can 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 an integer 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
. . .
COMMAND_LAST
exit
The equivalent of a bare exit is exit $? or even just omitting the exit.
#!/bin/bash
COMMAND_1
. . .
COMMAND_LAST
exit $?
#!/bin/bash
COMMAND1
. . .
COMMAND_LAST
$? 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." [28] 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 between it and the command.
# !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.
(( 200 || 11 )) # Logical OR
echo $? # 0 ***
# ...
let "num = (( 200 || 11 ))"
echo $num # 1
let "num = (( 200 || 11 ))"
echo $? # 0 ***
(( 200 | 11 )) # Bitwise OR
echo $? # 0 ***
# ...
let "num = (( 200 | 11 ))"
echo $num # 203
let "num = (( 200 | 11 ))"
echo $? # 0 ***
Chapter 7. Tests 50
Advanced Bash-Scripting Guide
• An if can test any command, not just conditions enclosed within brackets.
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."
else echo "Command failed."
fi
• These last two examples 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
Chapter 7. Tests 51
Advanced Bash-Scripting Guide
echo
echo
echo
echo
echo
Chapter 7. Tests 52
Advanced Bash-Scripting Guide
echo
echo
echo
exit 0
Exercise. Explain the behavior of Example 7-1, above.
if [ condition-true ]
then
command 1
command 2
...
else # Or else ...
# 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.
Chapter 7. Tests 53
Advanced Bash-Scripting Guide
if [ condition1 ]
then
command1
command2
command3
elif [ condition2 ]
# Same as else if
then
command4
command5
else
default-command
fi
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.
If, for some reason, you wish to use /usr/bin/test in a Bash script, then specify it by full
pathname.
#!/bin/bash
echo
if test -z "$1"
then
echo "No command-line arguments."
else
echo "First command-line argument is $1."
fi
echo
Chapter 7. Tests 54
Advanced Bash-Scripting Guide
echo "First command-line argument is $1."
fi
echo
echo
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.
Arithmetic evaluation of octal / hexadecimal constants takes place automatically within a [[ ... ]] construct.
decimal=15
octal=017 # = 15 (decimal)
Chapter 7. Tests 55
Advanced Bash-Scripting Guide
hex=0x0f # = 15 (decimal)
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.
#!/bin/bash
# Arithmetic tests.
Chapter 7. Tests 56
Advanced Bash-Scripting Guide
(( 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
# ======================================= #
var1=5
var2=4
exit 0
-e
file exists
-a
file exists
This is identical in effect to -e. It has been "deprecated," [29] and its use is discouraged.
-f
file is a regular file (not a directory or device file)
-s
file is not zero size
Chapter 7. Tests 57
Advanced Bash-Scripting Guide
-d
file is a directory
-b
file is a block device
-c
file is a character device
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
Chapter 7. Tests 58
Advanced Bash-Scripting Guide
A binary owned by root with set-user-id flag set runs with root privileges, even when an
ordinary user invokes it. [30] 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. [31] 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,
she can only delete those files that she owns in it. 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.)
-O
you are owner of file
-G
group-id of file same as yours
-N
file modified since it was last read
f1 -nt f2
file f1 is newer than f2
f1 -ot f2
file f1 is older than f2
f1 -ef f2
files f1 and f2 are hard links to the same file
!
"not" -- reverses the sense of the tests above (returns true if condition absent).
#!/bin/bash
# broken-link.sh
# Written by Lee bigelow <[email protected]>
# Used in ABS Guide 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. sh broken-link.sh /somedir /someotherdir|xargs rm
#
# This, however, is a better method:
#
# find "somedir" -type l -print0|\
# xargs -r0 file|\
Chapter 7. Tests 59
Advanced Bash-Scripting Guide
# 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!
################################################################
linkchk () {
for element in $1/*; do
[ -h "$element" -a ! -e "$element" ] && echo \"$element\"
[ -d "$element" ] && linkchk $element
# Of course, '-h' tests for symbolic link, '-d' for directory.
done
}
# 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 $?
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 60
Advanced Bash-Scripting Guide
-ne
is not equal to
string comparison
is equal to
if [ "$a" = "$b" ]
==
is equal to
if [ "$a" == "$b" ]
Chapter 7. Tests 61
Advanced Bash-Scripting Guide
The == comparison operator behaves differently within a double-brackets test than within
single brackets.
if [ "$a" != "$b" ]
if [ -z "$String" ]
then
echo "\$String is null."
else
echo "\$String is NOT null."
fi # $String is null.
-n
string is not null.
The -n test 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. [32]
Chapter 7. Tests 62
Advanced Bash-Scripting Guide
#!/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
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
Chapter 7. Tests 63
Advanced Bash-Scripting Guide
echo
echo
string1=initialized
string1="a = b"
#!/bin/bash
# zmore
E_NOARGS=65
E_NOTFOUND=66
E_NOTGZIP=67
Chapter 7. Tests 64
Advanced Bash-Scripting Guide
# $1 can exist, but be empty: zmore "" arg2 arg3
then
echo "Usage: `basename $0` filename" >&2
# Error message to stderr.
exit $E_NOARGS
# Returns 65 as exit status of script (error code).
fi
filename=$1
if [ ${filename##*.} != "gz" ]
# Using bracket in variable substitution.
then
echo "File $1 is not a gzipped file!"
exit $E_NOTGZIP
fi
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 [ "$exp1" -a "$exp2" ]
Refer to Example 8-3, Example 26-17, and Example A-29 to see compound comparison operators in action.
Chapter 7. Tests 65
Advanced Bash-Scripting Guide
a=3
if [ "$a" -gt 0 ]
then
if [ "$a" -lt 5 ]
then
echo "The value of \"a\" lies somewhere between 0 and 5."
fi
fi
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
# (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 snippet, then examine an updated version of the 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 66
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" ]
then
command
fi
arithmetic operators
+
plus
-
minus
*
multiplication
/
division
**
exponentiation
let "z=5**3" # 5 * 5 * 5
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=85
if [ $# -ne "$ARGS" ]
then
echo "Usage: `basename $0` first-number second-number"
exit $E_BADARGS
fi
# ------------------------------------------------------
gcd ()
{
gcd $1 $2
# Exercises :
# ---------
# 1) Check command-line arguments to make sure they are integers,
#+ and exit the script with an appropriate error message if not.
# 2) Rewrite the gcd () function to use local variables.
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 older versions of Bash were signed long (32-bit) integers, in the range of
-2147483648 to 2147483647. An operation that took a variable outside these limits gave 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,
# + and the leftmost bit, the sign bit,
# + has been set, making the result negative.
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. The bitwise operators seldom make an appearance in shell scripts. Their chief use seems to
be manipulating and testing values read from ports or sockets. "Bit flipping" is more relevant to compiled
languages, such as C and C++, which provide direct access to system hardware.
bitwise operators
<<
bitwise left shift (multiplies by 2 for each shift position)
<<=
left-shift-equal
!
NOT
if [ ! -f $FILENAME ]
then
...
&&
AND
&& may also be used, depending on context, in an and list to concatenate commands.
||
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. [33]
#!/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.
$ sh numbers.sh
$ echo $?
$ 1
bash4$ echo $$
11015
But ...
#!/bin/bash4
echo
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 [34] (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 and
/etc/group.
$HOME
Home directory of the user, usually /home/username (see Example 9-16)
$HOSTNAME
The hostname command assigns the system host name at bootup in an init script. However, the
gethostname() function sets the Bash internal variable $HOSTNAME. See also Example 9-16.
$HOSTTYPE
host type
This variable determines how Bash recognizes fields, or word boundaries, when it interprets character
strings.
$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.sh
output_args_one_per_line()
{
for arg
do
echo "[$arg]"
done # ^ ^ Embed within brackets, for your viewing pleasure.
}
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 pattern as above,
# ^ ^^ ^^^ #+ but substituting ":" for " " ...
output_args_one_per_line $var
# []
# [a]
# []
# [b]
# [c]
# []
# []
echo
exit
(Many thanks, Stéphane Chazelas, for clarification and above examples.)
See also Example 15-41, Example 10-7, and Example 18-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
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
bash: bogus_command: command not found
0 0 0
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
0 127 0
The pipefail option may be useful in cases where $PIPESTATUS does not give the
desired information.
$PPID
The $PPID of a process is the process ID (pid) of its parent process. [35]
#!/bin/bash
E_WRONG_DIRECTORY=83
TargetDirectory=/home/bozo/projects/GreatAmericanNovel
cd $TargetDirectory
echo "Deleting stale files in $TargetDirectory."
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.
echo
echo "Done."
echo "Old files deleted in $TargetDirectory."
echo
exit $?
$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.
#!/bin/bash
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. [36] If, at the command-line, $SHLVL is 1, then in a script it
will increment to 2.
This variable is not affected by subshells. Use $BASH_SUBSHELL when you need
an indication of subshell nesting.
$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
There are other, more complex, ways of implementing timed input in a script. One alternative is to set
up a timing loop to signal the script when it times out. This also requires a signal handling routine to
trap (see Example 29-5) the interrupt generated by the timing loop (whew!).
#!/bin/bash
# timed-input.sh
TIMER_INTERRUPT=14
TIMELIMIT=3 # Three seconds in this instance.
# May be set to different value.
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 $TIMER_INTERRUPT
}
exit 0
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
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
/etc/passwd or in an "init" script, and it is likewise not a Bash builtin.
/bin/tcsh
tcsh% echo $TERM
rxvt
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
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
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="$*"
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
$-
Flags passed to script (using set). See Example 14-17.
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"
# Thank you, Sylvain Fourmanoit, for this creative use of the "!" variable.
Or, alternately:
possibly_hanging_job & {
while ((count < TIMEOUT )); do
eval '[ ! -d "/proc/$!" ] && ((count = TIMEOUT))'
# /proc is where information about running processes is found.
# "-d" tests whether it exists (whether directory exists).
# So, we're waiting for the job in question to show up.
((count++))
sleep 1
done
eval '[ -d "/proc/$!" ] && kill -15 $!'
# If the hanging job is running, kill it.
}
$_
Special variable set to final 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 (PID) of the script itself. [38] The $$ variable often finds use in scripts to construct
"unique" temp file names (see Example 29-6, Example 15-31, and Example 14-28). 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
These are the equivalent of strlen() in C.
expr "$string" : '.*'
stringZ=abcABC123ABCabc
echo ${#stringZ} # 15
echo `expr length $stringZ` # 15
echo `expr "$stringZ" : '.*'` # 15
#!/bin/bash
# paragraph-space.sh
# Ver. 2.0, Reldate 05Aug08
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" && "$line" =~ \[*\.\] ]]
then echo # Add a blank line immediately
fi #+ after short line terminated by a period.
done
exit
# Exercises:
# ---------
# 1) The script usually inserts a blank line at the end
#+ of the target file. Fix this.
# 2) Line 17 only considers periods as sentence terminators.
stringZ=abcABC123ABCabc
# |------|
# 12345678
Index
stringZ=abcABC123ABCabc
# 123456 ...
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, [39] starting at
$position.
${string:position:length}
Extracts $length characters of substring from $string at $position.
stringZ=abcABC123ABCabc
# 0123456789.....
# 0-based indexing.
#!/bin/bash
# rand-string.sh
# Generating an 8-character "random" string.
randstring="${str1:$POS:$LEN}"
# Can parameterize ^^^^ ^^^^
echo "$randstring"
exit $?
If the $string parameter is "*" or "@", then this extracts a maximum of $length positional
parameters, starting at $position.
stringZ=abcABC123ABCabc
# =======
stringZ=abcABC123ABCabc
# ======
Substring Removal
${string#substring}
Deletes shortest match of $substring from front of $string.
${string##substring}
Deletes longest match of $substring from front of $string.
stringZ=abcABC123ABCabc
# |----| shortest
# |----------| longest
For example:
SUFF=TXT
suff=txt
stringZ=abcABC123ABCabc
# || shortest
# |------------| longest
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:
#!/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 *.ra audio 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 on the fly.
#!/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
}
---
Substring Replacement
${string/substring/replacement}
Replace first match of $substring with $replacement. [40]
${string//substring/replacement}
Replace all matches of $substring with $replacement.
stringZ=abcABC123ABCabc
echo ---------------
echo "$stringZ" # abcABC123ABCabc
echo ---------------
# The string itself is not altered!
echo
stringZ=abcABC123ABCabc
A Bash script may invoke the string manipulation facilities of awk as an alternative to using its built-in
operations.
#!/bin/bash
# substring-extraction.sh
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.
echo "----"
# And likewise:
exit 0
Script examples:
1. Example 15-9
2. Example 9-18
3. Example 9-19
4. Example 9-20
5. Example 9-22
6. Example A-36
7. Example A-41
${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.
echo ${username-`whoami`}
#!/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, [41] 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 15-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:
◊
${#*} and ${#@} give the number of positional parameters.
◊ For an array, ${#array[*]} and ${#array[@]} give the number of elements in
the array.
#!/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}
${var#Pattern} Remove from $var the shortest part of $Pattern that matches the front end
of $var.
${var##Pattern} Remove from $var the longest part of $Pattern that matches the front end
of $var.
{$var%Pattern} Remove from $var the shortest part of $Pattern that matches the back end
of $var.
{$var%%Pattern} Remove from $var the longest part of $Pattern that matches the back 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.
;;
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
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.)
echo
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.
exit 0
${!varprefix*}, ${!varprefix@}
Matches names of all previously declared variables beginning with varprefix.
xyz23=whatever
xyz24=
echo "---"
abc23=something_else
b=${!abc*}
echo "b = $b" # b = abc23
c=${!b} # Now, the more familiar type of indirect reference.
echo $c # something_else
The declare or typeset builtins, which are exact synonyms, permit modifying the properties of variables. This
is a very weak form of the typing [42] available in certain programming languages. The declare command is
specific to version 2 or later of Bash. The typeset command also works in ksh scripts.
declare/typeset options
-r readonly
(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.
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 function(s)
declare -f
A declare -f line with no arguments in a script causes a listing of all the functions previously
defined in that script.
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
}
bash$ zzy=68
bash$ declare | grep zzy
zzy=68
We have seen that referencing a variable, $var, fetches its value. But, what about the value of a value? What
about $$var?
The actual notation is \$$var, usually preceded by an eval (and sometimes an echo). This is called an
indirect reference.
#!/bin/bash
# ind-ref.sh: Indirect variable referencing.
# Accessing the contents of the contents of a variable.
var=23
echo
# Direct reference.
echo "a = $a" # a = letter_of_alphabet
# Indirect reference.
eval a=\$$a
# ^^^ Forcing an eval(uation), and ...
# ^ Escaping the first $ ...
# ------------------------------------------------------------------------
# The 'eval' forces an update of $a, sets it to the updated value of \$$a.
# So, we see why 'eval' so often shows up in indirect reference notation.
# ------------------------------------------------------------------------
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";
if [ "$(eval "echo \${$(echo get$(echo -ne $arch |
sed 's/^\(.\).*/\1/g' | tr 'a-z' 'A-Z'; echo $arch |
sed 's/^.\(.*\)/\1/g')):-false}")" = true ]
then
return 0;
else
return 1;
fi;
}
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
" "$filename"
# ------------------------------------------------
# End awk script.
exit $?
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 and Example A-22) 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, at best, something of an afterthought.
$RANDOM is an internal Bash function (not a constant) that returns a pseudorandom [43] integer in the range 0
- 32767. It should not be used to generate an encryption key.
#!/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 "Random number less than $RANGE --- $number"
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
echo
exit 0
#!/bin/bash
# pick-card.sh
Suites="Clubs
Diamonds
Hearts
Denominations="2
3
4
5
6
7
8
9
10
Jack
Queen
King
Ace"
# $bozo sh pick-cards.sh
# Jack of Clubs
#!/bin/bash
# brownian.sh
# Author: Mendel Cooper
# Reldate: 10/26/07
# License: GPL3
# ----------------------------------------------------------------
# This script models Brownian motion:
#+ the random wanderings of tiny particles in a fluid,
#+ as they are buffeted by random currents and collisions.
#+ This is colloquially known as the "Drunkard's Walk."
Show_Slots () {
echo -n " "
for i in $( seq $NUMSLOTS ) # Pretty-print array elements.
do
printf "%3d" ${Slots[$i]} # Allot three spaces per result.
done
# --------------
# main ()
Initialize_Slots
Run
Show_Slots
# --------------
exit $?
# Exercises:
# ---------
# 1) Show the results in a vertical bar graph, or as an alternative,
#+ a scattergram.
# 2) Alter the script to use /dev/urandom instead of $RANDOM.
# Will this make the results more random?
Jipe points out a set of techniques for generating random numbers within a range.
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.
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 -n "Expects up to 3 passed parameters, "
echo "but all are completely optional."
echo "min is the minimum value"
echo "max is the maximum value"
echo -n "multiple specifies that the answer must be "
echo "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 -n "Successful completion returns 0, "
echo "unsuccessful completion returns"
echo "function syntax and 1."
echo -n "The answer is returned in the global variable "
echo "randomBetweenAnswer"
echo -n "Negative values for any passed parameter are "
echo "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.
spread=$((max-min))
# Omair Eshkenazi points out that this test is unnecessary,
#+ since max and min have already been switched around.
[ ${spread} -lt 0 ] && spread=$((0-spread))
let spread+=divisibleBy
randomBetweenAnswer=$(((RANDOM%spread)/divisibleBy*divisibleBy+min))
return 0
declare -a answer
minimum=${min}
disp=$((0-minimum))
for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
answer[i+disp]=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
}
update_count()
{
case "$1" in
0) let "ones += 1";; # Since die has no "zero", this corresponds to 1.
1) let "twos += 1";; # And this to 2, etc.
2) let "threes += 1";;
3) let "fours += 1";;
4) let "fives += 1";;
5) let "sixes += 1";;
esac
}
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. [44] (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"
done
}
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 pseudo-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, Example 15-14, and Example A-36), 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.
Similar to the let command, the (( ... )) construct permits arithmetic expansion and evaluation. In its simplest
form, a=$(( 5 + 3 )) would set a to 5 + 3, or 8. However, this double-parentheses construct is also a
mechanism for allowing C-style manipulation of variables in Bash, for example, (( var++ )).
#!/bin/bash
# c-vars.sh
# Manipulating a variable, C-style, using the (( ... )) construct.
echo
echo
########################################################
# Note that, as in C, pre- and post-decrement operators
#+ have different side-effects.
echo
echo
# -----------------
# Easter Egg alert!
# See also "for" and "while" loops using the (( ... )) construct.
exit
See also Example 10-12 and Example 8-4.
--Shakespeare, Othello
Operations on code blocks are the key to structured and organized shell scripts. Looping and branching
constructs provide the tools for accomplishing this.
10.1. Loops
A loop is a block of code that iterates [45] 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.
echo; echo
for planet in "Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto"
# All planets on same line.
# Entire 'list' enclosed in quotes creates a single variable.
# Why? Whitespace incorporated into the variable.
do
echo $planet
done
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 14-17) 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.
exit 0
#!/bin/bash
# fileinfo.sh
FILES="/usr/sbin/accept
/usr/sbin/pwck
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 wild cards (* 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-15. See also Example 14-18.
#!/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
15-54, Example 10-10 and Example 15-48.
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
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
#!/bin/bash
# findstring.sh:
# Find a particular string in the binaries in a specified directory.
directory=/usr/bin/
fstring="Free Software Foundation" # See which files come from the FSF.
exit $?
# Exercise (easy):
# ---------------
# Convert this script to take command-line parameters
#+ for $directory and $fstring.
A final example of [list] / command substitution, but this time the "command" is a function.
generate_list ()
{
echo "one two three"
}
# one
# two
# three
#!/bin/bash
# symlinks.sh: Lists symbolic links in a directory.
directory=${1-`pwd`}
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
# --------------------------------------------------------
# Jean Helou proposes the following alternative:
OLDIFS=$IFS
IFS='' # Null IFS means no word breaks
for file in $( find $directory -type l )
do
echo $file
done | sort
#!/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
# Multiple 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
# +==========================================+
echo; echo
# +==========================================+
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
# +=========================================================================+
echo; echo
exit 0
See also Example 26-16, Example 26-17, and Example A-6.
---
#!/bin/bash
# Faxing (must have 'efax' package installed).
EXPECTED_ARGS=2
E_BADARGS=85
MODEM_PORT="/dev/ttyS2" # May be different on your machine.
# ^^^^^ PCMCIA modem card default port.
if [ $# -ne $EXPECTED_ARGS ]
# Check for proper number 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."
# File is not a regular file, or does not exist.
exit $E_BADARGS
fi
while [ condition ]
do
command(s)...
done
The bracket construct in a while loop is nothing more than our old friend, the test brackets used in an
if/then test. In fact, a while loop can legally use the more versatile double-brackets construct (while [[
condition ]]).
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 the test brackets are not mandatory in a while loop. See, for example, the getopts construct.
#!/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?).
echo "variable #1 = $var1" # Need quotes because of "#" . . .
# If input is 'end', echoes it here.
# Does not test for termination condition until top of loop.
echo
done
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-style syntax by using the double-parentheses construct
(see also Example 9-33).
#!/bin/bash
# wh-loopc.sh: Count to 10 in a "while" loop.
LIMIT=10
a=1
echo; echo
# +=================================================================+
echo
exit 0
t=0
condition ()
{
((t++))
if [ $t -lt 5 ]
then
return 0 # true
else
return 1 # false
fi
}
while condition
# ^^^^^^^^^
# Function call -- four loop iterations.
do
echo "Still going: t = $t"
done
# Still going: t = 1
Similar to the if-test construct, a while loop can omit the test brackets.
while condition
do
command(s) ...
done
By coupling the power of the read command with a while loop, we get the handy while read construct,
useful for reading and parsing files.
A while loop may have its stdin redirected to a file by a < at its end.
until [ condition-is-true ]
do
command(s)...
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
# ------------------------------------------- #
LIMIT=10
var=0
exit 0
How to choose between a for loop or a while loop or until loop? In C, you would typically use a for loop
when the number of loop iterations is known beforehand. With Bash, however, the situation is fuzzier. The
Bash for loop is more loosely structured and more flexible than its equivalent in other languages. Therefore,
feel free to use whatever type of loop gets the job done in the simplest way.
#!/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 [46] 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 of the loop, skipping all the remaining commands in that particular
loop cycle.
#!/bin/bash
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 the 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 outerloop in 1 2 3 4 5
do
echo -n "Group $outerloop: "
# --------------------------------------------------------
for innerloop in 1 2 3 4 5
do
echo -n "$innerloop "
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
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 ;;.
◊ If a condition tests true, then the associated commands execute and the case
block terminates.
◊ The entire case block ends 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.
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 Flash Dr."
echo "Hardscrabble, CO 80753"
echo "(303) 734-9874"
echo "(303) 734-9892 fax"
echo "[email protected]"
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 "[email protected]"
echo "Ex-girlfriend"
echo "Birthday: Feb. 11"
;;
* )
# Default option.
# Empty input (hitting RETURN) fits here, too.
echo
echo "Not yet in database."
esac
echo
# Exercise:
# --------
# Change the script so it accepts multiple inputs,
#+ instead of terminating after displaying just one address.
exit 0
#! /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
#!/bin/bash
# match-string.sh: Simple string matching.
match_string ()
{ # Exact string match.
MATCH=0
E_NOMATCH=90
PARAMS=2 # Function requires 2 arguments.
E_BAD_PARAMS=91
case "$1" in
"$2") return $MATCH;;
* ) return $E_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
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 this may be changed.
echo
exit
# Exercise:
# --------
# Fix this script to accept user input not specified in
#+ the "select" statement.
# For example, if the user inputs "peas,"
#+ The script would respond "Sorry. That is not on the menu."
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
}
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
exit $?
Examples of command substitution in shell scripts:
1. Example 10-7
2. Example 10-26
3. Example 9-31
4. Example 15-3
5. Example 15-22
6. Example 15-17
7. Example 15-54
8. Example 10-13
9. Example 10-10
10. Example 15-32
11. Example 19-8
12. Example A-16
13. Example 27-3
14. Example 15-47
15. Example 15-48
16. Example 15-49
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 15-9
2. Example 10-14
3. Example 26-1
4. Example 26-11
5. Example A-16
Don't break the chain! Send out your ten copies today!
Courtesy 'NIX "fortune cookies", with some alterations and many apologies
Mastering the commands on your Linux machine is an indispensable prelude to writing effective shell scripts.
Table of Contents
14. Internal Commands and Builtins
14.1. Job Control Commands
15. External Filters, Programs and Commands
15.1. Basic Commands
15.2. Complex Commands
15.3. Time / Date Commands
15.4. Text Processing Commands
15.5. File and Archiving Commands
15.6. Communications Commands
15.7. Terminal Control Commands
15.8. Math Commands
15.9. Miscellaneous Commands
16. System and Administrative Commands
16.1. Analyzing a System Script
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.
Generally, a Bash builtin does not fork a subprocess when it executes within a script. An external system
command or filter in a script usually will fork a subprocess.
A builtin may be a synonym to a system command of the same name, but Bash reimplements it internally. For
example, the Bash echo command is not the same as /bin/echo, although their behavior is almost
identical.
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 15-22, Example 15-3, Example 15-47, and Example 15-48.
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
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=85
var=nonexistent_directory
error()
{
printf "$@" >&2
# Formats positional params passed, and sends them to stderr.
echo
exit $E_BADDIR
}
# Thanks, S.C.
See also Example 33-15.
read
"Reads" the value of a variable from stdin, that is, interactively fetches input from the keyboard.
The -a option lets read get array variables (see Example 26-6).
#!/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
echo "========================="
echo
# ================================================================= #
# In some instances, you might wish to discard the first value read.
# In such cases, simply ignore the $REPLY variable.
{ # Code block.
read # Line 1, to be discarded.
read line2 # Line 2, saved in variable.
} <$0
echo "Line 2 of this script is:"
echo "$line2" # # read-novar.sh
echo # #!/bin/bash line discarded.
exit 0
Normally, inputting a \ suppresses a newline during input to a read. The -r option causes an
inputted \ to be interpreted literally.
#!/bin/bash
echo
read var1 # The "\" suppresses the newline, when reading $var1.
# first line \
# second line
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
# ========================================= #
#!/bin/bash
uparrow=$'\x1b[A'
downarrow=$'\x1b[B'
leftarrow=$'\x1b[D'
rightarrow=$'\x1b[C'
case "$x" in
$uparrow)
echo "You pressed up-arrow"
;;
$downarrow)
echo "You pressed down-arrow"
;;
$leftarrow)
echo "You pressed left-arrow"
;;
$rightarrow)
echo "You pressed right-arrow"
;;
exit $?
# ========================================= #
#!/bin/bash
while true
do
read -sn1 a
test "$a" == `echo -en "\e"` || continue
read -sn1 a
test "$a" == "[" || continue
read -sn1 a
case "$a" in
A) echo "up";;
B) echo "down";;
C) echo "right";;
D) echo "left";;
esac
done
# ========================================= #
# Exercise:
# --------
# 1) 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 and Example A-41).
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.
It is possible to paste text into the input field of a read (but not multiple lines!). See
Example A-38.
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 14-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. [51] In many cases, it functions as a
less complex version of expr.
#!/bin/bash
echo
# Trinary operator.
let a++
let "t = a<7?7:11" # False
echo $t # 11
exit
eval
eval arg1 [arg2] ... [argN]
Combines the arguments in an expression or list of expressions and evaluates them. Any variables
within the expression are expanded. The net result is to convert a string into a command.
The eval command can be used for code generation from the command-line or within
a script.
bash$ command_string=xterm
bash$ eval $command_string
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sda5 1904920 590204 1217952 33% /
/dev/sda9 5409672 892396 4517276 17% /home
/dev/sda1 18431244 13244376 5186868 72% /media/hd
/dev/sda7 10142384 7812496 1814676 82% /usr
/dev/sda8 1138428 258964 821632 24% /var
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
a='$b'
b='$c'
c=d
echo $a # $b
# First level.
eval echo $a # $c
# Second level.
eval eval echo $a # d
# Third level.
#!/bin/bash
# Exercising "eval" ...
echo; echo
echo
echo "==========================================================="
echo
#!/bin/bash
# arr-choice.sh
arr0=( 10 11 12 13 14 15 )
arr1=( 20 21 22 23 24 25 )
arr2=( 30 31 32 33 34 35 )
# 0 1 2 3 4 5 Element number (zero-indexed)
choose_array ()
{
eval array_member=\${arr${array_number}[element_number]}
# ^ ^^^^^^^^^^^^
# Using eval to construct the name of a variable,
#+ in this particular case, an array name.
#!/bin/bash
# echo-params.sh
exit $?
# =================================================
#!/bin/bash
# Killing ppp to force a log-off.
# For dialup connection, of course.
SERPORT=ttyS3
# Depending on the hardware and even the kernel version,
#+ the modem port on your machine may be different --
#+ /dev/ttyS1 or /dev/ttyS2.
exit $?
# 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
# ====
eval var=\$$var
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/options. 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.
#!/bin/bash
# ex34.sh
# 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
echo +++++
echo $_ # +++++
# Flags set in script.
echo $- # hB
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?
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.
If 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.
# shift 2 also works.
remaining_params="$*"
echo
echo "first parameter = $first_param" # one
echo "second parameter = $second_param" # two
echo "remaining parameters = $remaining_params" # three four five
# 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 15-56.
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
export
The export [52] command makes available variables to all child processes of the running script or
shell. One important use of the export command is in startup files, to initialize and make accessible
environmental variables to subsequent user processes.
Unfortunately, there is no way to export variables back to the parent process, to the
process that called or invoked the script or shell.
#!/bin/bash
ARGS=2
E_WRONGARGS=85
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 [53] 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 is a highly functional replacement for the traditional
getopt external command.
#!/bin/bash
# Exercising getopts and OPTIND
# Script modified 10/09/03 at the suggestion of Bill Gradwohl.
NO_ARGS=0
E_OPTERROR=65
exit 0
Script Behavior
same as if the "sourced" lines of code were physically present in the body of the script. This is useful
in situations when multiple scripts use a common data file or function library.
#!/bin/bash
exit 0
File data-file for Example 14-23, 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
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. [54] 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 $?.
#!/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
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.
bash$ true
bash$ echo $?
0
bash$ false
bash$ echo $?
1
# Testing "false"
if false
then
echo "false evaluates \"true\""
else
echo "false evaluates \"false\""
fi
# false evaluates "false"
hash [cmds]
Records the path name of specified commands -- in the shell hash table [56] -- 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
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). "1384" is the PID or
process ID 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
Suspend 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 han
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 elapsed when executing commands, in the following form:
0m0.020s 0m0.020s
This capability is of relatively limited value, since it is not common to profile and benchmark shell
scripts.
kill
Forcibly terminate a process by sending it an appropriate terminate signal (see Example 16-6).
#!/bin/bash
# self-destruct.sh
kill -l lists all the signals (as does the file /usr/include/asm/signal.h).
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.
killall
The killall command kills a running process by name, rather than by process ID. If there are multiple
instances of a particular command running, then doing a killall on that command will terminate them
all.
This refers to the killall command in /usr/bin, not the killall script in
/etc/rc.d/init.d.
command
The command directive disables aliases and functions for the command immediately following it.
bash$ command ls
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 the external command
/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. [58].
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. [59] This saves system resources.
Note that autoload is not a part of the core Bash installation. It needs to be loaded in with enable
-f (see above).
Notation Meaning
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, -b, show
escape characters, and -i, show file inodes (see Example 15-4).
The ls command returns a non-zero exit status when attempting to list a non-existent
file.
bash$ ls abc
ls: abc: No such file or directory
bash$ echo $?
2
Example 15-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.
exit $exitcode
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 (mirror image).
cp
This is the file copy command. cp file1 file2 copies file1 to file2, overwriting file2 if
it already exists (see Example 15-6).
Particularly useful are the -a archive flag (for copying an entire directory tree), the
-u update flag (which prevents overwriting identically-named newer files), 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-20 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.
Why? Because rm sees a dash-prefixed filename as an option.
bash$ rm -badname
rm: invalid option -- b
Try `rm --help' for more information.
One clever workaround is to precede the filename with a " -- " (the end-of-options
flag).
bash$ rm -- -badname
Another method to is to preface the filename to be removed with a dot-slash .
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 [60] -- 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 or directory (see Example 14-14).
chmod +x filename
# Makes "filename" executable for all users.
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 overwritten with binary
zeroes. [61]
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 more detailed descriptions than do the man
pages.
There have been various attempts at "automating" the writing of man pages. For a script that makes a
tentative first step in that direction, see Example A-39.
find
-exec COMMAND \;
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 (plus sign ... +5).
#
# "-type filetype", where
# f = regular file
# d = directory
# l = symbolic link, etc.
#
# (The 'find' manpage and info page have complete option listings.)
# Possibly by:
The -exec option to find should not be confused with the exec shell builtin.
Example 15-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.
# (Thanks, S.C.)
#!/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
The find command also works without the -exec option.
#!/bin/bash
# Find suid root files.
# A strange suid file might indicate a security hole,
#+ or even a system intrusion.
directory="/usr/sbin"
# Might also try /sbin, /bin, /usr/bin, /usr/local/bin, etc.
permissions="+4000" # suid root (dangerous!)
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
ls | xargs -p -l gzip gzips every file in current directory, one at a time, prompting before
each operation.
Note that xargs processes the arguments passed to it sequentially, one at a time.
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.)
Or:
#!/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 -n $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.
#!/bin/bash
# copydir.sh
E_NOARGS=85
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),
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
# ^^ ^^
# ---------------------------------------------------------------
# Notes:
# -i is the "replace strings" option to xargs.
# The curly brackets are the placeholder for the replacement.
# 2&>/dev/null suppresses unwanted error messages.
#
# Can grep "$PROCESS_NAME" be replaced by pidof "$PROCESS_NAME"?
# ---------------------------------------------------------------
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 remove duplicates, prefix occurrence count
#+ and sort numerically.
#####################################################
exit $?
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
returns 8
expr 5 % 3
returns 2
expr 1 / 0
returns the error message, expr: division by zero
The multiplication operator must be escaped when used in an arithmetic expression with
expr.
y=`expr $y + 1`
#!/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
y=25
b=`expr $x = $y` # Test equality.
echo "b = $b" # 0 ( $x -ne $y )
echo
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 : (null) 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 "Temporary filename = $filename"
# It's great for creating "unique and random" 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
#!/bin/bash
# date-calc.sh
# Author: Nathan Coulter
# Used in ABS Guide with permission (thanks!).
diff () {
printf '%s' $(( $(date -u -d"$TARGET" +%s) -
$(date -u -d"$CURRENT" +%s)))
# %d = day of month.
}
# Exercise:
# --------
# Rewrite the diff () function to accept passed parameters,
#+ rather than using global variables.
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 random integers.
# 115281032
# 63408725
# 394504284
There are many more options (try man date).
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 verbose timing statistics for executing a command.
real 0m0.067s
user 0m0.004s
sys 0m0.005s
See also the very similar times command in the previous section.
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.
Before doing a cp -u (copy/update), use touch to update the time stamp of files you
don't wish overwritten.
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
shell script a line at a time. Input terminates with a Ctl-D.
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 non-interactive. 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.
The concept of batch processing dates back to the era of mainframe computers. It means running a
set of commands without user intervention.
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 sleep command defaults to seconds, but minute, hours, or days may also be
specified.
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-grained 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 sort utility, 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,
The results of a tsort will usually differ markedly from those of the standard sort command, above.
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.
# range action
# 1 s/.* //p
# 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
See also Example 15-39.
tail
lists the (tail) end of a file to stdout. The default is 10 lines, but this can be changed with the -n
option. 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 -n 1. For example
head -n 8 database.txt | tail -n 1 lists the 8th line of the file
database.txt.
Newer implementations of tail deprecate the older tail -$LINES filename usage. The
standard tail -n $LINES filename is correct.
See also Example 15-5, Example 15-39 and Example 29-6.
grep
A multi-purpose file search tool that uses Regular Expressions. It was originally a command/filter in
the venerable ed line editor: g/re/p -- global - regular expression - print.
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.
The --color (or --colour) option marks the matching string in color (on the console or in an
xterm window). Since grep prints out each entire line containing the matching pattern, this lets you
see exactly what is being matched. See also the -o option, which shows only the matching portion of
the line(s).
Example 15-16. Printing out the From lines in stored e-mail messages
#!/bin/bash
# from.sh
exit $?
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.
#!/bin/bash
# grp.sh: Rudimentary reimplementation of grep.
E_BADARGS=85
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
#!/bin/bash
# cw-solver.sh
# This is actually a wrapper around a one-liner (line 46).
E_NOPATT=71
DICT=/usr/share/dict/word.lst
# ^^^^^^^^ Looks for word list here.
# ASCII word list, one word per line.
echo
# ===============================================
# This is where all the work gets done.
grep ^"$1"$ "$DICT" # Yes, only one line!
# | |
# ^ is start-of-word regex anchor.
# $ is end-of-word regex anchor.
$ sh cw-solver.sh w...i....n
wellington
workingman
workingmen
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 generally 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=85
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
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 $?
# 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' or 'zenity' . . .
# 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.
See also Example A-41 for an example of speedy fgrep lookup on a large text file.
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.
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:
bash $ wc /usr/share/doc/sed-4.1.2/README
13 70 447 README
[13 lines 70 words 447 characters]
wc -w gives only the word count.
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 embedded 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=85
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,
#+ and considerably simplified by the author of the ABS Guide.
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.
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.
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 15-26 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 15-5.
#!/bin/bash
# colms.sh
# A minor 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 -b, since, 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
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.
E_WRONGARGS=85
if [ -z "$1" ]
then
echo "Usage: `basename $0` filename"
exit $E_WRONGARGS
fi
# ---------------------------
groff -Tascii -man $1 | less
# From the man page for groff.
# ---------------------------
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. [64] 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, for example: 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). [65]
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 text files in a shell archive are concatenated without compression, and the
resultant archive is essentially a shell script, complete with #!/bin/sh header, containing all the
necessary unarchiving commands, as well as the files themselves. Shar archives still show up in
Usenet newsgroups, but otherwise shar has been replaced by tar/gzip. The unshar command
unpacks shar archives.
The mailshar command is a Bash script that uses shar to concatenate multiple files into a single one
for e-mailing. This script supports compression and uuencoding.
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.
mktemp-1.5-11
perl-5.6.0-17
reiserfs-utils-3.x.0j-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"
###################################################################
find "$source" -depth | cpio -admvp "$destination"
# ^^^^^ ^^^^^
# Read the 'find' and 'cpio' info pages to decipher these options.
# The above works only relative to $PWD (current directory) . . .
#+ full pathnames are specified.
###################################################################
# Exercise:
# --------
exit $?
rpm2cpio
This command extracts a cpio archive from an rpm one.
#!/bin/bash
# de-rpm.sh: Unpack an 'rpm' archive
exit 0
# Exercise:
# Add check for whether 1) "target-file" exists and
#+ 2) it is an rpm archive.
# Hint: Parse output of 'file' command.
pax
The pax portable archive exchange toolkit facilitates periodic file backups and is designed to be
cross-compatible between various flavors of UNIX. It was ported from BSD to Linux.
pax -f daily_backup.pax
# Lists the files in the archive.
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.
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 gives the full path to "command." This is useful for finding out whether a particular
command or utility is installed on the system.
$bash which rm
/usr/bin/rm
For an interesting use of this command, see Example 33-14.
whereis
Similar to which, above, whereis command gives the full path to "command," 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
-rw-r--r-- 1 bozo bozo 877 Dec 17 2000 employment.xrolo
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=85
E_NOFILE=86
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).
The merge (3-way file merge) command is an interesting adjunct to diff3. Its syntax is merge
Mergefile file1 file2. The result is to output to Mergefile the changes that lead from
file1 to file2. Consider this command a stripped-down version of patch.
sdiff
Compare and/or edit two files in order to merge them into an output file. Because of its interactive
nature, this command would find little use in a script.
cmp
The cmp command is a simpler version of diff, above. Whereas diff reports the differences between
two files, cmp merely shows at what point they differ.
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.
This command is useful for comparing "dictionaries" or word lists -- sorted text files with one word
per line.
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. Their usual use is 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.
#!/bin/bash
# splitcopy.sh
exit $?
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 ()
{
# ------------------------------------------- #
# 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.
exit $E_DIR_NOMATCH
fi
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.
Security consultants have demonstrated that even sha1sum can be compromised. Fortunately, newer
Linux distros include longer bit-length sha224sum, sha256sum, sha384sum, and sha512sum
commands.
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 15-60, 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
#!/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 batch
processing mode by means of a shell script.
crypt
At one time, this was the standard UNIX file encryption utility. [66] 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 [67] 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.
Utility for building and compiling binary packages. This can also be used for any set of operations
triggered by incremental changes in source files.
The make command checks a Makefile, a list of file dependencies and operations to be carried out.
The make utility is, in effect, a powerful scripting language similar in many ways to Bash, but with
the capability of recognizing dependencies. For in-depth coverage of this useful tool set, see the GNU
software documentation site.
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 prove
useful 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 is still useful.
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 from the command-line or 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.
# "[email protected]"
# "[email protected]"
# "[email protected]"
#! /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:
# --------
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.
HNAME=nastyspammer.com
# HNAME=$HOST # Debug: test for localhost.
bash$ finger
Login Name Tty Idle Login Time Office Office Phone
bozo Bozo Bozeman tty1 8 Jun 25 16:59 (:0)
bozo Bozo Bozeman ttyp0 Jun 25 16:59 (:0.0)
bozo Bozo Bozeman ttyp1 Jun 25 17:07 (:0.0)
Out of security considerations, many networks disable finger and its associated daemon. [68]
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 18-6 and Example A-4).
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.
Its use within a shell script is not recommended.
wget
The wget utility noninteractively 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.
wget -c ftp://ftp.xyz25.net/bozofiles/filename.tar.bz2
# The -c option lets wget resume an interrupted download.
# This works with ftp servers and many HTTP sites.
#!/bin/bash
# quote-fetch.sh: Download a stock quote.
E_NOPARAMS=86
stock_symbol=$1
file_suffix=.html
# -----------------------------------------------------------
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-30 and Example A-31.
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 noninteractively.
#!/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 # Newly introduced in Bash, version 3.
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 \
--filter "merge,+/ $TMP" \
--exclude '*' \
$URL $DEST \
| /usr/bin/tee $LOG
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
See also Example A-32.
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.
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
# Note that the "mailx" command (in "send" mode) may be substituted
#+ for "mail" ... but with somewhat different options.
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.
mailstats
Show mail statistics. This command may be invoked only by root.
vacation
This utility automatically replies to e-mails that the intended recipient is on vacation and temporarily
unavailable. It 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; tput reset is the equivalent of reset.
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.
1. Example 33-13
2. Example 33-11
3. Example A-44
4. Example A-42
5. Example 26-2
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{{||}}~~,
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.
resize
Echoes commands necessary to set $TERM and $TERMCAP to duplicate the size (dimensions) of the
current terminal.
bash$ resize
set noglob;
setenv COLUMNS '80';
setenv LINES '24';
unset noglob;
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.
#!/bin/bash
# primes2.sh
CEILING=10000 # 1 to 10000
PRIME=0
E_NOTPRIME=
is_prime ()
{
local factors
factors=( $(factor $1) ) # Load output of `factor` into array.
if [ -z "${factors[2]}" ]
echo
for n in $(seq $CEILING)
do
if is_prime $n
then
printf %5d $n
fi # ^ Five positions per number suffices.
done # For a higher $CEILING, adjust upward, as necessary.
echo
exit
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,"
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 . . .
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 ([email protected])
# 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=85
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. Why?
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
[1-9]*) ibase=10;; # decimal
*)
Msg "illegal number $i - ignored"
continue;;
esac
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.
# ==> (Above comment by Paulo Marcel Coelho Aragao.)
do
case "$1" in
--) shift; break;;
-h) Usage;; # ==> Help message.
-*) Usage;;
*) break;; # First number
esac # ==> Error checking for illegal input might be appropriate.
shift
if [ $# -gt 0 ]
then
PrintBases "$@"
else # Read from stdin.
while read line
do
PrintBases $line
done
fi
exit
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.
declare -r M_PI=3.141592654
# Actual 9-place value of PI, for comparison purposes.
get_random ()
{
SEED=$(head -n 1 /dev/urandom | od -N 1 | awk '{ print $2 }')
RANDOM=$SEED # From "seeding-random.sh"
#+ example script.
let "rnum = $RANDOM % $DIMENSION" # Range less than 10000.
echo $rnum
}
# ==========================================================
# main() {
# "Main" code block, mimmicking a C-language main() function.
# Initialize variables.
shots=0
splashes=0
thuds=0
Pi=0
error=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.
# But still usually within plus-or-minus 5% . . .
#+ a pretty good rough approximation.
error=$(echo "scale=9; $Pi - $M_PI" | bc)
pct_error=$(echo "scale=2; 100.0 * $error / $M_PI" | bc)
echo -n "Deviation from mathematical value of PI = $error"
echo " ($pct_error% error)"
echo
exit
The dc (desk calculator) utility is stack-oriented and uses RPN ("Reverse Polish Notation"). Like bc,
it has much of the power of a programming language.
#!/bin/bash
# hexconvert.sh: Convert a decimal number to hexadecimal.
if [ -z "$1" ]
then # Need a command-line argument.
echo "Usage: $0 number"
exit $E_NOARGS
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
Studying the info page for dc is a painful path to understanding its intricacies. 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
# $ sh factr.sh 270138
# 2
# 3
# 11
# 4093
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
jot, seq
These utilities emit a sequence of integers, with a user-selectable increment.
The default 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.
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 $?
Somewhat more capable than seq, jot is a classic UNIX utility that is not normally
included in a standard Linux distro. However, the source rpm is available for
download from the MIT repository.
Unlike seq, jot can generate a sequence of random numbers, using the -r option.
#!/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
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 might have unintended consequences.
The yes command parses variables, or more accurately, it echoes parsed variables. For
example:
This particular "feature" may be used to create a very large ASCII file on the fly:
Hit Ctl-C very quickly, or you just might get more than you bargained for. . . .
The yes command may be emulated in a very simple script function.
yes ()
{ # Trivial emulation of "yes" ...
local DEFAULT_TEXT="y"
while [ true ] # Endless loop.
do
Note that banner has been dropped from many Linux distros.
printenv
Show all the environmental variables set for a particular user.
lp
The lp and lpr commands send file(s) to the print queue, to be printed as hard copy. [70] 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 "siphoning 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
|
==========================|====================
command ---> command ---> |tee ---> command ---> ---> output of pipe
===============================================
#!/bin/bash
# This short script by Omair Eshkenazi.
# Used in ABS Guide with permission (thanks!).
rm -f pipe1
rm -f pipe2
exit $?
4830.tar.gz BOZO
pipe1 BOZO
pipe2 BOZO
mkfifo-example.sh BOZO
Mixed.msg BOZO
pathchk
This command checks the validity of a filename. If the filename exceeds the maximum allowable
length (255 characters) or one or more of the directories in its path is not searchable, then an error
message results.
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, [72] upper/lower case, swapping of byte pairs
between input and output, and skipping and/or truncating the head or tail of the input file.
◊ if=INFILE
OUTFILE is the target file, the file that will have the data written to it.
◊ bs=BLOCKSIZE
This is the size of each block of data being read and written, usually a power of 2.
◊ skip=BLOCKS
How many blocks of data to skip in INFILE before starting to copy. This is useful when the
INFILE has "garbage" or garbled data in its header or when it is desirable to copy only a
portion of the INFILE.
◊ seek=BLOCKS
How many blocks of data to skip in OUTFILE before starting to copy, leaving blank data at
beginning of OUTFILE.
◊ count=BLOCKS
Copy only this many blocks of data, rather than the entire INFILE.
◊ conv=CONVERSION
#!/bin/bash
# self-copy.sh
file_subscript=copy
exit $?
#!/bin/bash
# exercising-dd.sh
# --------------------------------------------------------
exit 0
#!/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.
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
necessarily recommended.
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
hexdump
Performs a hexadecimal, octal, decimal, or ASCII dump of a binary file. This command is the rough
equivalent of od, above, but not nearly as useful. May be used to view the contents of a binary file, in
combination with dd and less.
080490bc <.init>:
80490bc: 55 push %ebp
80490bd: 89 e5 mov %esp,%ebp
. . .
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 md5sum 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 [73] processing filter, 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
# Arithmetic
echo "incr(22)" | m4 # 23
echo "eval(99 / 3)" | m4 # 33
exit
xmessage
This X-based variant of echo pops up a message/query window on the desktop.
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.
The various behaviors of the executable must be defined within the code of the
executable itself, analogous to something like the following in a shell script:
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.
lid
The lid (list ID) command shows the group(s) that a given user belongs to, or alternately, the users
belonging to a given group. May be invoked only by root.
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-14.
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 probably 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:
Kurt Glaesemann points out that the newgrp command could prove helpful in setting
the default group permissions for files a user writes. However, the chgrp command
might be more convenient for this purpose.
Terminals
tty
Echoes the name (filename) 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.
exit 0
# Even after the script exits, the new key value remains set.
# Exercise: How would you reset the erase character to the default value?
#!/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 and Example A-43.
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.
setterm -bold on
echo bold hello
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 and is usually found in a
system setup script.
It can be quite 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 18-1).
If write access to a particular terminal has been disabled with mesg, then wall cannot
send a message to that terminal.
uname
Output system specifications (OS, kernel version, etc.) to stdout. Invoked with the -a option, gives
verbose system info (see Example 15-5). The -s option shows only the OS type.
bash$ uname
Linux
bash$ uname -s
Linux
bash$ uname -a
Linux iron.bozo 2.6.15-1.2054_FC5 #1 Tue Mar 14 15:48:33 EST 2006
i686 i686 i386 GNU/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
...
The lsof command is a useful, if complex administrative tool. If you are unable to dismount a
filesystem and get an error message that it is still in use, then running lsof helps determine which files
are still open on that filesystem. The -i option lists open network socket files, and this can help trace
intrusion or hack attempts.
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.
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 <[email protected]>
# Used in ABS Guide with permission.
# ---------------------------------------------------
E_BADARGS=65 # Need at least two args.
TWO_WINKS=2 # How long to sleep.
THREE_WINKS=3
IDPORT=113 # Authentication "tap ident" port.
RAND1=999
RAND2=31337
TIMEOUT0=9
TIMEOUT1=8
TIMEOUT2=4
# ---------------------------------------------------
case "${2}" in
"" ) echo "Need HOST and at least one PORT." ; exit $E_BADARGS ;;
esac
exit $?
# Notes:
# -----
bash$ free
total used free shared buffers cached
Mem: 30504 28624 1880 15820 1608 16376
-/+ buffers/cache: 10640 19864
Swap: 68540 3128 65412
To show unused RAM memory:
bash$ lsdev
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.
In a script, you can use stat to extract information about files (and filesystems) and set variables
accordingly.
FILENAME=testfile.txt
file_name=$(stat -c%n "$FILENAME") # Same as "$FILENAME" of course.
file_owner=$(stat -c%U "$FILENAME")
file_size=$(stat -c%s "$FILENAME")
# Certainly easier than using "ls -l $FILENAME"
#+ and then parsing with sed.
file_inode=$(stat -c%i "$FILENAME")
file_type=$(stat -c%F "$FILENAME")
file_access_rights=$(stat -c%A "$FILENAME")
exit 0
sh fileinfo2.sh
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-4.
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
. . .
A netstat -lptu shows sockets that are listening to ports, and the associated processes.
This can be useful for determining whether a computer has been hacked or
compromised.
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$ hostname
localhost.localdomain
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.
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 14-14 and Example 27-3).
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
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 and snice, which change
the priority of a running process or processes, and skill, which sends a kill signal to a process or
processes.
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.
On some Linux distros, the halt command has 755 permissions, so it can be invoked
by a non-root user. A careless halt in a terminal or a script may shut down the system!
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 in