Shell script parallel execution: Bash background jobs and exit status

Tech reviewed: Deepak Prasad
Shell script parallel execution: Bash background jobs and exit status

To run several independent scripts faster than “one after another”, start each job in the background with &, keep its PID from $!, then wait on each PID and capture $? before another command overwrites it. The parent shell must not exit until every background child has been waited on, otherwise work can be cut off.

Tested the snippets below on Ubuntu 25.04, kernel 6.14.0-37-generic, Bash 5.2.37 (2026-06-14). Timings are wall clock from TIMEFORMAT + time on this host.


Linux run shell script (execute shell script)

To execute shell script files you usually mark them executable and run them with a shell, or pass them explicitly to Bash:

bash
chmod u+x ./mytask.sh
./mytask.sh          # uses shebang line
# or
bash ./mytask.sh     # ignores shebang for the interpreter choice

That is the baseline for running scripts on Linux; the rest of this page assumes each child script is runnable that way.


Bash parallel execution with &, $!, and wait

Sending a command to the background frees the shell to continue immediately. $! is the PID of the last background process Bash started in this shell.

A reliable pattern: start all jobs in a loop, record PID → script name, then wait on each PID and capture $? immediately (before any other command overwrites it):

bash
pidfile=$(mktemp)
trap 'rm -f "$pidfile"' EXIT

for script in workers/*.sh; do
  bash "$script" &
  echo "$! $(basename "$script")" >> "$pidfile"
done

while read -r pid name; do
  wait "$pid"
  rc=$?
  echo "$name exit: $rc"
done < "$pidfile"

On Bash 4+, an associative array avoids the temp file:

bash
declare -A pid_to_name=()
for script in workers/*.sh; do
  bash "$script" &
  pid_to_name[$!]=$(basename "$script")
done
for pid in "${!pid_to_name[@]}"; do
  wait "$pid"
  rc=$?
  echo "${pid_to_name[$pid]} exit: $rc"
done

Either way you get parallel shell behavior with correct exit status per child. Avoid piping echo ... | while read for the wait loop unless you understand subshell rules; reading a PID list from a file or iterating stored keys keeps everything in one shell.


bash parallelize for loop (minimal example)

Three tiny workers sleep different amounts and exit with different codes. Sequential work takes roughly the sum of the sleeps; parallel work takes roughly the maximum sleep.

Workers (adjust paths as needed):

bash
# s1.sh — sleep 2, exit 5
# s2.sh — sleep 3, exit 10
# s3.sh — sleep 4, exit 15

Sequential loop:

bash
for s in s1.sh s2.sh s3.sh; do
  bash "$s"
  rc=$?
  echo "$s exit: $rc"
done

Sample timing and output from this environment:

text
s1.sh exit: 5
s2.sh exit: 10
s3.sh exit: 15
real 9.477 sec

Parallel loop (background + wait per PID):

bash
pidfile=$(mktemp)
for s in s1.sh s2.sh s3.sh; do
  bash "$s" &
  echo "$! $s" >> "$pidfile"
done
while read -r pid name; do
  wait "$pid"
  rc=$?
  echo "$name exit: $rc"
done < "$pidfile"
rm -f "$pidfile"
text
s1.sh exit: 5
s2.sh exit: 10
s3.sh exit: 15
real 4.049 sec

So this parallel loop cut wall time from ~9.5s to ~4s while still printing each script’s real exit code. If you only need “all succeeded” and not per-job reporting, wait with no arguments waits for all background jobs but only leaves you the exit status of the last job you waited for—usually too weak for audits.


Other tools (when Bash loops are not enough)

For embarrassingly parallel command lines, xargs -P n (GNU coreutils) or parallel (GNU parallel) add a process pool and built-in retry/quoting features. They are still shell-style parallelism in spirit, but the article’s Bash wait pattern stays the smallest dependency-free core.


Summary

Shell script parallel execution in Bash means: bash your.sh &, store $!, then wait "$pid" and read $? right away into a variable so echo "$(basename …)" does not clobber the exit code—an easy mistake in the original “sequential status” line. That pattern covers background jobs and per-PID exit codes without extra packages. To run a script in Linux, use chmod +x and ./script or bash script first, then add parallelism when independent jobs justify the complexity.

If you need to avoid overlapping runs of the same script, see check if a script is already running. For process listing context, ps command in Linux is still the usual reference. To measure how long a sequential versus parallel batch actually took on the wall clock, see measure script or command execution time in Bash.

Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with over a decade of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, …

  • Red Hat Certified System Administrator in Red Hat OpenStack
  • Certified Kubernetes Application Developer (CKAD)
  • Red Hat Certified Specialist in Ansible Automation
  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security