Search the Catalog
Learning Red Hat Linux, 2nd Edition

Learning Red Hat Linux, 2nd Edition

A Guide to Red Hat Linux for New Users

By Bill McCarty
2nd Edition February 2002 
0-596-00071-5, Order Number: 0715
368 pages, $34.95 US (Includes CD-ROM) $52.95 CA £24.95 UK

Chapter 13
Advanced Shell Usage and Shell Scripts

Like an MS-DOS Prompt window, the Unix shell is a command interpreter that lets you issue and execute commands. By means of the shell, you use and control your system. If you're accustomed to the point-and-click world of graphical user interfaces, you may question the value of learning to use the Linux shell. Many users initially find the shell cumbersome, and some retreat to the familiar comfort of the graphical user interface (GUI), avoiding the shell whenever possible. However, as this chapter explains, the shell unlocks the true power of Linux.

The Power of the Unix Shell

While it's true that the shell is an older style of interacting with a computer than the GUI, the graphical user interface is actually the more primitive interface. The GUI is easy to learn and widely used, but the shell is vastly more sophisticated. Using a GUI is somewhat like communicating in American Indian sign language. If your message is a simple one, like "We come in peace," you can communicate it by using a few gestures. However, if you attempted to give Lincoln's Gettysburg address--a notably short public discourse--you'd find your task quite formidable.[1]

The designer of a program that provides a GUI must anticipate all the possible ways in which the user will interact with the program and provide ways to trigger the appropriate program responses by means of pointing and clicking. Consequently, the user is constrained to working only in predicted ways. The user is therefore unable to adapt the GUI program to accommodate unforeseen tasks and circumstances. In a nutshell, that's why many system administration tasks are performed using the shell: system administrators, in fulfilling their responsibility to keep a system up and running, must continually deal with and overcome the unforeseen.

The shell reflects the underlying philosophy of Unix, which provides a wide variety of small, simple tools (that is, programs), each performing a single task. When a complex operation is needed, the tools work together to accomplish the complex operation as a series of simple operations, one step at a time. Many Unix tools manipulate text, and since Unix stores its configuration data in text form rather than in binary form, the tools are ideally suited for manipulating Unix itself. The shell's ability to freely combine tools in novel ways is what makes Unix powerful and sophisticated. Moreover, as you'll learn, the shell is extensible: you can create shell scripts that let you store a series of commands for later execution, saving you the future tedium of typing or pointing and clicking to recall them.

The contrary philosophy is seen in operating systems such as Microsoft Windows, which employ elaborate, monolithic programs that provide menus, submenus, and dialog boxes. Such programs have no way to cooperate with one another to accomplish complex operations that weren't anticipated when the programs were designed. They're easy to use so long as you remain on the beaten path, but once you step off the trail, you find yourself in a confusing wilderness.

