Chapter 9. Controlling Processes

Shell scripts were designed to run commands. Up to this point, all the scripts in the book have launched various commands, but all in isolation. The most you've seen so far is piping commands to connect the output of one command to the input of another. But the commands run from the scripts do not provide data back to the scripts, other than through writing data to a file. To make processes better fit into shell scripts, you need the capability to start and stop processes, as well as capture the output of processes into shell variables.

This chapter delves into processes and shows how you can launch and control processes from your scripts, including:

  • Exploring the processes running on your system

  • Launching processes in the foreground and the background

  • Using command substitution to set variables from commands

  • Checking the return codes of processes

Exploring Processes

There is a lot of terminology associated with processes. Don't be daunted; it's easier to understand when you see it in action than to explain.

Simply put, a process is a running program. A program is a file on disk (or other storage) that can be executed. Most programs are compiled into the binary format required by the processor chip and operating system. For example, the ls command is a program, compiled for a particular system. An ls program compiled for Linux on a Pentium system will not run in Windows, even on the same computer and processor chip. That's because the operating system defines the format of executable binary files.

A command is a program that is part of the operating system. For example, ls is a command in Linux and Windows systems, while format.exe is a command in Windows.

The act of making a program stored on disk into a process is called launching or running. The operating system reads the program file on disk, creates a new process, and loads the program into that process. Some operating systems allow for multiple processes to run from the same program. Other operating systems impose a limit, such as allowing only one instance of a program to run at any given time.

There are a lot of differences between operating systems and how they handle processes. Luckily, shells abstract a lot of the details, making for a more consistent view.

Checking Process IDs

When the operating system launches a process, it gives the process and ID. You can view this ID if you list the running processes with the ps command on Unix and Linux systems, or use the Task Manager in Windows. Figure 9-1 shows the Windows XP Task Manager.

Figure 9-1

Figure 9.1. Figure 9-1

In Figure 9-1, each process has a process ID, or PID. Each process ID uniquely identifies one process. The process ID is the number you need if you want to control or terminate the process.

Isn't it odd that the main way you interact with a running process is to terminate it?

The ps command lists the active processes. For example:

$ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Oct08 ?        00:00:05 init [5]
root         2     1  0 Oct08 ?        00:00:00 [ksoftirqd/0]
root         3     1  0 Oct08 ?        00:00:02 [events/0]
root         4     3  0 Oct08 ?        00:00:00 [kblockd/0]
root         6     3  0 Oct08 ?        00:00:00 [khelper]
root         5     1  0 Oct08 ?        00:00:00 [khubd]
root         7     3  0 Oct08 ?        00:00:10 [pdflush]
root        10     3  0 Oct08 ?        00:00:00 [aio/0]
root         9     1  0 Oct08 ?        00:00:10 [kswapd0]
root       117     1  0 Oct08 ?        00:00:00 [kseriod]
root       153     1  0 Oct08 ?        00:00:04 [kjournald]
root      1141     1  0 Oct08 ?        00:00:00 [kjournald]
root      1142     1  0 Oct08 ?        00:00:08 [kjournald]
root      1473     1  0 Oct08 ?        00:00:00 syslogd -m 0
root      1477     1  0 Oct08 ?        00:00:00 klogd -x
rpc       1505     1  0 Oct08 ?        00:00:00 portmap
rpcuser   1525     1  0 Oct08 ?        00:00:00 rpc.statd
root      1552     1  0 Oct08 ?        00:00:00 rpc.idmapd
root      1647     1  0 Oct08 ?        00:00:00 /usr/sbin/smartd
root      1657     1  0 Oct08 ?        00:00:00 /usr/sbin/acpid
root      1858     1  0 Oct08 ?        00:00:00 /usr/sbin/sshd
root      1873     1  0 Oct08 ?        00:00:00 xinetd -stayalive -pidfile /var
root      1892     1  0 Oct08 ?        00:00:01 sendmail: accepting connections
smmsp     1901     1  0 Oct08 ?        00:00:00 sendmail: Queue runner@01:00:00
root      1912     1  0 Oct08 ?        00:00:00 gpm -m /dev/input/mice -t imps2
root      1923     1  0 Oct08 ?        00:00:00 crond
xfs       1945     1  0 Oct08 ?        00:00:01 xfs -droppriv -daemon
daemon    1964     1  0 Oct08 ?        00:00:00 /usr/sbin/atd
dbus      1983     1  0 Oct08 ?        00:00:00 dbus-daemon-1 --system
root      1999     1  0 Oct08 ?        00:00:00 mdadm --monitor --scan
root      2017     1  0 Oct08 tty1     00:00:00 /sbin/mingetty tty1
root      2018     1  0 Oct08 tty2     00:00:00 /sbin/mingetty tty2
root      2024     1  0 Oct08 tty3     00:00:00 /sbin/mingetty tty3
root      2030     1  0 Oct08 tty4     00:00:00 /sbin/mingetty tty4
root      2036     1  0 Oct08 tty5     00:00:00 /sbin/mingetty tty5
root      2042     1  0 Oct08 tty6     00:00:00 /sbin/mingetty tty6
root      2043     1  0 Oct08 ?        00:00:00 /usr/bin/gdm-binary -nodaemon
root      2220  2043  0 Oct08 ?        00:00:25 /usr/bin/gdm-binary -nodaemon
root      2231  2220  0 Oct08 ?        05:52:20 /usr/X11R6/bin/X :0 -audit 0 -au
root      2805     1  0 Oct08 ?        00:00:00 /sbin/dhclient −1 -q -lf /var/li
root     18567     3  0 Oct18 ?        00:00:09 [pdflush]
root     20689     1  0 Nov03 ?        00:00:00 /usr/libexec/bonobo-activation-s
ericfj   22282     1  0 Nov04 ?        00:00:00 /usr/libexec/bonobo-activation-s
ericfj   25801  2220  0 Nov06 ?        00:00:02 /usr/bin/gnome-session
ericfj   25849 25801  0 Nov06 ?        00:00:00 /usr/bin/ssh-agent /etc/X11/xinit
ericfj   25853     1  0 Nov06 ?        00:00:01 /usr/libexec/gconfd-2 5
ericfj   25856     1  0 Nov06 ?        00:00:00 /usr/bin/gnome-keyring-daemon
ericfj   25858     1  0 Nov06 ?        00:02:12 metacity --sm-save-file 10940799
ericfj   25860     1  0 Nov06 ?        00:00:03 /usr/libexec/gnome-settings-daem
ericfj   25865  1873  0 Nov06 ?        00:00:08 fam
ericfj   25879     1  0 Nov06 ?        00:00:04 xscreensaver -nosplash
ericfj   25888     1  0 Nov06 ?        00:00:13 gnome-panel --sm-config-prefix
/ericfj   25890     1  0 Nov06 ?        00:00:28 magicdev --sm-config-prefix /mag
ericfj   25895     1  0 Nov06 ?        00:00:39 nautilus --sm-config-prefix /nau
ericfj   25909     1  0 Nov06 ?        00:00:03 eggcups --sm-config-prefix /eggc
ericfj   25912     1  0 Nov06 ?        00:00:00 /usr/libexec/gnome-vfs-daemon -
ericfj   25914     1  0 Nov06 ?        00:00:10 gnome-terminal --sm-config-prefi
ericfj   25939     1  0 Nov06 ?        00:00:01 /usr/bin/pam-panel-icon --sm-cli
root     25944 25939  0 Nov06 ?        00:00:00 /sbin/pam_timestamp_check -d roo
ericfj   25946     1  0 Nov06 ?        00:00:00 /usr/libexec/mapping-daemon
ericfj   25948     1  0 Nov06 ?        00:00:04 /usr/libexec/nautilus-throbber
ericfj   25949 25914  0 Nov06 ?        00:00:00 gnome-pty-helper
ericfj   25950 25914  0 Nov06 pts/92   00:00:00 bash
ericfj   25959 25914  0 Nov06 pts/93   00:00:00 bash
ericfj   25962 25914  0 Nov06 pts/94   00:00:00 bash
ericfj   26007     1  0 Nov06 ?        00:00:02 /usr/libexec/clock-applet --oaf-
ericfj   26009     1  0 Nov06 ?        00:00:01 /usr/libexec/notification-area-a
ericfj   26011     1  0 Nov06 ?        00:00:01 /usr/libexec/mixer_applet2 --oaf
ericfj   26018     1  0 Nov06 ?        00:00:28 /usr/libexec/wnck-applet --oaf-a
ericfj   26020     1  0 Nov06 ?        00:00:04 /usr/libexec/wireless-applet --o
ericfj   26022     1  0 Nov06 ?        00:00:02 /usr/libexec/gweather-applet-2
ericfj   26025 25950  0 Nov06 pts/92   00:00:00 /bin/sh /home2/ericfj/bin/favs
ericfj   26026 26025  0 Nov06 pts/92   00:00:32 xmms /home2/ericfj/multi/mp3/fav
root     26068     1  0 Nov06 ?        00:00:00 [usb-storage]
root     26069     1  0 Nov06 ?        00:00:00 [scsi_eh_12]
ericfj   26178     1  0 Nov06 ?        00:00:00 /bin/sh /usr/lib/firefox-0.9.3/f
ericfj   26188 26178  0 Nov06 ?        00:00:00 /bin/sh /usr/lib/firefox-0.9.3/r
ericfj   26193 26188  0 Nov06 ?        00:07:47 /usr/lib/firefox-0.9.3/firefox-b
root     26232 25950  0 Nov06 pts/92   00:00:00 su
root     26235 26232  0 Nov06 pts/92   00:00:00 bash
ericfj   27112     1  0 Nov06 ?        00:00:00 /usr/bin/artsd -F 10 -S 4096 -s
root     27742     1  0 04:08 ?        00:00:00 cupsd
ericfj    8585     1  0 07:51 ?        00:01:03 /usr/lib/ooo-1.1/program/soffice
ericfj    8604  8585  0 07:51 ?        00:00:00 /usr/lib/ooo-1.1/program/getstyl
ericfj    8615     1  0 07:53 ?        00:00:09 gedit file:///home2/ericfj/writi
ericfj    9582     1  0 19:22 ?        00:00:03 /usr/bin/esd -terminate -nobeeps
ericfj    9621 25962  0 19:37 pts/94   00:00:00 ps -ef

