Job_control
Chapter 25: Job Control in Bash
Section titled “Chapter 25: Job Control in Bash”Overview
Section titled “Overview”Job control allows you to manage multiple processes (jobs) running in a shell session. This chapter covers how to run jobs in the background, bring them to the foreground, and manage them effectively - essential skills for DevOps engineers.
Understanding Jobs
Section titled “Understanding Jobs”What is a Job?
Section titled “What is a Job?”A job is a process or group of processes started by the shell. Each job has:
- Job ID - Shell-relative identifier (%1, %2, etc.)
- Process Group ID - System-level identifier
- Status - Running, Stopped, Done
┌────────────────────────────────────────────────────────────────┐│ Job Control Flow │├────────────────────────────────────────────────────────────────┤│ ││ ┌─────────┐ ││ │ Shell │ ││ └────┬────┘ ││ │ ││ ┌───┴───┐ ││ │ Job 1 │ (Foreground) ││ └───┬───┘ ││ │ ││ ┌───┴───┐ ││ │ Job 2 │ (Background) ││ └───────┘ ││ ││ Job Control: ││ - Start jobs in background (&) ││ - Stop jobs (Ctrl+Z) ││ - Bring to foreground (fg) ││ - send to background (bg) ││ │└────────────────────────────────────────────────────────────────┘Job Commands
Section titled “Job Commands”jobs - List Jobs
Section titled “jobs - List Jobs”# List all jobsjobs
# List with process IDsjobs -l
# List only running jobsjobs -r
# List only stopped jobsjobs -sfg - Bring to Foreground
Section titled “fg - Bring to Foreground”# Bring job 1 to foregroundfg %1
# Bring current job (or +)fg %+
# Bring previous job (or -)fg %-
# By process groupfg %% # Currentfg %+ # Currentfg %- # Previousbg - Send to Background
Section titled “bg - Send to Background”# Resume job 1 in backgroundbg %1
# Resume current stopped jobbg
# Resume job in backgroundbg %-Starting Background Jobs
Section titled “Starting Background Jobs”Basic Background Execution
Section titled “Basic Background Execution”# Start in background./long_task.sh &
# Start multiple background jobs./task1.sh &./task2.sh &./task3.sh &
# Start with output redirection./script.sh > output.log 2>&1 &
# Using nohup (survives terminal close)nohup ./script.sh &
# Using setsid (new session)setsid ./script.sh &Job States
Section titled “Job States”Running
Section titled “Running”# Start running job./script.sh &
# Verify runningjobs# [1]+ Running ./script.sh &Stopped (Suspended)
Section titled “Stopped (Suspended)”# Stop foreground job# Press Ctrl+Z
# Verify stoppedjobs# [1]+ Stopped ./script.sh# Check when job completesjobs# [1]+ Done ./script.sh
# Or history shows completedjobs -l# [1]+ 1234 Done ./script.shManaging Multiple Jobs
Section titled “Managing Multiple Jobs”Parallel Execution
Section titled “Parallel Execution”#!/usr/bin/env bash# Run tasks in parallel
# Start all tasks./task1.sh &PID1=$!./task2.sh &PID2=$!./task3.sh &PID3=$!
# Wait for allwait $PID1 $PID2 $PID3
echo "All tasks complete"Controlled Parallelism
Section titled “Controlled Parallelism”#!/usr/bin/env bash# Run with limited parallelism
MAX_JOBS=4COUNTER=0
for item in {1..20}; do # Wait if max jobs reached while (( $(jobs -p | wc -l) >= MAX_JOBS )); do sleep 0.5 done
# Run task in background ./process_item.sh $item & ((COUNTER++))done
# Wait for remaining jobswait
echo "Processed $COUNTER items"Wait with Timeout
Section titled “Wait with Timeout”#!/usr/bin/env bash
# Start background job./long_task.sh &PID=$!
# Wait with timeoutTIMEOUT=60count=0
while kill -0 $PID 2>/dev/null; do if [[ $count -ge $TIMEOUT ]]; then echo "Timeout, killing process..." kill $PID break fi sleep 1 ((count++))done
echo "Job completed or timed out"Process Group Management
Section titled “Process Group Management”Session and Process Group
Section titled “Session and Process Group”# Get session IDps -o sid= -p $$
# Get process groupps -o pgrp= -p $$
# Move process to new groupsetsid ./script.sh
# Kill entire process groupkill -TERM -$(pgrep -f process_name)Real-World DevOps Examples
Section titled “Real-World DevOps Examples”Parallel Log Processing
Section titled “Parallel Log Processing”#!/usr/bin/env bash# Process multiple log files in parallel
LOG_FILES=( "/var/log/nginx/access.log" "/var/log/nginx/error.log" "/var/log/app/application.log" "/var/log/syslog")
process_log() { local logfile="$1" echo "Processing: $logfile"
# Extract metrics local lines=$(wc -l < "$logfile") local errors=$(grep -c ERROR "$logfile" 2>/dev/null || echo 0)
echo "$logfile: $lines lines, $errors errors"}
# Process in parallelfor log in "${LOG_FILES[@]}"; do process_log "$log" &done
waitecho "All logs processed"Concurrent API Requests
Section titled “Concurrent API Requests”#!/usr/bin/env bash# Make concurrent API requests
URLS=( "https://api1.example.com/data" "https://api2.example.com/data" "https://api3.example.com/data" "https://api4.example.com/data")
fetch_data() { local url="$1" curl -s "$url" | jq -c '.'}
for url in "${URLS[@]}"; do fetch_data "$url" &done
waitecho "All requests complete"Background Service Monitor
Section titled “Background Service Monitor”#!/usr/bin/env bash# Monitor multiple services
SERVICES=("nginx" "mysql" "redis" "docker")
monitor_service() { local service="$1"
while true; do if systemctl is-active --quiet "$service"; then : # Service running else echo "$(date): $service is DOWN!" fi sleep 10 done}
# Start monitors in backgroundfor service in "${SERVICES[@]}"; do monitor_service "$service" &done
echo "Monitors started"
# Wait (or use trap to cleanup)waitJob Control in Scripts
Section titled “Job Control in Scripts”Disown Jobs
Section titled “Disown Jobs”#!/usr/bin/env bash
# Start job and disown./background_task.sh &disown
# Disown specific job./task.sh &disown %1
# Disown all jobsdisown -a
# Keep job in job table but ignore SIGHUPdisown -h %1Protecting Jobs from HUP
Section titled “Protecting Jobs from HUP”#!/usr/bin/env bash
# nohup - ignore hangup signalsnohup ./script.sh &
# nohup with output redirectnohup ./script.sh > output.log 2>&1 &
# Using setsid (new session)setsid ./script.sh &
# Using screen/tmux (see next section)Using screen and tmux
Section titled “Using screen and tmux”GNU Screen
Section titled “GNU Screen”# Start named screen sessionscreen -S mysession
# Detach from screenCtrl+A D
# List screensscreen -ls
# Reattachscreen -r mysession
# Reattach or createscreen -d -RR mysession
# Run command in screenscreen -dmS mysession ./script.sh
# Send command to screenscreen -S mysession -X stuff './script.sh\n'tmux (Modern Alternative)
Section titled “tmux (Modern Alternative)”# Start tmux sessiontmux new -s mysession
# Detach from tmuxCtrl+B D
# List sessionstmux ls
# Reattachtmux attach -t mysession
# Split window verticallyCtrl+B "
# Split window horizontallyCtrl+B %
# Run command in tmuxtmux new -d -s mysession './script.sh'
# Send command to tmux sessiontmux send-keys -t mysession './script.sh' C-mwait Command
Section titled “wait Command”Waiting for Jobs
Section titled “Waiting for Jobs”# Wait for specific job./task.sh &wait $!
# Wait for all background jobs./task1.sh &./task2.sh &wait
# Wait with specific jobwait %1 %2
# Wait returns exit status of job./script.sh &wait $!echo "Exit status: $?"Troubleshooting Job Control
Section titled “Troubleshooting Job Control”Common Issues
Section titled “Common Issues”# Job not starting in background?# Check for background jobs limitulimit -u
# Too many background jobs?jobs -l# Kill excess jobskill $(jobs -p)
# Jobs not responding to Ctrl+C?# Check for disowned jobsps aux | grep script
# Kill manuallypkill -f script_nameSummary
Section titled “Summary”In this chapter, you learned:
- ✅ Job basics and job control concepts
- ✅ jobs, fg, bg commands
- ✅ Starting background jobs
- ✅ Job states (running, stopped, done)
- ✅ Parallel execution patterns
- ✅ Controlled parallelism
- ✅ Process groups
- ✅ Real-world DevOps examples
- ✅ nohup, disown, setsid
- ✅ screen and tmux for persistent sessions
- ✅ wait command and job exit status
- ✅ Troubleshooting job control
Next Steps
Section titled “Next Steps”Continue to the next chapter to learn about Environment Variables.
Previous Chapter: Signals and Traps Next Chapter: Environment Variables