Processes, Signals & Jobs

Jobs & Background

Every command you run normally blocks the shell until it finishes. A trailing & runs it in the background and hands the prompt back immediately.

Starting a Background Job

sleep 60 &                 # returns at once; prints [1] 48213
echo "the shell is still interactive"

Listing Jobs

jobs                       # [1]+  Running   sleep 60 &
jobs -l                    # include PIDs

The [1] is a job number managed by the shell; the second number is the process id. Job numbers are per-shell and convenient for interactive use; PIDs are global and used in scripts.

fg, bg and Disown

fg %1                      # bring job 1 to the foreground
bg %1                      # resume job 1 in the background
Ctrl-Z                     # suspend the foreground job
kill %1                    # send a signal to job 1
disown %1                  # forget the job so it survives the shell exiting

wait & Parallelism

wait blocks until a background job finishes and returns its exit status. It is the simplest way to run work in parallel.

pids=()
for host in web1 web2 web3; do
  ping -c1 "$host" >/dev/null &
  pids+=($!)
done

fail=0
for pid in "${pids[@]}"; do
  wait "$pid" || fail=1        # collect every status
done
echo "all finished; fail=$fail"
Run several independent downloads, builds or API calls at once with &, then wait. This is the shell's lightweight equivalent of a thread pool.

wait with no argument

for i in {1..5}; do heavy_task "$i" & done
wait                            # wait for all of them
echo "batch complete"

Signals & trap

Common Signals

SignalNumberMeaning
HUP1Terminal closed
INT2Ctrl-C interruption
TERM15Polite termination (default of kill)
KILL9Uncatchable, immediate kill
EXIT0Pseudo-signal: shell exits

trap — Cleanup on Exit

trap runs a command when a signal arrives. The EXIT pseudo-signal guarantees cleanup however the script ends.

tmp="$(mktemp -d)"

cleanup() {
  local status=$?
  rm -rf "$tmp"
  echo "cleaned up (exit was $status)" >&2
}
trap cleanup EXIT

trap 'echo "interrupted"; exit 130' INT
trap 'echo "terminated";  exit 143' TERM
Inside a trap, $? already holds the status that triggered it. Capture it first with local status=$? so your cleanup can report it and still exit "$status".

Ignoring and Resetting

trap '' INT              # ignore Ctrl-C (use with care)
trap - INT               # restore the default action

Subshells

Anything in ( … ), a pipeline stage, or $( … ) runs in a subshell: a child copy of the shell. It inherits variables but its own changes vanish when it exits.

total=0
printf 'a\nb\nc\n' | while read -r _; do
  (( total++ ))          # runs in a subshell — lost!
done
echo "$total"            # 0

# Correct: keep the loop in the current shell
while read -r _; do
  (( total++ ))
done < <(printf 'a\nb\nc\n')
echo "$total"            # 3

Inspecting Processes

ps -ef | grep myscript          # find by name
pgrep -f myscript               # PIDs matching the command line
ps -p $$ -o pid,ppid,cmd        # this shell and its parent

Pitfalls

  • Variables set in a subshell (a pipeline stage, ( ), or $( )) do not reach the parent.
  • Without wait, background failures go unnoticed and set -e does not catch them.
  • Signals cannot be sent to job numbers from another shell — use PIDs.
  • SIGKILL (9) cannot be trapped; never rely on it for graceful shutdown.