On most modern operating systems, you will find a lot of processes running at any given time.

Note that the options to the ps command to view all processes are either -ef or -aux, depending on the version of Unix. Berkeley-derived versions of Unix such as Mac OS X tend to use -aux, and System V–based versions of Unix tend to use -ef.

Linux systems support both types of options.

With Bourne shell scripts, a special variable, $$, holds the process ID, or PID, of the current process—that is, the process running your script. Note that this process is most likely a running instance of /bin/sh.

Another special variable, $!, holds the PID of the last command executed in the background. If your script has not launched any processes in the background, then $! will be empty.

Reading the /proc File System

In addition to the normal process listings, Linux systems support a special file system called /proc. The /proc file system holds information on each running process as well as hardware-related information on your system. The /proc file system started out holding just process information. Now it holds all sorts of operating system and hardware data.

The neat thing about the /proc file system is that it appears to be a normal directory on disk. Inside /proc, you'll find more directories and plain text files, making it easy to write scripts. Each process, for example, has a directory under /proc. The directory name is the process ID number.

The /proc file system holds more than information on processes. It also contains information on devices connected on USB ports, system interrupts, and other hardware-related statuses.

A more interesting process has more open file descriptors. For example:

$ ls -CFl /proc/11751/fd
total 25
lr-x------  1 ericfj ericfj 64 Nov  8 20:09 0 -> /dev/null
l-wx------  1 ericfj ericfj 64 Nov  8 20:09 1 -> pipe:[9645622]
lr-x------  1 ericfj ericfj 64 Nov  8 20:09 10 -> pipe:[9710243]
l-wx------  1 ericfj ericfj 64 Nov  8 20:09 11 -> pipe:[9710243]
lrwx------  1 ericfj ericfj 64 Nov  8 20:09 12 -> socket:[9710244]
lrwx------  1 ericfj ericfj 64 Nov  8 20:09 13 -> socket:[9710250]
lrwx------  1 ericfj ericfj 64 Nov  8 20:09 14 -> socket:[9710252]
lrwx------  1 ericfj ericfj 64 Nov  8 20:09 15 -> socket:[9710255]
lrwx------  1 ericfj ericfj 64 Nov  8 20:09 16 -> socket:[9710260]
lrwx------  1 ericfj ericfj 64 Nov  8 20:09 17 -> socket:[9710256]
lr-x------  1 ericfj ericfj 64 Nov  8 20:09 18 -> pipe:[9710402]
lrwx------  1 ericfj ericfj 64 Nov  8 20:09 19 -> socket:[9710284]
l-wx------  1 ericfj ericfj 64 Nov  8 20:09 2 -> pipe:[9645622]
l-wx------  1 ericfj ericfj 64 Nov  8 20:09 20 -> pipe:[9710402]
lr-x------  1 ericfj ericfj 64 Nov  8 20:09 21 -> pipe:[9710403]
l-wx------  1 ericfj ericfj 64 Nov  8 20:09 22 -> pipe:[9710403]
lrwx------  1 ericfj ericfj 64 Nov  8 20:09 23 -> socket:[9710404]
lrwx------  1 ericfj ericfj 64 Nov  8 20:09 24 -> socket:[9710407]
lrwx------  1 ericfj ericfj 64 Nov  8 20:09 3 -> socket:[9710237]
lr-x------  1 ericfj ericfj 64 Nov  8 20:09 4 -> pipe:[9710240]
l-wx------  1 ericfj ericfj 64 Nov  8 20:09 5 -> pipe:[9710240]
lr-x------  1 ericfj ericfj 64 Nov  8 20:09 6 -> pipe:[9710241]
l-wx------  1 ericfj ericfj 64 Nov  8 20:09 7 -> pipe:[9710241]
lr-x------  1 ericfj ericfj 64 Nov  8 20:09 8 -> pipe:[9710242]
l-wx------  1 ericfj ericfj 64 Nov  8 20:09 9 -> pipe:[9710242]