Of course, not everyone shares this perspective. The Usenet newsgroups, for example, are filled with postings debating the relative merits of GUIs. Some see the Unix shell as an arcane and intimidating monstrosity. But, even if they're correct, it's inarguable that when you learn to use the shell, you begin to see Unix as it was intended (whether that's for better or for worse).

When you are performing common, routine operations, a GUI that minimizes typing can be a relief, but when faced with a complex, unstructured problem that requires a creative solution, the shell is more often the tool of choice. Creating solutions in the form of shell scripts allows solutions to be stored for subsequent reuse. Perhaps even more important, shell scripts can be studied to quickly bone up on forgotten details, expediting the solution of related problems.

Filename Globbing

Before the shell passes arguments to an external command or interprets a built-in command, it scans the command line for certain special characters and performs an operation known as filename globbing. Filename globbing resembles the processing of wildcards used in MS-DOS commands, but it's much more sophisticated. Table 13-1 describes the special characters used in filename globbing, known as filename metacharacters.

Table 13-1: Filename Metacharacters

Metacharacter

Function

*

Matches a string of zero or more characters

?

Matches exactly one character

[abc...]

Matches any of the characters specified

[a-z]

Matches any character in the specified range

[!abc...]

Matches any character other than those specified

[!a-z]

Matches any character not in the specified range

~

The home directory of the current user

~userid

The home directory of the specified user

~+

The current working directory

~-

The previous working directory

In filename globbing, just as in MS-DOS wildcarding, the shell attempts to replace metacharacters appearing in arguments in such a way that arguments specify filenames. Filename globbing makes it easier to specify names of files and sets of files.

For example, suppose the current working directory contains the following files: file1, file2, file3, and file04. Suppose you want to know the size of each file. The following command reports that information:

ls -l file1 file2 file3 file04

However, the following command reports the same information and is much easier to type:

ls -l file*

As Table 13-1 shows, the * filename metacharacter can match any string of characters. Suppose you issued the following command:

ls -l file?

The ? filename metacharacter can match only a single character. Therefore, file04 would not appear in the output of the command.

Similarly, the command:

ls -l file[2-3]

would report only file2 and file3, because only these files have names that match the specified pattern, which requires that the last character of the filename be in the range 2-3.

You can use more than one metacharacter in a single argument. For example, consider the following command:

ls -l file??

This command will list file04, because each metacharacter matches exactly one filename character.

Most commands let you specify multiple arguments. If no files match a given argument, the command ignores the argument. Here's another command that reports all four files:

ls -l file0* file[1-3]

TIP:   Suppose that a command has one or more arguments that include one or more metacharacters. If none of the arguments matches any filenames, the shell passes the arguments to the program with the metacharacters intact. When the program expects a valid filename, an unexpected error may result.

The tilde (~) metacharacter lets you easily refer to your home directory. For example, the following command:

ls ~

would list the files in your home directory.

Filename metacharacters don't merely save you typing. They let you write scripts that selectively process files by name. You'll see how that works later in this chapter.

Shell Aliases

Shell aliases make it easier to use commands by letting you establish abbreviated command names and by letting you prespecify common options and arguments for a command. To establish a command alias, issue a command of the form:

alias name='command'

where command specifies the command for which you want to create an alias and name specifies the name of the alias. For example, suppose you frequently type the MS-DOS command dir when you intend to type the Linux command ls -l. You can establish an alias for the ls -l command by issuing this command:

alias dir='ls -l'

Once the alias is established, if you mistakenly type dir, you'll get the directory listing you wanted instead of the default output of the dir command, which resembles ls rather than ls -l. If you like, you can establish similar aliases for other commands.

Your default Linux configuration probably defines several aliases on your behalf. To see what they are, issue the command:

alias

If you're logged in as root, you may see the following aliases:

alias cp='cp -i'
alias dir='ls -l'
alias ls='ls --color'
alias mv='mv -i'
alias rm='rm -i'

Notice how several commands are self-aliased. For example, the command rm -i is aliased as rm. The effect is that the - i option appears whenever you issue the rm command, whether or not you type the option. The - i option specifies that the shell will prompt for confirmation before deleting files. This helps avoid accidental deletion of files, which can be particularly hazardous when you're logged in as root. The alias ensures that you're prompted for confirmation even if you don't ask to be prompted. If you don't want to be prompted, you can issue a command like:

rm -f files

where files specifies the files to be deleted. The - f option has an effect opposite that of the - i option; it forces deletion of files without prompting for confirmation. Because the command is aliased, the command actually executed is:

rm -i -f files

The - f option takes precedence over the - i option, because it occurs later in the command line.

If you want to remove a command alias, you can issue the unalias command:

unalias alias

where alias specifies the alias you want to remove. Aliases last only for the duration of a login session, so you needn't bother to remove them before logging off. If you want an alias to be effective each time you log in, you can use a shell script, which we'll discuss later in the chapter.

Using Virtual Consoles

You can use a terminal window to issue shell commands. However, you can issue shell commands even when X is not running or available. To do so, you use the Linux virtual console feature.

Linux provides six virtual consoles for interactive use; a seventh virtual console is associated with the graphical user interface. You can use special keystrokes to switch between virtual consoles. The keystroke Alt-Fn, where n is the number of a virtual console (1-6), causes Linux to display virtual console n. For example, you can display virtual console 2 by typing Alt-F2. You can view only a single console at a time, but you can switch rapidly between consoles by using the appropriate keystroke. The keystroke Alt-F7 causes Linux to enter graphical mode using virtual console 7.

Virtual consoles also have a screensaver feature like that found on Windows. If a virtual console is inactive for an extended period of time, Linux blanks the monitor. To restore the screen without disturbing its contents, simply press the Shift key.

Logging In

To log in using a virtual console, type your user ID and press Enter. The system prompts you for the password associated with your account. Type the proper password and press Enter. To prevent anyone nearby from learning your password, Linux does not display your password as your enter it. If you suspect you've typed it incorrectly, you can either hit the Backspace key a number of times sufficient to delete the characters you've entered and type the password again or simply press Enter and start over. If you type the user ID or password incorrectly, Linux displays the message "login incorrect" and prompts you to try again.

When you've successfully logged in, you'll see a command prompt that looks something like this:

[bill@home bill]$

If you logged in as a user other than the root user, you'll see a prompt other than a hash mark (#); by default, you'll see a dollar sign ($). The prompt tells you that the Linux bash shell is ready to accept your commands.

Logging Out

When you're done using a virtual console, you should log out by typing the command exit and pressing Enter. When you log out, the system frees memory and other resources that were allocated when you logged in, making those resources available to other users.

When the system logs you out, it immediately displays a login prompt. If you change your mind and want to access the system, you can log in simply by supplying your username and password.

X and the Shell

You can configure your system to boot into nongraphical mode, if you prefer. If your video adapter is not compatible with X, you have no alternative but to do so. However, some Linux users prefer to configure their system to boot into nongraphical mode. A simple command lets such users launch an X session whenever they wish.

Configuring a Nongraphical Login

Linux provides several runlevels. Each runlevel has an associated set of services. For instance, runlevel 3 is associated with a text-based login and run level 5 is associated with an X-based, graphical login. Changing runlevels automatically starts and stops services associated with the old and new run levels.

You can determine the current runlevel by issuing the following command:

[bill@home bill]$ runlevel

The output of the command shows the previous and current runlevels. For example, the output:

3 5

indicates that the current run level is 5 and that the previous run level was 3.

To change the current runlevel, issue the init command. For example, to enter runlevel 3, issue the following command while logged in as root:

[bill@home bill]$ init 3

In response to this command, the system will start and stop services as required to enter runlevel 3.

The /etc/inittab file specifies the default runlevel, which the system enters when booted. By changing the default run level to 3, you can configure your system to provide a nongraphical login when it boots. To do so, log in as root and load the /etc/inittab file into the pico editor by issuing the command:

[bill@home bill]$ pico /etc/inittab

Find the line that reads:

id:5:initdefault:

Change the 5 to a 3:

id:3:initdefault:

Save the file and exit pico. The next time you boot your system, it will automatically enter runlevel 3 and provide a nongraphical login screen.

Starting and Stopping X from a Text-based Login

To start X from a text-based login, type the command:

startx

Your system's screen should briefly go blank and then you should see X's graphical desktop.

WARNING:  If the screen is garbled or remains blank for more than about 30 seconds, your X configuration may be faulty. Immediately turn off your monitor or terminate X by pressing Ctrl-Alt-Backspace.

To quit X, press Ctrl-Alt-Backspace. This is a somewhat abrupt way of exiting X. Depending on the X configuration, you may be able to right-click the desktop and select Exit from the pop-up menu. This method is less abrupt than pressing Ctrl-Alt-Backspace, but it still falls short of ideal.

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

TIP:   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

Script

Function

/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

TIP:  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.

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

Redirector

Function

>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.

TIP:   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.

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

Variable

Function

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 next subsection)

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.

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 the default directories. However, the only 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).

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

