Introductory Shell Scripting - Scripting - Linux All-in-One For Dummies, 5th Edition (2014)

Linux All-in-One For Dummies, 5th Edition (2014)

Book VII. Scripting

 width=

webextras.eps Visit www.dummies.com/extras/linuxaio for great Dummies content online.

Contents at a Glance

Chapter 1: Introductory Shell Scripting

Chapter 2: Advanced Shell Scripting

Chapter 3: Programming in Linux

Chapter 1. Introductory Shell Scripting

In This Chapter

arrow Trying some simple shell scripts

arrow Discovering the basics of shell scripting

arrow Exploring bash’s built-in commands

Linux gives you many small and specialized commands, along with the plumbing necessary to connect these commands. Take plumbing to mean the way in which one command’s output can be used as a second command’s input. bash (short for Bourne-Again Shell) — the default shell in most Linux systems — provides this plumbing in the form of I/O redirection and pipes. bash also includes features such as the if statement that you can use to run commands only when a specific condition is true, and the for statement that repeats commands a specified number of times. You can use these features of bash when writing programs called shell scripts — task-oriented collections of shell commands stored in a file.

This chapter shows you how to write simple shell scripts, which are used to automate various tasks. For example, when your Linux system boots, many shell scripts stored in various subdirectories in the /etc directory (for example, /etc/init.d) perform many initialization tasks.

Trying Out Simple Shell Scripts

If you’re not a programmer, you may feel apprehensive about programming. But shell scripting (or programming) can be as simple as storing a few commands in a file. In fact, you can have a useful shell program that has a single command.

Shell scripts are popular among system administrators. If you are a system administrator, you can build a collection of custom shell scripts that help you automate tasks you perform often. If a hard drive seems to be getting full, for example, you may want to find all files that exceed some size (say, 1MB) and that have not been accessed in the past 30 days. In addition, you may want to send an e-mail message to all users who have large files, requesting that they archive and clean up those files. You can perform all these tasks with a shell script. You might start with the following find command to identify large files:

find / -type f -atime +30 -size +1000k -exec ls -l {} \; > /tmp/largefiles

This command creates a file named /tmp/largefiles, which contains detailed information about old files taking up too much space. After you get a list of the files, you can use a few other Linux commands — such as sort, cut, and sed — to prepare and send mail messages to users who have large files to clean up. Instead of typing all these commands manually, place them in a file and create a shell script. That, in a nutshell, is the essence of shell scripts — to gather shell commands in a file so that you can easily perform repetitive system administration tasks.

bash scripts, just like most Linux commands, accept 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.

Here’s a typical bash script that accepts arguments:

#!/bin/sh
echo "This script's name is: $0"
echo Argument 1: $1
echo Argument 2: $2

The first line runs 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 most Linux systems, /bin/sh is a symbolic link to /bin/bash, which is the executable program for bash.

Save this simple script in a file named simple and make that file executable with the following command:

chmod +x simple

Now run the script as follows:

./simple

It displays the following output:

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

The first line shows the script’s name. Because you have run the script without arguments, the script displays no values for the arguments.

Now try running the script with a few arguments, like this:

./simple "This is one argument" second-argument third

This time the script displays more output:

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

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

This sample script ignores the third argument because the script is designed to print only the first two arguments.

Exploring the Basics of Shell Scripting

Like any programming language, the bash shell supports 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 these programming features through simple examples. (It’s assumed that you’re already running bash, in which case, you can try the examples by typing them at the shell prompt in a terminal window. Otherwise all you have to do is open a terminal window; bash runs and displays its prompt in that window.)

Storing stuff

You define variables in bash just as you define environment variables. Thus you may 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 ($). For example, $PATH is the value of the variable PATH. (This variable is the famous PATH environment variable that lists all the directories that bash searches when trying to locate an executable file.) 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.

From a bash script, you can prompt the user for input and use the read command to read the input into a variable. Here is an example:

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

When this script runs, the read value command causes bash to read whatever you type at the keyboard and store your input in the variable called value.

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

Calling shell functions

You can group a number of shell commands that you use consistently 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 command to display a line of text.

The last line of the example shows how a shell function is called with arguments. In this case, the hello function is called with two arguments: Jane and Doe. The hello function takes these two arguments and prints a line that says Hello, Jane Doe.

Controlling the flow

In bash scripts, you can control the flow of execution — the order in which the commands are executed — by using special commands such as if, case, for, and while. These control statements use 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.

For example, suppose that you want to make a backup copy of a file before editing it with the vi editor. More importantly, you want to avoid editing the file if a backup can’t be made. Here’s a bash script that takes care of this task:

