Bash wait Command with Examples

Learn bash wait command in Linux to manage background processes, synchronization, parallel processing, and real-world scripting patterns.

Last updatedAuthorJoshua JamesRead time8 minGuide typeLinux Commands

The bash wait command solves a specific Linux scripting problem: a script can start work in the background, but it still needs a reliable point where those jobs have finished and their exit statuses are known. Use wait when fixed delays with sleep would be guesswork, especially in backup, test, conversion, and maintenance scripts that run several child processes in parallel.

On Linux, wait is normally a shell built-in, not a standalone binary. Bash adds useful options such as -n, -p, and -f, while POSIX sh keeps a smaller portable form for waiting on known child process IDs.

Understand the Bash wait Command

The GNU Bash manual documents wait as a command that waits for child processes or shell jobs and returns the termination status of the relevant job. The child-process boundary matters: Bash can wait for commands started by the current shell, but it cannot collect the exit status of a random PID from another terminal or service manager.

Start a command in the background with &, save its PID from $!, then wait for that PID when the script needs the result. That pattern is the safest foundation because the script keeps an explicit handle for each background job.

Bash wait Syntax

wait [-fn] [-p variable] [id ...]
FormPurposeExit status behavior
waitWait for all active child processes started by the current shellReturns 0 after all current children finish
wait "$pid"Wait for one saved process IDReturns that child process’s exit status
wait "$pid1" "$pid2"Wait for several specific process IDsReturns the status of the last ID in the list
wait -nReturn when the next background job finishesReturns the status of the job that finished first
wait -n -p done_idStore the PID or jobspec that satisfied wait -nUseful for reporting or cleanup after the first completed job
wait -f "$pid"With job control enabled, wait for termination instead of a status changeMost relevant in interactive shells or scripts that enable job control

Bash accepts process IDs, such as 12345, and job specifications, such as %1. Job specifications come from the current shell’s job table, so they are common in interactive sessions but less predictable in scripts than saved PIDs.

Verify wait Is a Bash Built-in

Use type to confirm that the current shell resolves wait as a built-in command. A Bash built-in has no separate executable path because Bash handles the child-process state internally.

type wait
wait is a shell builtin

For Bash-specific syntax and option support, use the shell’s built-in help instead of searching for an external man page:

help wait | sed -n '1,2p'
wait: wait [-fn] [-p var] [id ...]
    Wait for job completion and return exit status.

wait vs sleep in Bash Scripts

wait and sleep both pause execution, but they answer different questions. sleep 10 asks Bash to pause for ten seconds. wait "$pid" asks Bash to pause until a specific child process exits, whether that takes one second or one hour.

NeedUseReason
Synchronize with a background taskwaitThe script resumes only when the child process exits
Read the child process exit codewait "$pid"Bash returns the waited-for command’s status
Pause between retriessleepNo background job has to finish first
Rate-limit a loopsleepThe delay is the point, not process synchronization

Wait for a Background Process by PID

The $! variable expands to the PID of the most recent background job. Save it immediately after starting the command; another background command will overwrite the value.

#!/usr/bin/env bash
sleep 2 &
process_id=$!

echo "Started background process: $process_id"

if wait "$process_id"; then
    echo "Process finished successfully."
else
    status=$?
    echo "Process failed with status: $status"
fi

The printed PID changes on every run, but the success path should look like this:

Started background process: 2514821
Process finished successfully.

Use wait "$process_id" instead of a fixed sleep delay when the task length varies. The script receives the real exit status and avoids continuing too early or wasting time after the job has already finished.

Wait for All Background Jobs

Running wait with no IDs waits for all active child processes from the current shell. This compact form is useful when the script only needs a barrier before the next step and does not need per-job status reporting.

#!/usr/bin/env bash
sleep 1 &
sleep 2 &
sleep 3 &

echo "Waiting for all background jobs..."
wait
echo "Every background job has finished."
Waiting for all background jobs...
Every background job has finished.

This form returns 0 after all current children finish, even if one child failed earlier and another succeeded later. Use saved PIDs and individual waits when failures must be reported accurately.

Capture Exit Codes from Multiple Jobs

A single wait "$pid1" "$pid2" "$pid3" command waits for every listed PID, but Bash returns the status of the last ID in that list. That can hide an earlier failure, so production scripts should usually wait for each saved PID and record the status immediately.

#!/usr/bin/env bash
pids=()

bash -c 'sleep 1; exit 0' &
pids+=("$!")

bash -c 'sleep 2; exit 4' &
pids+=("$!")

failed=0

for pid in "${pids[@]}"; do
    if wait "$pid"; then
        echo "Process $pid finished successfully."
    else
        status=$?
        echo "Process $pid failed with status $status."
        failed=1
    fi
done

if ((failed)); then
    echo "At least one background job failed."
else
    echo "All background jobs finished successfully."
fi
Process 2514934 finished successfully.
Process 2514935 failed with status 4.
At least one background job failed.

Avoid if ! wait "$pid"; then echo "$?"; fi when you need the child’s real status. The ! operator inverts the command result, so $? inside that branch becomes the status of the inversion, not the original child process.

