13.6 Shell Scripts
A shell script is simply a file that
contains a set of commands to be run by the shell when invoked. By
storing commands as a shell script, you make it easy to execute them
again and again. As an example, consider a file named
deleter, which contains the following lines:
echo -n Deleting the temporary files...
rm -f *.tmp
echo Done.
The echo commands simply print text on the
console. The -n option of the first
echo command causes omission of the
trailing newline character normally written by the echo command, so both echo commands write their text on a single
line. The rm command removes all files having names
ending in .tmp from the current working
directory.
You can execute this script by issuing the sh command, as follows:
$ sh deleter
|
If you invoke the sh command without
an argument specifying a script file, a new interactive shell is
launched. To exit the new shell and return to your previous session,
issue the exit command.
|
|
If the deleter file were in a directory other
than the current working directory, you'd have to
type an absolute path, for example:
$ sh /home/bill/deleter
You can make it a bit easier to execute the script by changing its
access mode to include execute access. To do so, issue the
following command:
$ chmod 555 deleter
This gives you, members of your group, and everyone else the ability
to execute the file. To do so, simply type the absolute path of the
file, for example:
$ /home/bill/deleter
If the file is in the current directory, you can issue the following
command:
$ ./deleter
You may wonder why you can't simply issue the
command:
$ deleter
In fact, this still simpler form of the command will work, so long as
deleter resides in a directory on your search
path. You'll learn about the search path later.
Linux includes several standard scripts that are run at various
times. Table 13-2 identifies these and gives the
time when each is run. You can modify these scripts to operate
differently. For example, if you want to establish command aliases
that are available whenever you log in, you can use a text editor to
add the appropriate lines to the .profile file
that resides in your /home directory. Since the
name of this file begins with a dot (.), the
ls command won't
normally show the file. You must specify the -a option in order to see this and other hidden
files.
Table 13-2. Special shell scripts
/etc/profile
|
Executes when the user logs in
|
~/.bash_profile
|
Executes when the user logs in
|
~/.bashrc
|
Executes when bash is launched
|
~/.bash_logout
|
Executes when the user logs out
|
|
If you want to modify one of the standard scripts that should reside
in your home directory but find that your /home
directory does not contain the indicated file, simply create the
file. The next time you log in, log out, or launch
bash (as appropriate) the shell will execute
your script.
|
|
13.6.1 Input/Output Redirection and Piping
The shell provides three standard data streams:
- stdin
|
- The standard input stream
|
- stdout
|
- The standard output stream
|
- stderr
|
- The standard error stream
|
By default, most programs read their
input from stdin and write their output to
stdout. Because both streams are normally
associated with a console, programs behave as you generally want,
reading input data from the console keyboard and writing output to
the console screen. When a well-behaved program writes an error
message, it writes the message to the stderr
stream, which is also associated with the console by default. Having
separate streams for output and error messages presents an important
opportunity, as you'll see in a moment.
Although the shell associates the three standard input/output streams
with the console by default, you can specify input/output redirectors
that, for example, associate an input or output stream with a file.
Table 13-3 summarizes the most important
input/output redirectors.
Table 13-3. Input/output redirectors
>file
|
Redirects standard output stream to specified file
|
2>file
|
Redirects standard error stream to specified file
|
>>file
|
Redirects standard output stream to specified file, appending output
to the file if the file already exists
|
2>>file
|
Redirects standard error stream to specified file, appending output
to the file if the file already exists
|
&>file
|
Redirects standard output and error streams to the specified file
|
2>&1
|
Combines the standard error stream with the standard output stream
|
<file
|
Redirects standard input stream from the specified file
|
<<text
|
Reads standard input until a line matching text is found, at which
point end-of-file is posted
|
cmd1 | cmd2
|
Takes the standard input of cmd2 from the standard output of cmd1
(also known as the pipe redirector)
|
To see how redirection works, consider the wc command. This command takes a series of
filenames as arguments and prints the total number of lines, words,
and characters present in the specified files. For example, the
command:
$ wc /etc/passwd
might produce the output:
22 26 790 /etc/passwd
which indicates that the file /etc/passwd
contains 22 lines, 26 words, and 790 characters. Generally, the
output of the command appears on your console. But consider the
following command, which includes an output redirector:
$ wc /etc/passwd > total
If you issue this command, you won't see any console
output, because the output is redirected to the file
total, which the command creates (or overwrites,
if the file already exists). If you execute the following commands:
$ wc /etc/passwd > total
$ cat total
you will see the output of the wc
command on the console.
Perhaps you can now see the reason for having the separate output
streams
stdout
and
stderr.
If the shell provided a single output stream, error messages and
output would be mingled. Therefore, if you redirected the output of a
program to a file, any error messages would also be redirected to the
file. This might make it difficult to notice an error that occurred
during program execution. Instead, because the streams are separate,
you can choose to redirect only stdout to a
file. When you do so, error messages sent to
stderr appear on the console in the usual way.
Of course, if you prefer, you can redirect both
stdout and stderr to the
same file or redirect them to different files. As usual in the Unix
world, you can have it your own way.
A simple way of avoiding annoying output is to redirect it to the
null device
file, /dev/ null. If you
redirect the stderr stream of a command to
/dev/null, you won't see any
error messages the command produces. For example, the grep command prints an error message if you
invoke it on a directory. So, if you invoke the grep command on the current directory
(*) and the current directory contains
subdirectories, you'll see unhelpful error messages.
To avoid them, use a command like this one, which searches for files
containing the text "localhost":
$ grep localhost * 2>/dev/null
Just as you can direct the standard output or error stream of a
command to a file, you can also redirect a command's
standard input stream to a file so the command reads from the file
instead of the console. For example, if you issue the wc command without arguments, the command
reads its input from stdin. Type some words and
then type the end-of-file character (Ctrl-D),
and wc will report the number of
lines, words, and characters you entered. You can tell wc to read from a file, rather than the
console, by issuing a command like:
$ wc </etc/passwd
Of course, this isn't the usual way of invoking
wc. The author of wc helpfully provided a command-line argument
that lets you specify the file from which wc reads. However, by using a redirector, you
could read from any desired file even if the author had been less
helpful.
|
Some programs are written to ignore redirectors. For example, when
invoked without special options, the passwd
command expects to read the new password only from the console, not
from a file. You can compel such programs to read from a file, but
doing so requires techniques more advanced than redirectors.
|
|
When you specify no command-line arguments, many Unix programs read
their input from stdin and write their output to
stdout. Such programs are called
filters.
Filters can be easily fitted together to perform a series of related
operations. The tool for combining filters is the
pipe, which connects the output of one program
to the input of another. For example, consider this command:
$ ls ~ | wc -l
The command consists of two commands, joined by the pipe redirector
(|). The first command lists the names of the
nonhidden files in the user's home directory, one
file per line. The second command invokes wc by using the -l option, which causes wc to print only the total number of lines,
rather than printing the total number of lines, words, and
characters. The pipe redirector sends the output of the ls command to the wc command, which counts and prints the number
of lines in its input, which happens to be the number of files in the
user's home directory.
This is a simple example of the power and sophistication of the Unix
shell. Unix doesn't include a command that counts
the files in the user's home directory and
doesn't need to do so. Should the need to count the
files arise, a knowledgeable Unix user can prepare a simple script
that computes the desired result by using general- purpose Unix
commands.
13.6.2 Shell Variables
If you've studied
programming, you know that programming languages resemble algebra.
Both programming languages and algebra let you refer to a value by a
name. And both programming languages and algebra include elaborate
mechanisms for manipulating named values.
The shell is a programming language in its own right, letting you
refer to variables known as shell or
environment variables. To
assign a value to a shell variable, you use a command that has the
following form:
$ variable=value
For example, the command:
$ DifficultyLevel=1
assigns the value 1 to the shell variable named DifficultyLevel.
Unlike algebraic variable, shell variables can have nonnumeric
values. For example, the command:
$ Difficulty=medium
assigns the value medium to the shell variable named Difficulty.
Shell variables are widely used within Unix, because they provide a
convenient way of transferring values from one command to another.
Programs can obtain the value of a shell variable and use the value
to modify their operation, in much the same way they use the value of
command-line arguments.
You can see a list of shell variables by issuing the set command. Usually, the command produces
more than a single screen of output. So, you can use a
pipe
redirector and the less command to view the output one screen at
a time:
$ set | less
Press the spacebar to see each successive page of output.
You'll probably see several of the shell variables
described in Table 13-4 among those printed by the
set command. The values of these
shell variables are generally set by one or another of the startup
scripts described earlier in
this chapter.
Table 13-4. Important environment variables
DISPLAY
|
The X display to be used; for example, localhost:0
|
HOME
|
The absolute path of the user's home directory
|
HOSTNAME
|
The Internet name of the host
|
LOGNAME
|
The user's login name
|
MAIL
|
The absolute path of the user's mail file
|
PATH
|
The search path (see the upcoming Section 13.6.3)
|
SHELL
|
The absolute path of the current shell
|
TERM
|
The terminal type
|
USER
|
The user's current username; may differ from the
login name if the user executes the su command
|
You can use the value of a shell variable in a command by preceding
the name of the shell variable by a dollar sign
($). To avoid
confusion with surrounding text, you can enclose the name of the
shell variable within curly braces ({
});
it's good practice (though not necessary) to do so
consistently. For example, you can change the current working
directory to your /home directory by issuing the
command:
$ cd ${HOME}
Of course, issuing the cd command
with no argument causes the same result. However, suppose you want to
change to the /work subdirectory of your home
directory. The following command accomplishes exactly that:
$ cd ${HOME}/work
An easy way to see the value of a shell variable is to specify the
variable as the argument of the echo command. For example, to see the value
of the HOME shell variable, issue the command:
$ echo ${HOME}
To make the value of a shell variable available not just to the
shell, but to programs invoked by using the shell,
you must export the shell variable. To do so, use the export command, which has the form:
$ export variable
where variable specifies the name of the
variable to be exported. A shorthand form of the command lets you
assign a value to a shell variable and export the variable in a
single command:
$ export variable=value
You can remove the value associated with a shell variable by giving
the variable an empty value:
$ variable=
However, a shell variable with an empty value remains a shell
variable and appears in the output of the set command. To dispense with a shell
variable, you can issue the unset command:
$ unset variable
Once you unset the value of a variable, the variable no longer
appears in the output of the set
command.
13.6.3 The Search Path
The special shell
variable PATH holds a series of paths known collectively as the
search path. Whenever you
issue an external command, the shell searches the paths that comprise
the search path, seeking the program file that corresponds to the
command. The startup scripts establish the initial value of the PATH
shell variable, but you can modify its value to include any desired
series of paths. You must use a colon (:) to
separate each path of the search path. For example, suppose that PATH
has the following value:
/usr/bin:/bin:/usr/local/bin:/usr/bin/X11:/usr/X11R6/bin
You can add a new search directory, say
/home/bill, by issuing the following command:
$ PATH=${PATH}:/home/bill
Now, the shell will look for external programs in
/home/bill as well as in the default
directories. However, the problem is that the shell will look there
last. If you prefer to check /home/bill first,
issue the following command instead:
$ PATH=/home/bill:${PATH}
The which command helps you work with the PATH
shell variable. It checks the search path for the file specified as
its argument and prints the name of the matching path, if any. For
example, suppose you want to know where the program file for the
wc command resides. Issuing the
command:
$ which wc
will tell you that the program file is
/usr/bin/wc (or whatever other path is correct
for your system).
13.6.4 Quoted Strings
Sometimes
the shell may misinterpret a command you've written,
globbing a filename or expanding a reference to a shell variable that
you hadn't intended. Of course,
it's actually your interpretation
that's mistaken, not the shell's.
Therefore, it's up to you to rewrite your command so
the shell's interpretation is congruent with what
you intended.
Quote
characters, described in Table 13-5, can help you
by controlling the operation of the shell. For example, by enclosing
a command argument within single quotes, you can prevent the shell
from globbing the argument or substituting the argument with the
value of a shell variable.
Table 13-5. Quote characters
' (single quote)
|
Characters within a pair of single quotes are interpreted literally;
that is, their metacharacter meanings (if any) are ignored.
Similarly, the shell does not replace references to shell or
environment variables with the value of the referenced variable.
|
" (double quote)
|
Characters within a pair of double quotes are interpreted literally;
that is, their metacharacter meanings (if any) are ignored. However,
the shell does replace references to shell or environment variables
with the value of the referenced variable.
|
´ (backquote)
|
Text within a pair of backquotes is interpreted as a command, which
the shell executes before executing the rest of the command line. The
output of the command replaces the original backquoted text.
|
\ (backslash)
|
The following character is interpreted literally; that is, its
metacharacter meaning (if any) is ignored. The backslash character
has a special use as a line continuation character. When a line ends
with a backslash, the line and the following line are considered part
of a single line.
|
To see quoted characters in action, consider how you might cause the
echo command to produce the output
$PATH. If you simply issue the command:
$ echo $PATH
the echo command prints the value of
the PATH shell variable. However, by enclosing the argument within
single quotes, you obtain the desired result:
$ echo '$PATH'
Double quotes have a similar effect. They prevent the shell from
globbing a filename but permit the expansion of shell variables.
Backquotes operate differently; they let you execute a command and
use its output as an argument of another command. For example, the
command:
$ echo My home directory contains ´ls ~ | wc -l´ files.
prints a message that gives the number of files in the
user's /home directory. The
command works by first executing the command contained within
backquotes:
$ ls ~ | wc -l
This command, as explained earlier, computes and prints the number of
files in the user's directory. Because the command
is enclosed in backquotes, its output is not printed; instead the
output replaces the original backquoted text.
The resulting command becomes:
echo My home directory contains 22 files.
When executed, this command prints the output:
My home directory contains 22 files.
You may now begin to appreciate the power of the Linux shell: by
including command aliases in your bashrc script,
you can extend the command repertoire of the shell. And, by using
filename completion and the history list, you can reduce the amount
of typing it takes to enter frequently used commands. Once you grasp
how to use it properly, the Linux shell is a powerful, fast, and
easy-to-use interface that avoids the limitations and monotony of the
more familiar point-and-click graphical interface.
But the shell has additional features that extend its capabilities
even further. As you'll see in the next section, the
Linux shell includes a powerful programming language that provides
argument processing, conditional logic, and loops.
|