Character

Function

' (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 this 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.

Understanding Shell Scripts

This section explains how more advanced shell scripts work. The information is also adequate to equip you to write many of your own useful shell scripts. The section begins by showing how to process a script's arguments. Then it shows how to perform conditional and iterative operations.

Processing Arguments

You can easily write scripts that process arguments, because a set of special shell variables holds the values of arguments specified when your script is invoked. Table 13-6 describes the most popular such shell variables.

Table 13-6: Special Shell Variables Used in Scripts

Variable

Meaning

$#

The number of arguments.

$0

The command name.

$1, $2, ... ,$9

The individual arguments of the command.

$*

The entire list of arguments, treated as a single word.

$@

The entire list of arguments, treated as a series of words.

$?

The exit status of the previous command; a value of 0 denotes a successful completion.

$$

The ID of the current process.

For example, here's a simple one-line script that prints the value of its second argument:

echo My second argument has the value $2.

Suppose you store this script in the file second, change its access mode to permit execution, and invoke it as follows:

./second a b c

The script will print the output:

My second argument has the value b.

Notice that the shell provides variables for accessing only nine arguments. Nevertheless, you can access more than nine arguments. The key to doing so is the shift command, which discards the value of the first argument and shifts the remaining values down one position. Thus, after executing the shift command, the shell variable $9 contains the value of the 10th argument. To access the 11th and subsequent arguments, you simply execute the shift command the appropriate number of times.