This process is using quite a number of network sockets and pipes. This is for a GNOME text editor, gedit.

Explore the /proc file system to see what you can find. Once you find some interesting files, you can use the cat command to view the file contents. For example:

$ cat /proc/11751/environ
SSH_AGENT_PID=11135HOSTNAME=kirkwallTERM=dumbSHELL=/bin/bashHISTSIZE=1000QTDIR=/u
sr/lib/qt3.3USER=ericfjLS_COLORS=SSH_AUTH_SOCK=/tmp/sshPwg11087/agent.110
87KDEDIR=/usrPATH=/usr/kerberos/bin:/usr/local/bin:/usr/bin:/bin:/usr/X11R6/b
in:/home2/ericfj/bin:/usr/java/j2sdk1.4.1_03/bin:/opt/jext/binDESKTOP_S
ESSION=defaultMAIL=/var/spool/mail/ericfjPWD=/home2/ericfjINPUTRC=/etc/in
putrcLANG=en_US.UTF-8GDMSESSION=defaultSSH_ASKPASS=/usr/libexec/openssh/gn
ome-ssh-askpassSHLVL=1HOME=/home2/ericfjLOGNAME=ericfjLESSOPEN=|usr/bin/less
pipe.sh%sDISPLAY=:0.0G_BROKEN_FILENAMES=1XAUTHORITY=/home2/ericfj/.Xau
thorityGTK_RC_FILES=/etc/gtk/gtkrc:/home2/ericfj/.gtkrc-1.2-gnome2SESSION_MANAGER=local/kirkwall:/tmp/.ICE-unix/11087GNOME_KEYRI
NG_SOCKET=/tmp/keyring-9XCKKR/socketGNOME_DESKTOP_SESSION_ID=Default

If you stare at this output long enough (with your eyes in a slight squint), you can see a set of environment variables. The problem is that the output is all mashed together.

Killing Processes

Armed with a process ID, you can kill a process. Typically, processes end when they are good and ready. For example, the ls command performs a directory listing, outputs the results, and then exits. Other processes, particularly server processes, remain around for some time. And you may see a process that remains running when it no longer should. This often happens with processes that get stuck for some reason and fail to exit at the expected time.

Stuck is a technical term for when something weird happens in software, and the program won't do what it should or stop doing what it shouldn't. This is the software equivalent of when your mom says to you that "I put the thing in the thing and now it won't come out," referring to a CD-ROM lovingly shoved into the gap between two optical drives, and the CD-ROM would apparently not eject.

Ironically, in Unix and Linux systems, the kill command merely sends a signal to a process. The process typically decides to exit, or commit suicide, when it receives a signal. Thus, kill doesn't really kill anything. It is more of an accessory to the crime. Think of this as a "Manchurian Candidate" kind of event, as most commands are programmed to commit suicide on a signal.

The kill command needs the process ID of the process to signal as a command-line argument. You can use command-line options to determine which kind of signal to send. In most cases, you want to use the −9 option, which sends a kill signal (known as SIGKILL) to the process. For example, to kill a process with the ID of 11198, use the following command:

$ kill −9 11198

You need to own the process or be logged in as the root user to kill a process. The online documentation for the kill command can tell you other signals. Not all commands support all signals.

Launching Processes

Most often, your scripts will launch processes instead of kill them. With the rich command set available, your scripts can perform a lot of work by running one command after another.

The actual task of launching processes differs by operating system. For example, in Windows, you need to call CreateProcess, a Win32 API call. In Unix and Linux, you can call fork and exec. The fork call clones your current process, and exec launches a process in a special way, covered following.

Fortunately, the shell hides some of these details for you. Even so, you still need to make a number of choices regarding how you want to launch processes.

The following sections describe four such ways:

  • Running commands in the foreground

  • Running commands in the background

  • Running commands in subshells

  • Running commands with the exec command

Running Commands in the Foreground

To run a command from within your scripts, simply place the command in your script. For example:

ls

This tells the script to execute the command and wait for the command to end. This is called running the command in the foreground. You've been doing this since Chapter 2.

The shell treats any extra elements on the same line as the command as options and arguments for the command. For example:

ls -l /usr/local

Again, this is what you have been adding to your scripts since Chapter 2. The key point is that the shell runs the command in the foreground, waiting for the command to exit before going on.

Running Commands in the Background

Place an ampersand character, &, after a command to run that command in the background. This works on the command line. You can also use this technique in your shell scripts.

This can work for any command, but commands don't work well if run in the background if:

  • They send output to stdout, unless you redirect stdout for the command.

  • They need to read from stdin to get input from the user, unless you redirect stdin for the command.

  • Your script needs to interact with the command.

Running Commands in Subshells

Placing a set of commands inside parentheses runs those commands in a subshell, a child shell of the current shell. For example:

( cd /usr/local/data; tar cf ../backup.tar . )

In this case, the subshell runs the cd command to change to a different directory and then the tar command to create an archive from a number of files.)

See Chapter 4 for more on using subshells.

Running Commands with the exec Command

The exec command runs a command or shell script and does not return to your original script. Short for execute, exec runs any script or command, replacing your current script with the process you execute. In other words, the exec command overlays your script with another process, a script, or a command.

This is based on the C language exec call.

The basic syntax for exec follows:

exec command options arguments

To use exec, all you really need to do is prefix a command and all its options and arguments with exec. For example:

$ exec echo "Hello from exec."

This command uses exec to launch the echo command. The effects of exec, however, are more apparent when you call exec in a script, as in the following example.

Launching a process from exec is a handy way to start processes, but you always need to keep in mind that exec does not return. Instead, the new process runs in place of the old. This means your script exits, or more precisely, the exec command overlays the shell running your script with the command exec launches.

Using exec is more efficient than merely launching a process because you free up all the resources used by your script. Note that while your scripts may be small, the shell that runs them, such as /bin/sh, is not a trivial program, so it uses system resources, especially memory.