#!/bin/sh
if cp "$1" "#$1"
then
vi "$1"
else
echo "Failed to create backup copy"
fi

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 command to determine the next action. If cp returns zero, the script uses vi to edit the file; otherwise, the script displays an error message and exits. By the way, the script saves the backup in a file whose name is the same as that of the original, except for a hash mark (#) added at the beginning of the filename.

tip.eps Don’t forget the final fi that terminates the if command. Forgetting fi is a common source of errors in bash scripts.

You can use the test command 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 edits a file only if it already exists. Using test, you can write such a script as follows:

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

A shorter form of the test command is to place the expression in square brackets ([ … ]). Using this shorthand notation, you can rewrite the preceding script like this:

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

Note: You must have spaces around the two square brackets.

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

#!/bin/sh
sum=0
for i in 1 2 3 4 5 6 7 8 9 10
do
sum='expr $sum + $i'
done
echo "Sum = $sum"

This example also illustrates the use of the expr command to evaluate an expression.

The case statement is used to execute a group of commands based on the value of a variable. For example, consider the following script:

#!/bin/sh
echo -n "What should I do -- (Y)es/(N)o/(C)ontinue? [Y] "
read answer
case $answer in
y|Y|"")
echo "YES"
;;
c|C)
echo "CONTINUE"
;;
n|N)
echo "NO"
;;
*)
echo "UNKNOWN"
;;
esac

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

./confirm

When the script prompts you, type one of the characters y, n, or c and press Enter. The script displays YES, NO, or CONTINUE, respectively. For example, here’s what happens when you type c (and then press Enter):

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 you type c, the following block of commands executes:

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:

case $variable in
value1 | value2)
command1
command2
. . . other commands . . .
;;
value3)
command3
command4
. . . other commands . . .
;;
esac

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 closing parenthesis and terminated by a pair of semicolons (;;).

Exploring bash’s built-in commands

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. Table 1-1 describes most of the bash built-in commands and their arguments. After looking through this information, type help command to read more about a specific built-in command. For example, to find out more about the built-in command test, type the following:

help test

Doing so displays the following information:

test: test [expr]
Exits with a status of 0 (true) or 1 (false) 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:
-a FILE True if file exists.
-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.
-G FILE True if the file is effectively owned by your group.
( . . . Lines deleted . . .)

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

Table 1-1 Summary of Built-in Commands in bash Shell

This Function

Does the Following

. filename [arguments]

Reads and executes commands from the specified filename using the optional arguments. (Works the same way as the source command.)

: [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] … ]

Allows one value to equal another. For example, you could set xyz to run bg.

bg [job]

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

break [n]

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

cd [dir]

Changes the current directory to dir.

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, arg … , on standard output.

enable [-n] [-all]

Enables or disables the specified built-in commands.

eval [arg … ]

Concatenates the arguments, arg … , 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. with the given arguments

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]

Re-executes the command after replacing the pattern pat with rep.

fg [jobspec]

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

hash [-r] [name]

Remembers the full pathname of a specified command.

help [cmd … ]

Displays help information for specified built-in commands, cmd… .

history [n]

Displays past commands or past n commands, if you specify a number n.

jobs [-lnp] [ jobspec … ]

Lists currently active jobs.

kill [-s sigspec | -sigspec] [pid | jobspec] … let arg [arg … ]

Evaluates each argument and returns 1 if the last arg is 0.

local [name[=value] … ]

Creates a local variable with the specified name and value (used in shell functions).

logout

Exits a login shell.

popd [+/-n]

Removes the specified number of entries from the directory stack.

pushd [dir]

Adds a specified directory, dir, to the top of the directory stack.

pwd

Prints the full pathname of the current working directory.

read [-r] [name … ]

Reads a line from standard input and parses it.

readonly [-f] [name … ]

Marks the specified variables as read-only so that the variables cannot be changed later.

return [n]

Exits the shell function with the return value n.

set [--abefhkmnptuvxldCHP] [-o option] [arg … ]

Sets various flags.

shift [n]

Makes the n+1 argument $1, the n+2 argument $2, and so on.

times

Prints the accumulated user and system times for processes run from the shell.

trap [-l] [cmd] [sigspec]

Executes cmd when the signal sigspec is received.

type [-all] [-type |-path] name [name … ]

Indicates how the shell interprets each name.

ulimit [-SHacdfmstpnuv [limit]]

Controls resources available to the shell.

umask [-S] [mode]

Sets the file creation mask — the default permission to the mode specified for the files.

unalias [-a] [name … ]

Undefines a specified alias.

unset [-fv] [name … ]

Removes the definition of specified variables.

wait [n]

Waits for a specified process (n represents its PID) to terminate.

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