Exit Codes

The shell variable $? holds the numeric exit status of the most recently completed command. By convention, an exit status of zero denotes successful completion; other values denote error conditions of various sorts.You can set the error code in a script by issuing the exit command, which terminates the script and posts the specified exit status. The format of the command is:

exit status

where status is a nonnegative integer that specifies the exit status.

Conditional Logic

A shell script can employ conditional logic, which lets the script take different action based on the values of arguments, shell variables, or other conditions. The test command lets you specify a condition, which can be either true or false. Conditional commands (including the if, case, while, and until commands) use the test command to evaluate conditions.

The test command

Table 13-7 describes some argument forms commonly used with the test command. The test command evaluates its arguments and sets the exit status to zero, which indicates that the specified condition was true, or a nonzero value, which indicates that the specified condition was false.

Table 13-7: Commonly Used Argument Forms of the test Command

Form

Function

-d file

The specified file exists and is a directory.

-e file

The specified file exists.

-r file

The specified file exists and is readable.

-s file

The specified file exists and has nonzero size.

-w file

The specified file exists and is writable.

-x file

The specified file exists and is executable.

-L file

The specified file exists and is a symbolic link.

f1 -nt f2

File f1 is newer than file f2.

f1 -ot f2

File f1 is older than file f2.

-n s1

String s1 has nonzero length.

-z s1

String s1 has zero length.

s1 = s2

String s1 is the same as string s2.

s1 != s2

String s1 is not the same as string s2.

n1 -eq n2

Integer n1 is equal to integer n2.

n1 -ge n2

Integer n1 is greater than or equal to integer n2.

n1 -gt n2

Integer n1 is greater than integer n2.

n1 -le n2

Integer n1 is less than or equal to integer n2.

n1 -lt n2

Integer n1 is less than integer n2.

n1 -ne n2

Integer n1 is not equal to integer n2.

!

The NOT operator, which reverses the value of the following condition.

-a

The AND operator, which joins two conditions. Both conditions must be true for the overall result to be true.

-o

The OR operator, which joins two conditions. If either condition is true, the overall result is true.

\( ... \)

You can group expressions within the test command by enclosing them within \( and \).

To see the test command in action, consider the following script:

test -d $1
echo $?

This script tests whether its first argument specifies a directory and displays the resulting exit status, a zero or a nonzero value that reflects the result of the test.

If the script was stored in the file tester, which permitted execute access, executing the script might yield results similar to the following:

$ ./tester /
0
$ ./tester /missing
1

These results indicate that the root directory (/) exists and that the /missing directory does not.

The if command

The test command is not of much use by itself, but combined with commands such as the if command, it is useful indeed. The if command has the following form:

if command
then
  commands
else
 commands
fi

The command that usually follows if is a test command. However, this need not be so. The if command merely executes the specified command and tests its exit status. If the exit status is zero, the first set of commands is executed; otherwise, the second set of commands is executed. An abbreviated form of the if command does nothing if the specified condition is false:

if command
then
  commands
fi

When you type an if command, it occupies several lines; nevertheless, it's considered a single command. To underscore this, the shell provides a special prompt, called the secondary prompt, after you enter each line. You won't see the secondary prompt when entering a script using a text editor, or any other shell prompt for that matter.

As an example, suppose you want to delete a file, file1, if it's older than another file, file2. The following command would accomplish the desired result:

if test file1 -ot file2
then
  rm file1
fi

You could incorporate this command in a script that accepts arguments specifying the filenames:

if test $1 -ot $2
then
  rm $1
  echo Deleted the old file.
