1. Overview

An effective way to deal with a big time-consuming computing task is to break it into smaller ones. Then, we can run these smaller tasks in parallel as background jobs. In such cases, we may want to wait for these jobs to finish before we proceed. The wait command does exactly this – it blocks execution until one or more child processes end.

In this tutorial, we’ll explore how we can use the wait command.

2. The wait Command

The wait command makes a shell script or terminal session wait for background processes to finish. Notably, the command works only for jobs that were launched in the current shell session.

Furthermore, we can set a list of background processes that we wish to wait for. Otherwise, if we set no options, the command waits for all open background jobs in the current shell session.

The wait command is a shell builtin. Shell builtins are commands created inside the shell, usually for performance reasons. We can verify this with the type command:

$ type -a wait
wait is a shell builtin

The output of the command confirms that wait is indeed a shell builtin.

3. Simple Child Process

To aid in our exploration of wait, let’s write the shell function child which contains the commands that the background processes will execute:

function child() {
  echo child process $BASHPID starting
  sleep 6
  echo child process $BASHPID finished
}

The function first echoes an entry message with its current process ID. Then, it sleeps for six seconds. Finally, it prints an exit message.

Importantly, this child process code is part of all scripts we test below.

4. Calling wait With No Arguments

Let’s create a shell script that will help us test wait. We’ll save the script in a file with the name testwait.sh.

Now, we add the commands for the parent process:

echo parent process $$ starting
child &
sleep 1
child &
wait
echo parent wait finished, exit status $?

Here, the parent process will perform these steps:

  • echo a starting message that contains its process ID
  • create two background processes that execute the child function in the background via the & operator
  • execute the wait command with no options
  • echo an exit message that also contains the wait command’s exit status

Now, it’s time to execute the script and see the result:

$ ./testwait.sh
parent process 100 starting
child process 101 starting
child process 102 starting
child process 101 finished
child process 102 finished
parent wait finished, exit status 0

Although they are slower, all child processes finish before the parent process. So, the wait command indeed blocked the execution of the parent process until all background jobs ended.

Notably, the command returned an exit status of 0.

5. Calling wait for a List of Jobs

Let’s modify our testwait.sh script to test the wait command with a list of jobs. In this scenario, the command won’t wait for all running background processes of the current shell session. Instead, it’ll wait only for those that we specify:

echo parent process $$ starting
child &
pid1=$!
sleep 1
child &
pid2=$!
sleep 1
child &
pid3=$!
wait $pid1 $pid2
echo parent wait finished, exit status $?

Now, there are three background processes that will run in total. In addition, we keep the process ID of each background process in separate shell variables. We do this immediately after the creation of the process, using the $! job identifier, which returns the process ID of the last process started in the session background.

Finally, we call the wait command, with the process ID of the first and the second job only. In other words, we don’t wait for the third process to finish, but only for the first two.

At this point, we’re ready to run our script to see what happens:

$ ./testwait.sh
parent process 100 starting
child process 101 starting
child process 102 starting
child process 103 starting
child process 101 finished
child process 102 finished
parent wait finished, exit status 0
child process 103 finished

As expected, the parent process didn’t wait for the third job to finish. Again, the exit status of the wait command was 0.

6. Waiting for the First Job to Finish

The -n option makes the command wait for the next job out of a batch to finish. Let’s modify our wait call in the previous example:

wait -n $pid1 $pid2 $pid3

Here, the -n option makes the wait command block until one of the three child processes ends. In our case, the first child process will also be the one that finishes first:

$ ./testwait.sh
parent process 100 starting
child process 101 starting
child process 102 starting
child process 103 starting
child process 101 finished
wait finished, exit status 0
child process 102 finished
child process 103 finished

Indeed, we can see in the messages that wait exited after the first child process ended. The exit status value was again 0, which is the exit status of the background process that finished.

7. Exit Status of wait

The wait command returns the exit status of the last child process in the list. For verification, we can terminate the last process with a kill signal. The wait command will return the exit status from kill.

To try this, we’ll add a function in our original script:

function killjob() {
  sleep 1
  kill -9 $1
}

The killjob function sends a kill signal to the process with the supplied PID ($1) after sleeping for one second.

Next, let’s modify the parent process to kill the second child process with killjob:

echo parent process $$ starting
child &
pid1=$!
sleep 1
child &
pid2=$!
killjob $pid2 &
wait $pid1 $pid2
echo parent wait finished, exit status $?

So, after starting two background child processes, we also start the killjob function as a background process. Effectively, it kills the second child process after one second:

$ ./testwait.sh
parent process 100 starting
child process 101 starting
child process 102 starting
child process 101 finished
./testwait.sh: line 19: 102 Killed                  child
parent wait finished, exit status 137

As expected the second child process was terminated by the killjob function. Critically, the exit status of the wait command was 137 which corresponds to the kill signal.

An important note here is that this doesn’t happen when we call wait with no arguments. In that case, the exit status is always 0, even if the last child process ends abnormally.

8. Using Job Identifiers

The wait command also works with job identifiers. Let’s look at some example job identifiers:

  • $! for the last background process
  • %N for the N-th job
  • %- for the last job

Let’s try some of these identifiers.

First, we check how the last background process PID can be used:

$ sleep 10 &
[1] 1407
$ wait $!
[1]+  Done                    sleep 10

The wait command didn’t have any trouble understanding this identifier and wait for the corresponding process.

Next, we can try the job number identifier, as seen in square brackets as a prefix to the PID:

$ sleep 10 &
[1] 1410
$ wait %1
[1]+  Done                    sleep 10

As we expected, the wait command was fully functional with this job identifier. In this example, we’re waiting for the job with internal ID 1 to finish.

9. Conclusion

In this article, we learned about the wait command and how it’s used.

Initially, we looked at examples with no options. Next, we tried the command with a list of background process IDs. Following that, we discussed how we can wait for the first background process to end. Finally, we examined the exit status of wait and how the command works with job identifiers.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.