Reference Interactive Jobs with wait

Interactive shells can refer to background jobs by job specification. Start a job, inspect it with jobs -l, then wait for the job number shown in brackets.

sleep 60 &
jobs -l
wait %1
[1]+ 2514978 Running                 sleep 60 &

Job specifications are shell-local. A %1 value from one terminal does not identify a job in another terminal, and it can change as jobs finish or new jobs start. Scripts are more reliable when they save PIDs in variables or arrays.

Use wait -n for the First Completed Job

The -n option returns as soon as one unwaited-for child process exits. Bash added wait -n in Bash 4.3, so scripts that must run on older enterprise systems need a version check or a simpler PID loop.

#!/usr/bin/env bash
bash -c 'sleep 2; exit 0' &
fast_pid=$!

bash -c 'sleep 4; exit 0' &
slow_pid=$!

if wait -n -p finished_pid "$fast_pid" "$slow_pid"; then
    status=0
else
    status=$?
fi

echo "First completed PID: $finished_pid"
echo "Exit status: $status"

for pid in "$fast_pid" "$slow_pid"; do
    if [[ $pid != "$finished_pid" ]]; then
        wait "$pid"
    fi
done
First completed PID: 2515036
Exit status: 0

The -p option stores the PID or jobspec that satisfied wait -n in the named variable. Bash added -p in Bash 5.1, and it is useful only with -n because normal wait already knows which explicit PID it is waiting for.

Check the installed Bash version before using newer options in scripts that run across many Linux distributions:

bash --version | head -n 1
GNU bash, version 5.3.9(1)-release (x86_64-pc-linux-gnu)

Limit Parallel Jobs with wait -n

Unbounded background loops can overload CPU, memory, disk, or remote services. Use wait -n as a simple throttle: when the script reaches the job limit, it waits for one job to finish before starting another.

#!/usr/bin/env bash
max_jobs=2
running=0

for item in alpha beta gamma delta; do
    if ((running >= max_jobs)); then
        wait -n
        ((running -= 1))
    fi

    bash -c 'sleep 1; printf "%s done\n" "$1"' _ "$item" &
    ((running += 1))
done

wait
echo "All queued work finished."

The worker lines can appear in a different order because two jobs run at the same time. One possible run looks like this, with the final line appearing only after the last queued job finishes:

alpha done
beta done
gamma done
delta done
All queued work finished.

For file-driven work, combine this pattern with a safe file discovery step. For example, find with -exec can locate matching paths, while a Bash loop controls how many worker processes run at once.

Add a Timeout Around wait

Bash wait has no built-in timeout option. In Bash 5.1 or newer, a common pattern is to start the real task and a timer process, then use wait -n -p to see which one finished first.

#!/usr/bin/env bash
sleep 5 &
task_pid=$!

sleep 2 &
timer_pid=$!

if wait -n -p finished_pid "$task_pid" "$timer_pid"; then
    status=0
else
    status=$?
fi

if [[ $finished_pid == "$timer_pid" ]]; then
    echo "Task timed out."
    kill "$task_pid" 2>/dev/null
    wait "$task_pid" 2>/dev/null || true
    exit_status=124
else
    kill "$timer_pid" 2>/dev/null
    wait "$timer_pid" 2>/dev/null || true
    echo "Task exited with status $status."
    exit_status=$status
fi

exit "$exit_status"
Task timed out.

The timeout branch exits with status 124 so a caller can distinguish a deadline from a successful task. If the command is a single foreground command rather than a broader background-job workflow, the GNU timeout utility is often simpler. Use the timer-plus-wait pattern when the script already manages child PIDs and needs cleanup logic around them.

Use wait -f with Job Control Status Changes

The -f option changes behavior only when Bash job control is enabled. Without -f, wait can return when a job changes status, such as when it is stopped with SIGSTOP. With -f, Bash waits for termination instead.

set -m
sleep 300 &
pid=$!

(sleep 1; kill -STOP "$pid") &
controller_pid=$!

wait "$pid"
echo "wait returned after the stop signal."

kill -CONT "$pid"
kill "$pid"
wait "$pid" 2>/dev/null || true
wait "$controller_pid" 2>/dev/null || true
wait returned after the stop signal.

With -f, Bash keeps waiting until the process terminates. This example stops the job, resumes it after one second, then terminates it so the demonstration cleans itself up:

set -m
sleep 300 &
pid=$!

(sleep 1; kill -STOP "$pid"; sleep 1; kill -CONT "$pid"; sleep 1; kill -TERM "$pid") &
controller_pid=$!

if wait -f "$pid"; then
    status=0
else
    status=$?
fi

echo "wait -f returned after termination with status $status."
wait "$controller_pid" 2>/dev/null || true
wait -f returned after termination with status 143.

The status 143 in this example is the shell’s signal-style status for a process terminated by SIGTERM, calculated as 128 plus signal number 15.

In non-interactive scripts, job control is disabled by default, so -f rarely changes normal script behavior. It is mainly useful for interactive job-control sessions or scripts that explicitly enable job control with set -m.