fi

If you name the script riddance and invoke it as follows:

riddance thursday wednesday

the script will delete the thursday file if that file is older than the wednesday file.

The case command

The case command provides a more sophisticated form of conditional processing:

case value in
  pattern1) commands ;;
  pattern2) commands ;;
  ...
esac

The case command attempts to match the specified value against a series of patterns. The commands associated with the first matching pattern, if any, are executed. Patterns are built using characters and metacharacters, such as those used to specify command arguments. As an example, here's a case command that interprets the value of the first argument of its script:

case $1 in
  -r) echo Force deletion without confirmation ;;
  -i) echo Confirm before deleting ;;
   *) echo Unknown argument ;;
esac

The command echoes a different line of text, depending on the value of the script's first argument. As done here, it's good practice to include a final pattern that matches any value.

The while command

The while command lets you execute a series of commands iteratively (that is, repeatedly) so long as a condition tests true:

while command
do
 commands
done

Here's a script that uses a while command to print its arguments on successive lines:

echo $1
while shift 2> /dev/null
do
  echo $1
done

Notice how the 2> operator is used to direct error messages to the device /dev/null, which prevents them from being seen. You can omit this operator if you prefer.

The commands that comprise the do part of a while (or any other loop command) can include if, case, and even other while commands. However, scripts rapidly become difficult to understand when this occurs often. You should include conditional commands within other conditional commands only with due consideration for the clarity of the result. Don't forget to include comments in your scripts (with each commented line beginning with a #) to clarify difficult constructs.

The until command

The until command lets you execute a series of commands iteratively (that is, repeatedly) so long as a condition tests false:

until command
do
 commands
done

Here's a script that uses an until command to print its arguments on successive lines, until it encounters an argument that has the value red:

until test $1 = red
do
  echo $1
  shift
done

For example, if the script were named stopandgo and stored in the current working directory, the command:

./stopandgo green yellow red blue

would print the lines:

green
yellow

The for command

The for command iterates over the elements of a specified list:

for variable in list
do
  commands
done

Within the commands, you can reference the current element of the list by means of the shell variable $variable, where variable is the name specified following the for. The list typically takes the form of a series of arguments, which can incorporate metacharacters. For example, the following for command:

for i in 2 4 6 8
do
  echo $i
done

prints the numbers 2, 4, 6, and 8 on successive lines.

A special form of the for command iterates over the arguments of a script:

for variable
do
  commands
done

For example, the following script prints its arguments on successive lines:

for i
do
  echo $i
done

The break and continue commands

The break and continue commands are simple commands that take no arguments. When the shell encounters a break command, it immediately exits the body of the enclosing loop ( while, until, or for) command. When the shell encounters a continue command, it immediately discontinues the current iteration of the loop. If the loop condition permits, other iterations may occur; otherwise the loop is exited.

Periscope: A Useful Networking Script

Suppose you have a free email account such as that provided by Yahoo! You're traveling and find yourself in a remote location with web access. However, you're unable to access files on your home machine or check email that has arrived there. This is a common circumstance, especially if your business requires that you travel.

If your home computer runs Windows, you're pretty much out of luck. You'll find it extraordinarily difficult to access your home computer from afar. However, if your home computer runs Linux, gaining access is practically a piece of cake.

In order to show the power of shell scripts, this subsection explains a more complex shell script, periscope. At an appointed time each day, periscope causes your computer (which you must leave powered on) to establish a PPP connection to your ISP, which is maintained for about one hour. This provides you enough time to connect to an ISP from your hotel room or other remote location and then connect via the Internet with your home Linux system, avoiding long-distance charges. Once connected, you have about an hour to view or download mail and perform other work. Then, periscope breaks its PPP connection, which it will reestablish at the appointed time the next day.

Example 13-1 shows the periscope script file, which is considerably larger than any script you've so far encountered in this chapter. Therefore, we'll disassemble the script, explaining it line by line. As you'll see, each line is fairly simple in itself, and the lines work together in a straightforward fashion.

Example 13-1: The Periscope shell script

1  route del default
2  wvdial &
3  sleep 1m
4  ifconfig | mail username@mail.com
5  sleep 1h
6  killall wvdial 
7  sleep 2s
8  killall -9 wvdial 
9  killall pppd 
10  sleep 2s
11  killall -9 pppd 
12  echo "/root/periscope" | at 10:00

