Signals, jobs & processes
Run commands in the background, wait on them, and handle Ctrl-C gracefully. The runtime side of shell scripting.
Process lifecycle: each command runs in a child process (fork+exec). $$ is the current shell's PID; $! is the PID of the last backgrounded command. $BASHPID (Bash-only) is the current subshell's actual PID (differs from $$ inside ( ... ) subshells).
Signals list (kill -l): SIGHUP (1, terminal closed / reload config), SIGINT (2, Ctrl-C), SIGQUIT (3), SIGKILL (9, uncatchable), SIGTERM (15, polite terminate), SIGSTOP (19, uncatchable pause), SIGTSTP (20, Ctrl-Z), SIGCHLD (17, child state change). Services often interpret SIGHUP as 'reload config without restart' — a common Unix pattern.
trap semantics: trap 'cmd' SIG1 SIG2... sets; trap - SIG resets; trap (no args) lists. Special pseudo-signals: EXIT (on any exit), ERR (when any command fails, only with set -e-like logic via this trap), DEBUG/RETURN (before/after each command — tracing). EXIT runs even on exit 1 or set -e aborts — ideal for cleanup.
Waiting patterns: wait (all bg jobs), wait $! (specific PID, returns that job's exit code), wait -n (Bash 4.3+, wait for any one job to finish — build work-stealing pools). For concurrency cap: xargs -P N or parallel -j N is simpler than hand-rolled Bash.
SIGKILL cannot be trapped: you cannot clean up on kill -9. Important for robust daemons: write state before risky ops, use atomic file operations, assume any ungraceful death can happen. SIGSTOP (pause) is also uncatchable.
nohup and disown: nohup cmd & makes the process survive the terminal closing (ignores SIGHUP, redirects output to nohup.out). disown -h %1 removes a job from the shell's job table, also HUP-proof. Modern alternative: systemd-run --user for proper service management, or tmux/screen to detach sessions.
Pitfalls in parallel scripts: (i) background jobs share stdout/stderr → interleaved output; redirect per-job: cmd >"log-$i" 2>&1 &. (ii) set -e + & does not abort the script if a backgrounded job fails — check wait $pid; rc=$? per job. (iii) variables set in a subshell (or a piped while read) don't survive back to the parent — restructure with process substitution < <(cmd) or write to a file.
Grounded on https://www.gnu.org/software/bash/manual/bash.html#Job-Control