Thursday, December 11, 2008

BASH job control: fg, bg, jobs, and Ctrl-Z

The BASH shell has a feature called job control that allows you to run and manage multiple processes from a single interactive prompt. In the age of serial connected dumb terminals, this was a killer feature. In the age of multi-homed multi-core Macs, not so killer, but there are certain situations where it can still be useful.

The commands I want to focus on are bg (background), fg (foreground), and jobs. These are all shell builtins, meaning they are commands built into and handled by BASH itself instead of being external programs, like the top program. Examples in this article were run on Mac OS 10.4.10.

Since the advent of the GUI, one solution to running multiple shell commands at a time is to open more than one terminal window. The beauty of this solution is that you can see both display windows at the same time and monitor what is happening. You can also copy and paste information between them.

What about times when you are connected to remote Mac (or Linux or Unix) computer with secure shell? Now, you are back to the single interactive shell and the job control commands can be of assistance. Yes, you can always open another terminal on your computer and establish a second secure shell connection, but that may be more trouble than it's worth.

Here is a scenario where job control can help get your work done a little faster. Again, say you have logged into a remote computer using secure shell and you start a long running job, such as compiling a large program from source, or updating the locate database. As an aside, the locate database came to the Mac from the BSD world where there was no spotlight. It lets you quickly find files based on a partial file name. It doesn't search inside files like spotlight, it only looks at file names. The Mac command for updating the locate database is a C shell script named /usr/libexec/locate.updatedb. It can be run directly:

keithw$ /usr/libexec/locate.updatedb

note: if run as a regular user, it won't be able to scan all directories, so it should really be run as root.

Since it takes a long time to run, you will be unable to run any other commands until it finishes. The script is running in the foreground of the interactive shell. You can force it into the background by using Ctrl-Z. The Ctrl-Z sequence pushes the foreground job into the background, pauses it, and gives control back to you. When I use Ctrl-Z, I see this output:

[1]+ Stopped /usr/libexec/locate.updatedb

This means job number 1 has been stopped and shows the associated command. To check the status of the all background jobs, use the jobs command with the -l (list) switch:

keithw$ jobs -l
[1]+ 4836 Suspended /usr/libexec/locate.updatedb

Now we have control of the terminal back, but the script we started is currently not running. That can be fixed by using the bg command. The bg command tells BASH to run the specified job number in the background. You don't need to give it a job number if there is only one background job. This gets the script running again:

keithw$ bg
[1]+ /usr/libexec/locate.updatedb &
keithw$ jobs -l
[1]+ 4836 Running /usr/libexec/locate.updatedb &

At this point, we have interactive control, the script is running in the background, and we can do something else while it finishes. When it is done, the shell will send us a notice. If you want to bring it back to the foreground, use the fg command. Then, you are back where you started, waiting for it to finish. Generally, you would only use the fg command, if you have multiple background jobs running, and want to interact with one of them. You could theoretically have a text editor, a compile, and a long running script all running as jobs and swap them in and out as needed.

Take a closer look at the output from the jobs command above. Notice the & at the end of the command? You can start a job running in the background by appending an & to the end of the command. Had we done that originally, it would have started in the background and returned interactive control immediately. But for those times when you forget, the job control commands are your friends.

The difference between job number and process ID

I want to make a distinction between jobs and processes. Each running program in the operating system has a process ID (pid). From the shell's point of view, each background process is a job and jobs get assigned numbers starting at 1. The second background process would be job 2. To reference a background process when using the fg or bg commands, use the job number instead of the process ID. For sending operating system signals to a background process, you use the process ID. Going back to the last jobs command, the process ID is printed after the job number, in this case 4836. To kill the background process unconditionally, you would send it the KILL signal using the process ID instead of job number:

keithw$ kill -9 4836