Here's the line-by-line analysis of the periscope script:

  1. route del default
  2. This line is perhaps the most complex line of the entire script. The route command is normally issued by the system administrator. You've probably never issued the command yourself, because neat or another network configuration program has issued it on your behalf. The effect of the command is to delete the default network route, if any. The default route is the one along which TCP/IP sends packets when it knows no specific route to their specified destination. It's necessary to delete the default route because the wvdial program, which the script uses to establish its PPP connection, will not override an existing default route.

  3. wvdial &
  4. This line launches the wvdial program. As specified by the ampersand (&), the program will run in the background, so the script continues executing while wvdial starts up and runs.

  5. sleep 1m
  6. This line pauses the script for one minute, giving wvdial time to establish the PPP connection.

  7. ifconfig | mail username@mail.com
  8. This line runs the ifconfig command and mails its output to the specified user (you must replace username@mail.com with your own email address, which you can access remotely).

    The ifconfig command produces output that looks something like this:

    ppp0 Link encap:Point-Point Protocol inet addr:10.144.153.105 P-t-P:10.144.153.52 Mask:255.255.255.0 UP POINTOPOINT RUNNING MTU:552 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 TX packets:0 errors:0 dropped:0 overruns:0

    You'll probably see other sections in the output that describe your Ethernet interface (eth0) and a loopback device (lo). The inet addr given in the command output (10.144.153.105) is the IP address of your computer. By mailing the output to yourself, you provide a simple way to discover your computer's IP address, which is likely to be different each time it connects to your ISP.

  9. sleep 1h
  10. This line causes the script to pause for an interval of one hour. You can easily change this interval to something more appropriate to your own needs.

  11. killall wvdial
  12. Now that the connection interval has elapsed, the last line terminates all executing instances of the wvdial program.

    TIP:   Appendix D briefly describes the killall command and other possibly unfamiliar commands employed in this script.

  13. sleep 2s
  14. The script then pauses for two seconds, to ensure that wvdial has completely terminated.

  15. killall -9 wvdial
  16. Under some circumstances, a program will ignore a termination request. This line deals with this possibility by sending a special code that compels a reluctant program to terminate without further delay.

  17. killall pppd
  18. Behind the scenes, wvdial launches a program known as pppd, which actually establishes and manages the PPP connection. Another killall command is designed to terminate pppd if wvdial has failed to do so.

  19. sleep 2s
  20. Again, the script pauses for a few seconds. This time, it does so to ensure that pppd has completely terminated.

  21. killall -9 pppd
  22. And, again, the script uses the - 9 option to specify that any remaining instances of pppd should terminate immediately.

  23. echo "/root/periscope" | at 10:00
  24. Finally, the script uses the at command to schedule itself for execution at 10:00 tomorrow. The at command reads one or more commands from its standard input and executes them at the time specified as an argument.

To try the script for yourself, you must have installed the wvdial program, as explained in Chapter 10. Place the script in the file /root/periscope. Of course, you'll probably want to customize the script to specify an appointment time and duration of your own choosing. To start periscope, log in as root and issue the command:

(echo "/root/periscope" | at 10:00)&

The parentheses cause the & operator to apply to the entire command, not just the at. When 10:00 a.m. (or any other time you specify) comes around, your Linux system should obediently dial your ISP and maintain the connection for the specified interval of time.

Using Periscope

At the appointed time, fire up your computer and access your email account. You should find a mail message that contains the ifconfig output giving your computer's current IP address. Now you can use telnet or an ssh client--your choice corresponds to the server you're running on your Linux system--to contact your computer and work for the remainder of the specified connection time. At the end of the connection time, your Linux system will sever its PPP connection and begin counting down until it's again time to connect.


1. American Sign Language, used to communicate with those who have a hearing impairment, is a much richer language than American Indian sign language. Unfortunately, programmers have not yet risen to the challenge of creating graphical user interfaces that are equally sophisticated.

Back to: Learning Red Hat Linux, 2nd Edition


oreilly.com Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies | Privacy Policy

© 2001, O'Reilly & Associates, Inc.
webmaster@oreilly.com