Typically, scripts call exec near the end. For example, a script may determine which device driver to load based on the operating system or hardware capabilities. Then the script can call exec to run the process that loads the device driver. Whenever you have a similar need for a script that makes some decisions and then launches a process, exec is the call for you.

Don't limit your use of exec to just scripts that decide which program to run. You can find exec useful for scripts that need to set up the environment or locate files for another program to use. In this case, your script needs to find the necessary items, set up the environment using the export command, and then call exec to launch the process.

Capturing the Output of Processes

In addition to being able to launch commands from your scripts, you can run commands and capture the output of those commands. You can set variables from the output of commands or even read whole files into variables. You can also check on the return codes from processes. Return codes are another way processes can output information.

Using Backticks for Command Substitution

Surround a command with backtick characters, `, to execute a command in place. The shell runs the command within the backticks and then replaces the backtick section of the script with the output of the command. This format, often called command substitution, fills in the output from a command. This is similar to variable substitution, where the shell replaces $variable with the value of the variable.

The ` character is often called a backtick. That's because an apostrophe, ', is called a tick, so ` is a backtick. This is similar to /, a slash, and , a backslash.

The basic syntax follows:

`command`

If the command requires options or arguments, place these inside the backtick characters, too. For example:

`command option argument argument2`

You can also use variables as arguments or options to the command. For example:

`command $1 $2 $3`

This backtick syntax proves very, very useful for building up text messages and values in your scripts. For example, you can use the output of the date command in a message output by the echo command. For example:

$ echo "The date is `date`"
The date is Sun Nov 12 16:01:23 CST 2006

You can also use the backtick syntax in for loops, as in the following Try It Out.

Using Parentheses in Place of Backticks

The backtick syntax has been around a long, long time. Newer shell scripts, however, may use an alternate format using parentheses:

$(command)

This syntax is very similar to the syntax for subshells, used in a previous example and described in Chapter 4.

You can also pass arguments and options to the command:

$(command options argument1 argument2)

The two formats, backticks or $( ), are both acceptable for command substitution. The parenthesis format is preferred for the future, but most old scripts use the older backtick format.

Setting Variables from Commands

The basic format for using command substitution with variable settings follows:

variable=`command`

For example:

today=`date`

As you'd expect, you can also pass options or arguments to the command. For example:

scriptname=`basename $0`

The basename command returns the base file name, removing any path information and any file-name extension. For example, the basename command transforms /usr/local/report.txt to report.txt:

$ basename /usr/local/report.txt
report.txt

You can also have basename strip a suffix or file-name extension:

$ basename /usr/local/report.txt .txt
report

In this example, you need to pass the suffix to remove, .txt, as a separate command-line argument.

Many scripts use the basename command as a handy way to get the base name of the script. For example, if $0, the special variable holding the script name, holds /usr/local/bin/backup, you may want to display messages with just the name backup.

Of course, you can place the name inside your script. This is called hard-coding the value. You can also use the basename command with the $0 variable to return the base name of your script.

Performing Math with Commands

Another common use for command substitution is to perform math operations and then set variables to the results. Shell scripts weren't made for performing computations. The moment you try to calculate anything, you see the truth of this. To help get around this problem, you can call on a number of math-related commands, including expr and bc.

The expr command, short for expression evaluator, provides an all-purpose evaluator for various types of expressions, including math expressions.

You can nest backticks within a backtick expression by escaping the interior backticks. To do this, use a backslash prior to the interior backticks, as in the following example.

Reading Files into Variables

You can take advantage of command substitution to read in files into variables. For example, you can use the output of the cat command with command substitution, as shown following:

file_contents=`cat filename`

In addition, you can use the shorthand method by redirecting stdin with no command. For example:

file_contents=`<filename`

Both these constructs will have the same results.

Note

Be careful with this construct. Do not load a huge file into a shell variable. And never load a binary file into a shell variable. Evaluating such a variable can lead to unexpected results, depending on the data in the file. The shell gives you awesome power. Don't use that power to hurt yourself.

Using expr is far easier than most alternatives, but the expr command works best for small expressions. If you have a complicated set of calculations to perform, or you need to work with decimal numbers (often called real or floating-point numbers), your best bet is to use the bc command.

The bc command provides a mini programming language, and you can pass it a number of commands in its special language. To use the bc command, you typically pipe a set of bc commands to the program.

The bc syntax is similar to that of the C language. The most crucial difference from shell scripts is that you do not use a dollar sign, $, in front of variables when you want to access their values. This will create an error in bc.

This pattern of using echo to output commands to the bc command is very common. You can also store the bc commands in a file and redirect bc's stdin to that file. Or you can use a here document in your script, shown in the next example.

You can enter the numbers with as much decimal precision as desired. The bc command won't care. The first command passed to bc is scale, which sets the decimal precision to two decimal places for this example.

The final bc command prints the variable you are interested in, total. The shell will set the result variable to the value of this variable.

If you have a lot of commands, the echo approach will not be convenient. Instead, you can place the commands inside a here document, covered in Chapter 5. Enter the following script and save it under the name math2:

# Using bc for math with a here document.
# Calculates sales tax.

echo -n "Please enter the amount of purchase: "
read amount
echo

echo -n "Please enter the total sales tax: "
read rate
echo

result=$(bc << EndOfCommands
scale=2   /* two decimal places */

tax = ( $amount * $rate ) / 100
total=$amount+tax

print total

EndOfCommands
)

echo "The total with sales tax is: $ $result."

When you run this script, you will see output like the following:

$ sh math2
Please enter the amount of purchase: 100.00

Please enter the total sales tax: 7

The total with sales tax is: $ 107.00.

Another useful math command is dc, the desktop calculator. Despite its name, dc is a command-line tool.

Capturing Program Return Codes

The Bourne shell supports a special variable, $?. The shell sets this variable to the return code of the last process executed. A value of zero means the command was successful—that is, the command exited with a nonerror status. Any other value means the command exited with an error.

You can check the value of $? in your scripts to see whether commands succeeded or not. You can also place commands in if statements as described in Chapter 3.

Summary

You now have a number of ways to run programs and scripts from within your scripts, listed in the following table.

Method

Usage

sh script_file

Runs script_file with the Bourne shell in a separate process.

. script_file

Runs script_file from within the current process.

script_file

Runs script_file if it has execute permissions and is in the command path, the PATH environment variable, or if you provide the full path to the script.

command

Runs command, assumed to be a program, if it has execute permissions and is in the command path, the PATH environment variable, or if you provide the full path to the command executable.

exec script_or_command

Launches the given script or command in place of the current script. This does not return to the original script.

$(script_or_command)

Runs script or command and replaces the construct with the output of the script or command.

`script_or_command`

Runs script or command and replaces the construct with the output of the script or command.

You can add all these methods to your scripting toolbox. More important, when you look at system shell scripts, you'll be able to decipher these constructs.

This chapter covers how to:

  • Find which processes are running on your system using the Windows Task Manager or the Unix and Linux ps command.

  • Determine the process IDs for these processes from the lists of running processes.

  • Query information about processes. In Linux, you can access the special /proc file system to get detailed information on running processes.

  • Kill processes with the kill command.

  • Run a process and check its return value to see if the process succeeded or not.

Shells provide a special variable that holds the process ID, $$. Another special variable, $!, holds the process ID of the last-run process. The special variable $? holds the return code of the last-run process. A value of zero indicates success. Any other value indicates a failure.

The next chapter shows how to create blocks within your scripts, called functions, so that you can reuse common blocks of scripting commands. Functions also allow you to hide a lot of the complexity of scripting.

Exercises

  1. Write a short script that outputs its process number and waits, allowing you to view the contents of the Linux /proc in another shell window. That is, this script should remain at rest long enough that you can view the contents of the process-specific directory for that process.

  2. The tick_for example of using the ls command within a for loop appears, at least at first glance, to be a bit clumsy. Rewrite the tick_for script using a wildcard glob, such as *.txt. Make the output appear the same. Discuss at least one major difference between these scripts.

  3. Rewrite the math1 or math2 script using the expr command instead of the bc command. Note that expr will not work well with floating-point, or decimal, numbers.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.43.26