Comprehensive Reference Guide — Background jobs, signals, traps, process management, and parallel execution
Bash provides comprehensive process and job control for running commands in the background, managing multiple jobs, handling signals, and ensuring clean script termination. These capabilities are essential for writing robust scripts that handle long-running tasks, parallel execution, and graceful shutdown.
Background Jobs: Run processes asynchronously with & and manage with jobs, fg, bg
Process IDs: Track processes with $$, $!, $BASHPID
Signals & Traps: Catch and handle signals for cleanup and control
Wait & Synchronization: Coordinate multiple background processes
| Variable | Description | Example |
|---|---|---|
$$ |
Current shell's PID | echo "Script PID: $$" |
$! |
PID of last background job | cmd &; pid=$! |
$BASHPID |
PID of current Bash process (subshell-aware) | echo $BASHPID |
$PPID |
Parent process PID | echo "Parent: $PPID" |
$? |
Exit status of last command | cmd; echo $? |
${PIPESTATUS[@]} |
Exit statuses of pipeline commands | cmd1 | cmd2; echo ${PIPESTATUS[0]} |
$$ is the PID of the main shell and doesn't change in subshells. $BASHPID gives the actual PID of the current Bash process, including subshells.
| Signal | Number | Description | Default Action |
|---|---|---|---|
SIGHUP |
1 | Hangup (terminal closed) | Terminate |
SIGINT |
2 | Interrupt (Ctrl+C) | Terminate |
SIGQUIT |
3 | Quit (Ctrl+\) | Core dump |
SIGKILL |
9 | Kill (cannot be caught) | Terminate |
SIGTERM |
15 | Terminate (default kill) | Terminate |
SIGSTOP |
19 | Stop (cannot be caught) | Stop process |
SIGTSTP |
20 | Terminal stop (Ctrl+Z) | Stop process |
SIGCONT |
18 | Continue if stopped | Continue |
SIGCHLD |
17 | Child process terminated | Ignore |
SIGUSR1 |
10 | User-defined signal 1 | Terminate |
SIGUSR2 |
12 | User-defined signal 2 | Terminate |
| Signal | Description |
|---|---|
EXIT |
Script exit (any reason) |
ERR |
Command returns non-zero (with set -e) |
DEBUG |
Before each command |
RETURN |
Function or sourced script returns |
Running commands in the background and tracking their PIDs.
#!/bin/bash
# Background jobs and process IDs
echo "=== Process ID Variables ==="
echo "Script PID ($$): $$"
echo "Parent PID (PPID): $PPID"
echo "Bash PID (BASHPID): $BASHPID"
echo ""
echo "=== $$ vs \$BASHPID in Subshells ==="
echo "Main shell: $$ / $BASHPID"
(echo "Subshell: $$ / $BASHPID")
echo ""
echo "=== Run Background Job ==="
# Start a background job
sleep 5 &
bg_pid=$!
echo "Started background sleep, PID: $bg_pid"
# Check if running
if kill -0 $bg_pid 2>/dev/null; then
echo "Process $bg_pid is running"
fi
# Kill it
kill $bg_pid
echo "Killed process $bg_pid"
echo ""
echo "=== Multiple Background Jobs ==="
# Start multiple jobs
pids=()
for i in {1..3}; do
sleep $((i * 2)) &
pids+=($!)
echo "Started job $i with PID $!"
done
echo ""
echo "All PIDs: ${pids[*]}"
# List jobs
echo ""
echo "Jobs:"
jobs -l
# Clean up
for pid in "${pids[@]}"; do
kill $pid 2>/dev/null
done
echo ""
echo "=== Check Exit Status ==="
# Run command and check
ls /nonexistent 2>/dev/null
echo "ls exit status: $?"
(exit 42)
echo "Subshell exit status: $?"
echo ""
echo "=== Pipeline Exit Statuses ==="
# PIPESTATUS array
false | true | false
echo "Pipeline statuses: ${PIPESTATUS[*]}"
echo " First: ${PIPESTATUS[0]}"
echo " Second: ${PIPESTATUS[1]}"
echo " Third: ${PIPESTATUS[2]}"
Using wait to synchronize with background jobs.
#!/bin/bash
# Wait for background processes
echo "=== Basic Wait ==="
# Start background job
sleep 2 &
pid=$!
echo "Started sleep (PID: $pid), waiting..."
wait $pid
status=$?
echo "Process $pid finished with status: $status"
echo ""
echo "=== Wait for Multiple Jobs ==="
# Start multiple jobs
pids=()
echo "Starting 3 jobs..."
for i in 1 2 3; do
(sleep $i; echo "Job $i done" >&2; exit $i) &
pids+=($!)
done
echo "PIDs: ${pids[*]}"
echo "Waiting for all to complete..."
# Wait for all
wait
echo "All jobs complete"
echo ""
echo "=== Wait for Specific Jobs ==="
# Start jobs with different durations
(sleep 3; exit 0) &
pid1=$!
(sleep 1; exit 0) &
pid2=$!
(sleep 2; exit 0) &
pid3=$!
echo "Started PIDs: $pid1, $pid2, $pid3"
# Wait for each and get status
wait $pid2
echo "PID $pid2 (1s) finished: $?"
wait $pid3
echo "PID $pid3 (2s) finished: $?"
wait $pid1
echo "PID $pid1 (3s) finished: $?"
echo ""
echo "=== Wait with Timeout Pattern ==="
wait_with_timeout() {
local pid=$1
local timeout=$2
local elapsed=0
while kill -0 $pid 2>/dev/null; do
if ((elapsed >= timeout)); then
echo "Timeout after ${timeout}s, killing $pid"
kill $pid
return 1
fi
sleep 1
((elapsed++))
done
wait $pid
return $?
}
# Test timeout
sleep 10 &
pid=$!
echo "Started long job (PID: $pid)"
wait_with_timeout $pid 3
echo "Result: $?"
echo ""
echo "=== Collect All Exit Statuses ==="
declare -A results
for i in {1..3}; do
(sleep $((RANDOM % 3)); exit $((RANDOM % 2))) &
results[$!]="pending"
done
echo "Started jobs: ${!results[*]}"
for pid in "${!results[@]}"; do
wait $pid
results[$pid]=$?
done
echo "Results:"
for pid in "${!results[@]}"; do
echo " PID $pid: exit ${results[$pid]}"
done
Catching signals and performing cleanup on exit.
#!/bin/bash
# Signal handling with trap
echo "=== Basic EXIT Trap ==="
cleanup() {
echo "Cleanup: removing temp files..."
rm -f /tmp/myscript_*.tmp
}
# Set trap for EXIT (runs on any exit)
trap cleanup EXIT
# Create temp file
tmpfile="/tmp/myscript_$$.tmp"
echo "data" > "$tmpfile"
echo "Created: $tmpfile"
echo "Script continues..."
# cleanup runs automatically when script exits
echo ""
echo "=== Trap Multiple Signals ==="
# Handler function
handle_signal() {
local signal=$1
echo ""
echo "Caught signal: $signal"
exit 1
}
# Trap multiple signals
trap 'handle_signal SIGINT' SIGINT
trap 'handle_signal SIGTERM' SIGTERM
trap 'handle_signal SIGHUP' SIGHUP
echo "Traps set for SIGINT, SIGTERM, SIGHUP"
echo "PID: $$ (send signals to test)"
# Short wait to demonstrate
sleep 1
echo ""
echo "=== Trap with Cleanup and Exit ==="
tempdir=""
setup() {
tempdir=$(mktemp -d)
echo "Created temp dir: $tempdir"
}
cleanup_and_exit() {
local exit_code=${1:-0}
echo "Cleaning up..."
if [[ -n "$tempdir" && -d "$tempdir" ]]; then
rm -rf "$tempdir"
echo "Removed: $tempdir"
fi
exit $exit_code
}
# Trap EXIT to ensure cleanup
trap cleanup_and_exit EXIT
# Trap signals to exit cleanly
trap 'echo "Interrupted!"; exit 130' INT
trap 'echo "Terminated!"; exit 143' TERM
setup
sleep 1
echo "Work done"
echo ""
echo "=== Ignore Signals ==="
# Ignore SIGINT temporarily
trap '' SIGINT
echo "SIGINT ignored (Ctrl+C won't work)"
sleep 1
# Restore default
trap - SIGINT
echo "SIGINT restored"
echo ""
echo "=== Show Current Traps ==="
trap -p
Using kill to send signals and control processes.
#!/bin/bash
# Sending signals to processes
echo "=== Basic Kill ==="
# Start a process
sleep 100 &
pid=$!
echo "Started sleep, PID: $pid"
# Check if running
if kill -0 $pid 2>/dev/null; then
echo "Process is running"
fi
# Kill it (SIGTERM by default)
kill $pid
sleep 0.1
if ! kill -0 $pid 2>/dev/null; then
echo "Process terminated"
fi
echo ""
echo "=== Different Signals ==="
# Start process
sleep 100 &
pid=$!
# Send specific signals
echo "Sending SIGTERM (15) to $pid"
kill -TERM $pid
# or: kill -15 $pid
# or: kill -s SIGTERM $pid
# Start another
sleep 100 &
pid=$!
# SIGKILL - cannot be caught
echo "Sending SIGKILL (9) to $pid"
kill -9 $pid
echo ""
echo "=== Check Process Existence ==="
check_process() {
local pid=$1
# kill -0 doesn't send signal, just checks
if kill -0 $pid 2>/dev/null; then
echo "PID $pid: running"
return 0
else
echo "PID $pid: not running"
return 1
fi
}
sleep 100 &
pid=$!
check_process $pid
kill $pid
sleep 0.1
check_process $pid
echo ""
echo "=== Graceful Shutdown Pattern ==="
graceful_kill() {
local pid=$1
local timeout=${2:-5}
# Check if process exists
if ! kill -0 $pid 2>/dev/null; then
echo "Process $pid not running"
return 0
fi
# Send SIGTERM (graceful)
echo "Sending SIGTERM to $pid..."
kill -TERM $pid
# Wait for termination
local count=0
while kill -0 $pid 2>/dev/null; do
if ((count >= timeout)); then
echo "Timeout, sending SIGKILL..."
kill -9 $pid
return 1
fi
sleep 1
((count++))
done
echo "Process terminated gracefully"
return 0
}
# Test it
(trap 'echo "Caught TERM"; exit 0' TERM; sleep 100) &
pid=$!
graceful_kill $pid 3
echo ""
echo "=== Kill Process Group ==="
# Start a process that spawns children
(
sleep 100 &
sleep 100 &
sleep 100
) &
parent=$!
sleep 0.5
echo "Parent PID: $parent"
echo "Killing process group..."
# Negative PID kills the process group
kill -- -$parent 2>/dev/null
echo ""
echo "=== pkill and pgrep ==="
# Start named process
(exec -a "myprocess" sleep 100) &
pid=$!
echo "Started 'myprocess' (PID: $pid)"
# Find by name
found=$(pgrep -f "myprocess")
echo "pgrep found: $found"
# Kill by name
pkill -f "myprocess"
echo "pkill executed"
Understanding subshells, command grouping, and process substitution.
#!/bin/bash
# Subshells and process substitution
echo "=== Subshell Basics ==="
var="original"
# Subshell - changes don't affect parent
(
var="modified"
echo "In subshell: var=$var"
echo "Subshell PID: $BASHPID"
)
echo "After subshell: var=$var"
echo "Parent PID: $BASHPID"
echo ""
echo "=== Command Grouping ==="
# {} runs in current shell
{
var="changed"
echo "In braces: var=$var"
}
echo "After braces: var=$var"
# () runs in subshell
var="original"
(
var="changed"
)
echo "After parens: var=$var"
echo ""
echo "=== Pipelines Create Subshells ==="
# Each command in pipeline runs in subshell
count=0
echo -e "a\nb\nc" | while read line; do
((count++))
done
echo "Count after pipe: $count" # 0! Lost in subshell
# Solution: use process substitution
count=0
while read line; do
((count++))
done < <(echo -e "a\nb\nc")
echo "Count with process sub: $count" # 3
echo ""
echo "=== Process Substitution ==="
# <() creates a file descriptor from command output
echo "Using <():"
# Compare two command outputs
diff <(echo -e "a\nb\nc") <(echo -e "a\nB\nc")
echo ""
# Feed multiple inputs
paste <(seq 1 3) <(seq 4 6)
echo ""
echo "=== Output Process Substitution ==="
# >() sends output to command
echo "Using >():"
# Tee to multiple commands
echo "Hello World" | tee >(wc -c > /tmp/count.txt) >(tr 'a-z' 'A-Z')
sleep 0.1
echo "Character count: $(cat /tmp/count.txt)"
rm /tmp/count.txt
echo ""
echo "=== Subshell Exit Status ==="
# Subshell exit status
(exit 5)
echo "Subshell exit: $?"
# Pipeline exit status
false | true
echo "Pipeline exit (last): $?"
echo "PIPESTATUS: ${PIPESTATUS[*]}"
echo ""
echo "=== Practical: Parallel Processing ==="
# Process files in parallel with subshells
process_file() {
local file=$1
sleep 1 # Simulate work
echo "Processed: $file"
}
files=("a.txt" "b.txt" "c.txt" "d.txt")
pids=()
for file in "${files[@]}"; do
(process_file "$file") &
pids+=($!)
done
echo "Processing ${#files[@]} files in parallel..."
wait
echo "All files processed"
< <(cmd) or lastpipe option to work around this.
Running tasks in parallel with controlled concurrency.
#!/bin/bash
# Parallel execution patterns
echo "=== Simple Parallel ==="
# Run all in parallel, wait for all
start=$(date +%s)
sleep 1 &
sleep 1 &
sleep 1 &
wait
end=$(date +%s)
echo "3 jobs (1s each) took $((end - start))s (parallel)"
echo ""
echo "=== Parallel with PID Tracking ==="
declare -A jobs
run_job() {
local name=$1
local duration=$2
sleep $duration
echo "Job '$name' complete"
}
# Start jobs
for job in "database:2" "api:1" "cache:3"; do
IFS=':' read -r name dur <<< "$job"
run_job "$name" "$dur" &
jobs[$!]="$name"
echo "Started $name (PID: $!)"
done
# Wait and report
for pid in "${!jobs[@]}"; do
wait $pid
status=$?
echo "${jobs[$pid]} finished with status $status"
done
echo ""
echo "=== Limited Concurrency ==="
MAX_JOBS=2
job_count=0
limited_parallel() {
local cmd=$1
# Wait if at max jobs
while ((job_count >= MAX_JOBS)); do
wait -n # Wait for any job
((job_count--))
done
# Start new job
eval "$cmd" &
((job_count++))
}
echo "Running 5 jobs with max $MAX_JOBS concurrent..."
for i in {1..5}; do
limited_parallel "sleep 1; echo 'Job $i done'"
done
wait
echo "All done"
echo ""
echo "=== xargs Parallel ==="
# Using xargs for parallel execution
echo -e "1\n2\n3\n4\n5" | xargs -P 3 -I {} bash -c 'sleep 1; echo "xargs job {}"'
echo ""
echo "=== GNU Parallel Style ==="
# Simulate parallel with bash
parallel_run() {
local max_procs=$1
shift
local -a commands=("$@")
local running=0
local i=0
while ((i < ${#commands[@]})); do
if ((running < max_procs)); then
eval "${commands[$i]}" &
((running++))
((i++))
else
wait -n
((running--))
fi
done
wait
}
commands=(
"echo 'A'; sleep 1"
"echo 'B'; sleep 1"
"echo 'C'; sleep 1"
"echo 'D'; sleep 1"
)
echo "Parallel run:"
parallel_run 2 "${commands[@]}"
echo "Done"
Preventing multiple instances of a script using lock files.
#!/bin/bash
# Lock files and singleton scripts
echo "=== Basic Lock File ==="
LOCKFILE="/tmp/myscript.lock"
acquire_lock() {
if [[ -e "$LOCKFILE" ]]; then
local pid=$(cat "$LOCKFILE")
# Check if process is still running
if kill -0 "$pid" 2>/dev/null; then
echo "Script already running (PID: $pid)"
return 1
else
echo "Stale lock file, removing"
rm -f "$LOCKFILE"
fi
fi
echo $$ > "$LOCKFILE"
return 0
}
release_lock() {
rm -f "$LOCKFILE"
}
# Set up trap to release lock on exit
trap release_lock EXIT
if ! acquire_lock; then
exit 1
fi
echo "Lock acquired, running..."
sleep 2
echo "Done"
echo ""
echo "=== flock for Atomic Locking ==="
LOCKFILE="/tmp/flock_test.lock"
(
# flock creates lock on file descriptor
exec 200>"$LOCKFILE"
# Try to acquire lock (non-blocking)
if ! flock -n 200; then
echo "Could not acquire lock"
exit 1
fi
echo "Got lock, working..."
sleep 2
echo "Done"
# Lock released when fd closes
) &
# Try to get lock while first is running
sleep 0.5
(
exec 200>"$LOCKFILE"
if ! flock -n 200; then
echo "Second instance blocked (good!)"
exit 1
fi
echo "Second got lock (unexpected)"
) &
wait
rm -f "$LOCKFILE"
echo ""
echo "=== Complete Singleton Pattern ==="
singleton() {
local lockfile="/tmp/${0##*/}.lock"
local fd=200
# Open lock file
eval "exec $fd>\"$lockfile\""
# Try to acquire lock
if ! flock -n $fd; then
local pid=$(cat "$lockfile")
echo "Already running (PID: $pid)" >&2
return 1
fi
# Write PID
echo $$ > "$lockfile"
# Set cleanup trap
trap "rm -f '$lockfile'" EXIT
return 0
}
# Usage
if ! singleton; then
exit 1
fi
echo "Singleton running..."
echo ""
echo "=== Timeout Lock ==="
acquire_lock_timeout() {
local lockfile=$1
local timeout=$2
local fd=200
eval "exec $fd>\"$lockfile\""
if flock -w "$timeout" $fd; then
echo $$ > "$lockfile"
return 0
else
echo "Could not acquire lock within ${timeout}s"
return 1
fi
}
# Test
LOCKFILE="/tmp/timeout_test.lock"
# First process holds lock
(
exec 200>"$LOCKFILE"
flock 200
echo "First process has lock"
sleep 3
) &
sleep 0.5
# Second tries with timeout
if acquire_lock_timeout "$LOCKFILE" 2; then
echo "Got lock"
else
echo "Timed out"
fi
wait
rm -f "$LOCKFILE"
flock provides atomic locking and handles race conditions properly. Simple PID-file approaches can have race conditions.
Spawning, monitoring, and cleaning up child processes.
#!/bin/bash
# Managing child processes
echo "=== Track All Children ==="
declare -a children
spawn() {
"$@" &
children+=($!)
echo "Spawned PID $!: $*"
}
cleanup_children() {
echo "Cleaning up ${#children[@]} children..."
for pid in "${children[@]}"; do
if kill -0 "$pid" 2>/dev/null; then
kill "$pid"
echo "Killed $pid"
fi
done
}
trap cleanup_children EXIT
spawn sleep 10
spawn sleep 20
spawn sleep 30
echo "Children: ${children[*]}"
sleep 1
# cleanup runs on exit
echo ""
echo "=== Monitor Child Health ==="
monitor_children() {
local -n pids=$1
local -a running=()
for pid in "${pids[@]}"; do
if kill -0 "$pid" 2>/dev/null; then
running+=($pid)
else
wait $pid
echo "Child $pid exited with status $?"
fi
done
pids=("${running[@]}")
return ${#running[@]}
}
# Start some children
children=()
for i in {1..3}; do
(sleep $i; exit $((i % 2))) &
children+=($!)
done
echo "Started: ${children[*]}"
while ((${#children[@]} > 0)); do
sleep 0.5
monitor_children children
done
echo "All children done"
echo ""
echo "=== Restart Failed Children ==="
declare -A workers
start_worker() {
local name=$1
(
trap 'exit 0' TERM
while true; do
sleep 1
done
) &
workers[$name]=$!
echo "Started worker $name (PID: $!)"
}
supervise() {
for name in "${!workers[@]}"; do
local pid=${workers[$name]}
if ! kill -0 "$pid" 2>/dev/null; then
wait $pid
local status=$?
echo "Worker $name (PID $pid) died with status $status"
# Restart
start_worker "$name"
fi
done
}
# Start workers
for name in worker1 worker2; do
start_worker "$name"
done
# Simulate a crash
sleep 1
echo "Killing worker1..."
kill ${workers[worker1]}
sleep 0.5
supervise
sleep 1
echo "Stopping all workers..."
for pid in "${workers[@]}"; do
kill "$pid" 2>/dev/null
done
echo ""
echo "=== Forward Signals to Children ==="
children=()
forward_signal() {
echo "Forwarding signal to children..."
for pid in "${children[@]}"; do
kill -TERM "$pid" 2>/dev/null
done
}
trap forward_signal TERM INT
# Start children
for i in {1..3}; do
(trap 'echo "Child $$ got signal"; exit' TERM; sleep 100) &
children+=($!)
done
echo "Parent $$, children: ${children[*]}"
sleep 1
forward_signal
wait
Implementing timeouts and resource constraints for commands.
#!/bin/bash
# Timeout and resource limits
echo "=== timeout Command ==="
# Basic timeout
echo "Running with 2s timeout..."
if timeout 2 sleep 5; then
echo "Completed"
else
echo "Timed out (exit: $?)"
fi
# Exit code 124 means timeout
echo ""
echo "Exit code 124 means timeout:"
timeout 1 sleep 10
echo "Exit: $?"
echo ""
echo "=== Custom Timeout Function ==="
run_with_timeout() {
local timeout=$1
shift
# Run command in background
"$@" &
local pid=$!
# Start watchdog
(
sleep "$timeout"
if kill -0 $pid 2>/dev/null; then
kill -TERM $pid
fi
) &
local watchdog=$!
# Wait for command
wait $pid
local status=$?
# Kill watchdog if still running
kill $watchdog 2>/dev/null
wait $watchdog 2>/dev/null
return $status
}
echo "Custom timeout test:"
run_with_timeout 2 sleep 5
echo "Status: $?"
echo ""
echo "=== ulimit Resource Limits ==="
echo "Current limits:"
echo " Max processes: $(ulimit -u)"
echo " Open files: $(ulimit -n)"
echo " Stack size: $(ulimit -s) KB"
echo " CPU time: $(ulimit -t) seconds"
# Set limit in subshell
echo ""
echo "Limited subshell:"
(
ulimit -t 1 # 1 second CPU time
echo "CPU limit: $(ulimit -t)s"
)
echo ""
echo "=== Retry with Timeout ==="
retry_with_timeout() {
local max_attempts=$1
local timeout=$2
shift 2
local attempt=1
while ((attempt <= max_attempts)); do
echo "Attempt $attempt of $max_attempts..."
if timeout "$timeout" "$@"; then
echo "Success on attempt $attempt"
return 0
fi
echo "Attempt $attempt failed"
((attempt++))
sleep 1
done
echo "All $max_attempts attempts failed"
return 1
}
retry_with_timeout 3 1 bash -c '((RANDOM % 2 == 0)) && echo "OK" || sleep 5'
echo ""
echo "=== Deadline Pattern ==="
run_until_deadline() {
local deadline=$1
shift
local now=$(date +%s)
local timeout=$((deadline - now))
if ((timeout <= 0)); then
echo "Deadline already passed"
return 1
fi
echo "Running with ${timeout}s until deadline..."
timeout "$timeout" "$@"
}
# Set deadline 3 seconds from now
deadline=$(($(date +%s) + 3))
run_until_deadline $deadline sleep 10
A reusable library for process and job control.
#!/bin/bash
# Complete process management library
#######################################
# Global State
#######################################
declare -a __MANAGED_PIDS=()
declare -A __PID_NAMES=()
#######################################
# Process Information
#######################################
pid_exists() {
kill -0 "$1" 2>/dev/null
}
get_pid_status() {
local pid=$1
if ! pid_exists "$pid"; then
echo "not running"
elif [[ -f /proc/$pid/status ]]; then
grep State /proc/$pid/status | awk '{print $2}'
else
echo "running"
fi
}
#######################################
# Job Management
#######################################
start_job() {
local name=$1
shift
"$@" &
local pid=$!
__MANAGED_PIDS+=($pid)
__PID_NAMES[$pid]="$name"
echo "$pid"
}
stop_job() {
local pid=$1
local timeout=${2:-5}
if ! pid_exists "$pid"; then
return 0
fi
# Graceful shutdown
kill -TERM "$pid"
local count=0
while pid_exists "$pid" && ((count < timeout)); do
sleep 1
((count++))
done
# Force kill if needed
if pid_exists "$pid"; then
kill -9 "$pid"
return 1
fi
return 0
}
stop_all_jobs() {
for pid in "${__MANAGED_PIDS[@]}"; do
stop_job "$pid"
done
__MANAGED_PIDS=()
}
wait_all_jobs() {
local failed=0
for pid in "${__MANAGED_PIDS[@]}"; do
if ! wait "$pid"; then
((failed++))
fi
done
return $failed
}
#######################################
# Signal Handling
#######################################
setup_signal_handlers() {
trap 'echo "Interrupted"; stop_all_jobs; exit 130' INT
trap 'echo "Terminated"; stop_all_jobs; exit 143' TERM
trap 'stop_all_jobs' EXIT
}
#######################################
# Execution Helpers
#######################################
run_with_timeout() {
local timeout=$1
shift
timeout "$timeout" "$@"
}
run_parallel() {
local max_jobs=$1
shift
local -a commands=("$@")
local running=0
local idx=0
while ((idx < ${#commands[@]} || running > 0)); do
while ((running < max_jobs && idx < ${#commands[@]})); do
eval "${commands[$idx]}" &
((running++))
((idx++))
done
if ((running > 0)); then
wait -n
((running--))
fi
done
}
retry() {
local attempts=$1
local delay=$2
shift 2
local i
for ((i=1; i<=attempts; i++)); do
if "$@"; then
return 0
fi
if ((i < attempts)); then
sleep "$delay"
fi
done
return 1
}
#######################################
# Demo
#######################################
echo "========================================"
echo " Process Management Library Demo"
echo "========================================"
echo ""
echo "=== Setup ==="
setup_signal_handlers
echo "Signal handlers installed"
echo ""
echo "=== Start Jobs ==="
pid1=$(start_job "worker1" sleep 10)
pid2=$(start_job "worker2" sleep 10)
pid3=$(start_job "worker3" sleep 10)
echo "Started:"
for pid in "${__MANAGED_PIDS[@]}"; do
echo " PID $pid: ${__PID_NAMES[$pid]}"
done
echo ""
echo "=== Check Status ==="
for pid in "${__MANAGED_PIDS[@]}"; do
status=$(get_pid_status $pid)
echo " PID $pid: $status"
done
echo ""
echo "=== Stop One Job ==="
stop_job $pid1
echo "Stopped PID $pid1"
echo ""
echo "=== Parallel Execution ==="
commands=(
"sleep 1; echo 'Task A done'"
"sleep 1; echo 'Task B done'"
"sleep 1; echo 'Task C done'"
)
run_parallel 2 "${commands[@]}"
echo ""
echo "=== Cleanup ==="
stop_all_jobs
echo "All jobs stopped"
echo ""
echo "========================================"
# Background job
command & # Run in background
$! # PID of last background job
wait $pid # Wait for specific job
wait # Wait for all jobs
jobs # List jobs
# Signals
kill $pid # Send SIGTERM
kill -9 $pid # Send SIGKILL
kill -0 $pid # Check if running
trap 'cmd' SIGNAL # Set signal handler
# Process info
$$ # Current PID
$BASHPID # Current Bash PID (subshell-aware)
$PPID # Parent PID
$? # Last exit status
| Task | Code |
|---|---|
| Cleanup on exit | trap 'cleanup' EXIT |
| Handle Ctrl+C | trap 'exit 130' INT |
| Check process running | kill -0 $pid 2>/dev/null |
| Wait with timeout | timeout 30 command |
| Singleton script | flock -n lockfile |
| Parallel execution | cmd &; pids+=($!); wait |
wait to collect child exit statusesflock for proper locking$$ instead of $BASHPID in subshells$! is valid before using