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
| Signal | Number | Meaning |
|---|---|---|
| HUP | 1 | Terminal closed |
| INT | 2 | Ctrl-C interruption |
| TERM | 15 | Polite termination (default of kill) |
| KILL | 9 | Uncatchable, immediate kill |
| EXIT | 0 | Pseudo-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 andset -edoes 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.