Learning the Basics of Shell Scripting in Bash

Learning the Basics of Shell Scripting in Bash

Now that you have seen examples of simple shell scripts, the next few sections provide an overview of Bash programming.

Secret

As you try out simple shell programs, don't name your sample programs test. Linux includes an important program named test (/usr/bin/test). This program tests for various conditions in shell scripts, such as whether or not a file exists and whether or not a file is readable. If you create a sample program named test, some scripts may end up calling your program instead of calling the system's test program.

Writing a Simple Shell Script

Earlier in this chapter, you learn how to place frequently used commands in a file and use the chmod command to make the file executable. Voila-you have a shell script. Just as most Linux commands accept command-line options, a Bash script accepts command-line options. Inside the script, you can refer to the options as $1, $2, and so on. The special name $0 refers to the name of the script itself.

The first line causes Linux to run the /bin/sh program, which subsequently processes the rest of the lines in the script. The name /bin/sh traditionally refers to the Bourne shell-the first UNIX shell. In Linux, /bin/sh is a symbolic link to /bin/bash, which is the executable program for Bash. Therefore, in Linux, Bash runs Bourne shell scripts (Bash happens to be compatible with the Bourne shell).

If you save this simple script in a file named simple, and you make that file executable with the command chmod +x simple, you can run the script as follows:

./simple
This script's name is: ./simple
Argument 1:
Argument 2:

The script file's name appears relative to the current directory, which is represented by a period. Because you have run the script without arguments, the script does not display any arguments.

Now, try running the script with a few arguments, as follows:

./simple "This is one argument." second-argument third
This script's name is: ./simple
Argument 1: This is one argument
Argument 2: second-argument

As this example shows, the shell treats the entire string within double quotation marks as a single argument. Otherwise, the shell uses spaces as separators between arguments on the command line.

Note that this sample script ignores the third argument because the script is designed to print only the first two arguments. The script ignores all arguments after the first two.

Getting an Overview of Bash Programming

Like any programming language, Bash includes the following features:

Variables that store values, including special built-in variables for accessing command-line arguments passed to a shell script and other special values

The capability to evaluate expressions

Control structures that enable you to loop over several shell commands or to execute some commands conditionally

The capability to define functions that can be called in many places within a script. Bash also includes many built-in commands that you can use in any script.

The next few sections illustrate some of Bash's programming features through simple examples. Because you are already running Bash, you can try the examples by typing them at the shell prompt in a terminal window.

Understanding Bash Variables

You define variables in Bash just as you define environment variables. Thus, you might define a variable as follows:

count=12 # note no embedded spaces allowed

To use a variable's value, prefix the variable's name with a dollar sign ($). $PATH, for example, is the value of the variable PATH (yes, the famous PATH environment variable that contains the list of directories to search for any commands the user types). To display the value of the variable count, use the following command:

echo $count

Bash has some special variables for accessing command-line arguments. In a shell script, $0 refers to the name of the shell script. The variables $1, $2, and so on refer to the command-line arguments. The variable $* stores all the command-line arguments as a single variable, and $? contains the exit status of the last command the shell executes.

In addition, you can prompt the user for input and use the read command to read the input into a variable. Following is an example:

echo -n "Enter value: "
read value
echo "You entered: $value."

Insider Insight

The -n option prevents the echo command from automatically adding a new line at the end of the string that it displays.

Writing Shell Functions

You can group a number of shell commands into a function and assign it a name. Later, you can execute that group of commands by using the single name assigned to the function. Here is a simple script that illustrates the syntax of shell functions:

#!/bin/sh
hello() {
echo -n "Hello, "
echo $1 $2
}
hello Jane Doe

When you run this script, it displays the following output:

Hello, Jane Doe

This script defines a shell function named hello. The function expects two arguments; in the body of the function, these arguments are referenced by $1 and $2. The function definition begins with hello()-the name of the function, followed by parentheses. The body of the function is enclosed in curly braces-{...}. In this case, the body uses the echo built-in command (see Table 24-1 for a list of built-in commands).

Using Bash Control Structures

In Bash scripts, the control structures-such as if, case, for, and while-depend on the exit status of a command to decide what to do next. When any command executes, it returns an exit status: a numeric value that indicates whether or not the command has succeeded. By convention, an exit status of zero means the command has succeeded. (Yes, you read it right: zero indicates success.) A nonzero exit status indicates that something has gone wrong with the command.

As an example of using control structures, consider the following script, which makes a backup copy of a file before opening it with the vi text editor:

This script illustrates the syntax of the if-then-else structure and shows how the exit status of the cp command is used by the if structure to determine the next action. If cp returns zero, the script invokes vi to edit the file; otherwise, the script displays a message and exits. By the way, the script names the backup file whose name is the same as that of the original, except for a number sign (#) added at the beginning of the filename.

Insider Insight

Don't forget the final fi that terminates the if structure. Forgetting fi is a common source of errors in Bash scripts.

Bash includes the test command to enable you to evaluate any expression and to use the expression's value as the exit status of the command. Suppose that you want a script that enables you to edit a file only if it exists. Using test, you might write such a script as follows:

#!/bin/sh
if test -f "$1"
then
vi "$1"
else
echo "No such file"
fi

Secret

A shorter form of the test command omits test and places the test command's options in square brackets ([...]). Using this notation, you can write the script that enables you to edit only existing files, as follows:

#!/bin/sh
if [ -f "$1" ]
then
vi "$1"
else
echo "No such file"
fi

The left square bracket ([) is in fact a symbolic link to /usr/bin/test. You can confirm this fact by typing ls -l /usr/bin/[.

Another common control structure is the for loop. The following script adds the numbers 1 through 10:

Save this in a file named confirm, and type chmod +x confirm to make it executable. Then, try it out like this:

./confirm
What should I do -- (Y)es/(N)o/(C)ontinue? [Y] c
CONTINUE

The script displays a prompt and reads the input you type. Your input is stored in a variable named answer. Then the case statement executes a block of code based on the value of the answer variable. For example, when I type c, the following block of commands is executed:

c|C)
echo "CONTINUE"
;;

The echo command causes the script to display CONTINUE.

From this example, you can see that the general syntax of the case command is as follows:

Essentially, the case command begins with the word case and ends with esac. Separate blocks of code are enclosed between the values of the variable, followed by a right parenthesis and terminated by a pair of semicolons (;;).

Insider Insight

Don't forget the final esac that terminates the case structure. Forgetting esac is a common source of errors in Bash scripts.

Taking Stock of Built-in Commands In Bash

Bash has more than 50 built-in commands, including common commands such as cd and pwd, as well as many others that are used infrequently. You can use these built-in commands in any Bash script or at the shell prompt. Appendix A describes many of the built-in commands that you use typically at the shell prompt.

Although this chapter does not have enough space to cover all built-in Bash commands, Table 24-1 describes most of these commands and their arguments. After looking through this information, type help cmd (where cmd is a command's name) to read more about a specific built-in command. For example, to learn more about the built-in command test, type the following:

help test | more
test: test [expr]
Exits with a status of 0 (trueness) or 1 (falseness) depending on
the evaluation of EXPR. Expressions may be unary or binary. Unary
expressions are often used to examine the status of a file. There
are string operators as well, and numeric comparison operators.
File operators:
-b FILE True if file is block special.
-c FILE True if file is character special.
-d FILE True if file is a directory.
-e FILE True if file exists.
-f FILE True if file exists and is a regular file.
-g FILE True if file is set-group-id.
-h FILE True if file is a symbolic link.
-L FILE True if file is a symbolic link.
-k FILE True if file has its `sticky' bit set.
-p FILE True if file is a named pipe.
-r FILE True if file is readable by you.
-s FILE True if file exists and is not empty.
-S FILE True if file is a socket.
-t FD True if FD is opened on a terminal.
-u FILE True if the file is set-user-id.
-w FILE True if the file is writable by you.
-x FILE True if the file is executable by you.
-O FILE True if the file is effectively owned by you.
(... Lines deleted ...)

Where necessary, the online help from the help command includes a considerable amount of detail.

Note that some external programs may have the same name as Bash built-in commands. If you want to run any such external program, you have to specify explicitly the full pathname of that program. Otherwise, Bash executes the built-in command of the same name.

Table 24-1: Summary of Built-in Commands in Bash Shell

Function

Description

. filename [arguments]

Reads and executes commands from the specified file using the optional arguments (same as source)

: [arguments]

Expands the arguments but does not process them

[ expr ]

Evaluates the expression expr and returns zero status if expr is true

alias [name[=value] ...]

Defines an alias or lists all aliases if no arguments provided

bg [job]

Puts the specified job in the background. If no job is specified, it puts the currently executing command in the background.

bind [-m keymap] [-lvd]

Binds a key sequence to a macro [-q name]

break [n]

Exits from a for, while, or until loop. If n is specified, the nth enclosing loop is exited.

builtin builtin_command

Executes a shell built-in command [arguments]

cd [dir]

Changes the current directory to dir or to user's home directory if no arguments provided

command [-pVv] cmd [arg ...]

Runs the command cmd with the specified arguments (ignoring any shell function named cmd)

continue [n]

Starts the next iteration of the for, while, or until loop. If n is specified, the next iteration of the nth enclosing loop is started.

declare [-frxi] [name[=value]]

Declares a variable with the specified name and, optionally, assigns it a value

dirs [-l] [+/-n]

Displays the list of currently remembered directories

echo [-neE] [arg ...]

Displays the arguments on standard output

enable [-n] [-all]

Enables or disables the specified built-in commands [name ...]

eval [arg ...]

Concatenates the arguments and executes them as a command

exec [command [arguments]]

Replaces the current instance of the shell with a new process that runs the specified command

exit [n]

Exits the shell with the status code n

export [-nf] [name[=word]] ...

Defines a specified environment variable and exports it to future processes

fc -s [pat=rep] [cmd]

Reexecutes the command after replacing the pattern pat with rep

fg [jobspec]

Puts the specified job in the foreground. If no job is specified, it puts the most recent job in the foreground.