Use Portable wait in POSIX sh

The POSIX wait utility supports waiting for known process IDs or all known child processes, but it does not define Bash-specific options such as -n, -p, or -f. Portable sh scripts should avoid Bash arrays, [[ ... ]], and wait -n.

#!/bin/sh
sleep 1 &
pid1=$!

sleep 2 &
pid2=$!

wait "$pid1"
status1=$?

wait "$pid2"
status2=$?

printf 'First status: %s\n' "$status1"
printf 'Second status: %s\n' "$status2"

This portable form works across Linux distributions that link /bin/sh to shells such as dash, Bash in POSIX mode, or another POSIX-compatible shell. Use #!/usr/bin/env bash when a script depends on Bash-only features.

Troubleshoot Common wait Command Errors

Most wait failures come from the same root causes: the target PID is not a child of the current shell, the shell is too old for the option, or the script lost the real exit status before saving it.

wait Reports That the PID Is Not a Child

Bash returns status 127 when the PID is invalid or not a child process of the current shell. A typical error looks like this:

bash: wait: pid 999999 is not a child of this shell

Use wait only for processes started by the same shell. To monitor a non-child PID, poll for existence with kill -0, but remember that this only tells you whether the process still exists; it cannot recover the process’s exit code.

while kill -0 "$pid" 2>/dev/null; do
    sleep 1
done

echo "Process $pid no longer exists."

wait -n or wait -p Returns invalid option

wait -n requires Bash 4.3 or newer, and wait -p requires Bash 5.1 or newer. POSIX sh also rejects these options, even on systems where Bash is installed.

bash --version | head -n 1
GNU bash, version 5.3.9(1)-release (x86_64-pc-linux-gnu)

If the script starts with #!/bin/sh, changing the command line is not enough. Either rewrite the script to use portable sequential waits, or change the shebang to Bash when Bash-only options are required.

A Subshell or Pipeline Lost the Child PID

A background job started inside a subshell belongs to that subshell. The parent shell cannot wait for a child it did not start directly.

# The parent shell cannot wait for this background job.
pid=$(bash -c 'sleep 10 & echo "$!"')
wait "$pid"
kill "$pid" 2>/dev/null || true

Launch the background job in the current shell when the parent needs to wait for it:

sleep 10 &
pid=$!
wait "$pid"

The Script Prints the Wrong Failure Code

Save $? immediately after wait fails. Another command, including echo, a test, or the ! negation operator, can replace the status before you print it.

if wait "$pid"; then
    status=0
else
    status=$?
fi

echo "wait status: $status"

jobs and ps Do Not Show the Same Processes

jobs -l shows the current shell’s job table, while ps --ppid $$ shows processes whose parent PID is the current shell. They are useful diagnostics, but they answer slightly different questions.

jobs -l
ps --ppid $$
kill -0 "$pid" 2>/dev/null && echo "Running" || echo "Gone"

Pipe ps output through the grep command only when you need to filter by process name. If the PID never appears as a child of the current shell, wait cannot collect its exit status.

When Not to Use wait

wait is small and reliable when the current shell owns the child processes. Other tools fit better when the job queue, scheduler, or supervisor needs features that Bash does not provide.

ScenarioBetter toolReason
Large queues with retries, progress, and argument replacementGNU Parallel or xargs -PPurpose-built parallel runners handle queue control better than hand-written PID loops
Jobs that should start when system load dropsbatch command in LinuxThe job belongs to the system queue, not the current shell session
Long-running services that should restart after failuresystemd or another supervisorService managers own restart policy, logs, dependencies, and boot behavior
Waiting for a process started elsewherekill -0, service status checks, logs, or IPCwait cannot collect exit status from non-child processes
Sequential scripts with no background jobsNo synchronization commandForeground commands already finish before the next line runs

Conclusion

Background work is synchronized when each child PID is saved, waited for once, and checked before the script continues. Use basic wait for portable shell scripts, wait -n and -p for responsive Bash job handling, and external queue or supervisor tools when the workflow outgrows the current shell.

Share this guide

Help another Linux user troubleshoot faster

Share this guide with someone troubleshooting Linux systems or saving it for later.

Follow LinuxCapable

Want more LinuxCapable guides in Google?

Add LinuxCapable as a preferred source so Google can show more of our fresh Linux tutorials in Top Stories and From your sources when relevant.

Add LinuxCapable as a preferred source on Google
Search LinuxCapable

Need another guide?

Search LinuxCapable for package installs, commands, troubleshooting, and follow-up guides related to what you just read.

Found this guide useful?

Support LinuxCapable to keep tutorials free and up to date.

Buy me a coffeeBuy me a coffee
Before commenting, please review our Comments Policy.
Formatting tips for your comment

You can use basic HTML to format your comment. Useful tags currently allowed in published comments:

You type Result
<code>command</code> command
<strong>bold</strong> bold
<em>italic</em> italic
<blockquote>quote</blockquote> quote block

Got a Question or Feedback?

We read and reply to every comment - let us know how we can help or improve this guide.

Verify before posting: