Apple Developer Connection
Member Login Log In | Not a Member? Contact ADC

< Previous PageNext Page > Hide TOC

Background Jobs and Job Control

For end-user convenience in the days of text terminals before the advent of tools like screen, the C shell contains job control features that allow you to start a process in the background, then go off and work on other things, bringing these background tasks into the foreground, suspending foreground tasks to complete them later, and continuing these suspended tasks as background tasks.

Over the years, many modern Bourne shell variants including bash and zsh have added similar support. The details of using these commands from the command line is beyond the scope of this document, but in brief, control-Z suspends the foreground process, fg brings a suspended or background job to the foreground, and bg causes a job to begin executing in the background.

Up until this point, all of the scripts have involved a single process operating in the foreground. Indeed, most shell scripts operate in this fashion. Sometimes, though, parallelism can improve performance, particularly if the shell script is spawning a processor-hungry task. For this reason, this section describes programmatic ways to take advantage of background jobs in shell scripts.

Note: All Bourne shell variants support running a command in the background. However, the information obtained about these jobs varies from shell to shell, and pure Bourne shell implementations do not provide this information at all. Thus, when writing scripts that use this functionality, you should be aware that you are significantly limiting the portability of your script when you use BASH-specific or Zsh-specific builtins.

Also note that these examples are specific to BASH. For Zsh, there are subtle differences in the formatting of job status that will require changes to various bits of code. Making this code work in other shells is left as an exercise for the reader.

To start a process running in the background, add an ampersand at the end of the statement. For example:

sleep 10 &

This will start a sleep process running in the background and will immediately return you to the command line. Ten seconds later, the command will finish executing, and the next time you hit return after that, you will see its exit status. Depending on your shell, it will look something like this:

[1]+  Done                    sleep 10

This indicates that the sleep command completed execution. A related feature is the wait builtin. This command causes the shell to wait for a specified background job to complete. If no job is specified, it will wait until all background jobs have finished.

The next example starts several commands in the background and waits for them to finish.

#!/bin/bash
 
function delayprint()
{
    local TIME;
    TIME=$1
    echo "Sleeping for $TIME seconds."
    sleep $TIME
    echo "Done sleeping for $TIME seconds."
}
 
delayprint 3 &
delayprint 5 &
delayprint 7 &
wait

This is a relatively simple example. It executes three commands at once, then waits until all of them have completed. This may be sufficient for some uses, but it leaves something to be desired, particularly if you care about whether the commands succeed or fail.

The following example is a bit more complex. Because the job control mechanism in most Bourne shell variants was designed primarily for interactive use rather than programmatic use, obtaining information about background jobs can be somewhat clumsy. Fortunately, there are few things that a well-written regular expression can’t fix.

Note: Regular expressions are described in “Regular Expressions Unfettered.” For the purposes of this example, it is sufficient to understand that the function jobidfromstring takes a job string like the one shown previously and prints out the first single digit or multi-digit number by itself.

#!/bin/bash
 
function jobidfromstring()
{
        local STRING;
        local RET;
 
        STRING=$1;
        RET="$(echo $STRING | sed 's/^[^0-9]*//' | sed 's/[^0-9].*$//')"
 
        echo $RET;
}
 
function delayprint()
{
        local TIME;
        TIME=$1
        echo "Sleeping for $TIME seconds."
        sleep $TIME
        echo "Done sleeping for $TIME seconds."
}
 
delayprint 3 &
DP3=`jobidfromstring $(jobs %%)`
 
delayprint 5 &
DP5=`jobidfromstring $(jobs %%)`
 
delayprint 7 &
DP7=`jobidfromstring $(jobs %%)`
 
echo "Waiting for job $DP3";
wait %$DP3
 
echo "Waiting for job $DP5";
wait %$DP5
 
echo "Waiting for job $DP7";
wait %$DP7
 
echo "Done."

In this example, you pass a job number argument to the jobs builtin to tell it which job you want to find out information about. Job numbers begin with a percent (%) sign and are normally followed by a number. In this case, however, a second percent sign is used. This is one of a number of special job “numbers” that the shell provides. In this case, it tells the jobs command to output information about the last command that was executed in the background.

The result of this jobs command is a status string like the one shown earlier. This is passed as a series of arguments to the jobidfromstring function, which then prints the job ID by itself, which gets stored into one of the variables DP3, DP5, or DP7.

Finally, the script ends with a series of wait commands. Like the jobs command, these commands can take a job ID. This job ID consists of a percent sign followed by the number (obtained from one of the DP* variables).

The final example shows how to execute a limited number of concurrent jobs in which the order of job completion is not important.

#!/bin/bash
 
MAXJOBS=3
 
 
function jobidfromstring()
{
        local STRING;
        local RET;
 
        STRING=$1;
        RET="$(echo $STRING | sed 's/^[^0-9]*//' | sed 's/[^0-9].*$//')"
 
        echo $RET;
}
 
function spawnjob()
{
        echo $1 | bash
}
 
function clearToSpawn
{
    local JOBCOUNT="$(jobs -r | grep -c .)"
    if [ $JOBCOUNT -lt $MAXJOBS ] ; then
        echo 1;
        return 1;
    fi
 
    echo 0;
    return 0;
}
 
JOBLIST=""
 
COMMANDLIST='ls
echo "sleep 3"; sleep 3; echo "sleep 3 done"
echo "sleep 10"; sleep 10 ; echo "sleep 10 done"
echo "sleep 1"; sleep 1; echo "sleep 1 done"
echo "sleep 5"; sleep 5; echo "sleep 5 done"
echo "sleep 7"; sleep 7; echo "sleep 7 done"
echo "sleep 2"; sleep 2; echo "sleep 2 done"
'
 
IFS="
"
 
for COMMAND in $COMMANDLIST ; do
        while [ `clearToSpawn` -ne 1 ] ; do
                sleep 1
        done
        spawnjob $COMMAND &
        LASTJOB=`jobidfromstring $(jobs %%)`
        JOBLIST="$JOBLIST $LASTJOB"
done
 
IFS=" "
 
for JOB in $JOBLIST ; do
        wait %$JOB
        echo "Job $JOB exited with status $?"
done
 
echo "Done."

Most of the code here is straightforward. It is worth noting, however, that in the function clearToSpawn, the -r flag must be passed to the jobs command to restrict output to currently running jobs. Since this shell script does not actually wait for any process until after it has finished starting processes, the jobs builtin would otherwise return a list that included completed jobs.

!

Warning: While it is tempting to put the while loop inside the clearToSpawn subroutine, if you do so, the program will wait forever. The status of jobs does not get updated by the shell until script execution returns to the main body of the program.

The -c flag to grep causes it to return the number of matching lines rather than the lines themselves, and the period causes it to match on any nonblank lines (those containing at least one character). Thus, the JOBCOUNT variable contains the number of currently running jobs, which is, in turn, compared to the value MAXJOBS to determine whether it is appropriate to start another job or not.



< Previous PageNext Page > Hide TOC


Last updated: 2008-04-08




Did this document help you?
Yes: Tell us what works for you.

It’s good, but: Report typos, inaccuracies, and so forth.

It wasn’t helpful: Tell us what would have helped.
Get information on Apple products.
Visit the Apple Store online or at retail locations.
1-800-MY-APPLE

Copyright © 2007 Apple Inc.
All rights reserved. | Terms of use | Privacy Notice