⚙️ The jobs Command

Manage Background Jobs and Process Control

jobs [OPTIONS] [JOB_SPEC]

Overview

The jobs command is a shell built-in that displays information about jobs running in the current shell session. A "job" is a process or group of processes started from the shell. Jobs can run in the foreground (occupying your terminal) or background (running independently). The jobs command is essential for job control - managing multiple tasks from a single terminal.

What is a Job?

  • A process or pipeline started from the shell
  • Identified by a job number (e.g., [1], [2], [3])
  • Can be in foreground or background
  • Can be running, stopped, or terminated
  • Only visible to the shell that created them
  • Lost when shell exits (unless disowned or nohup)

Job States:

  • Running: Currently executing
  • Stopped: Paused (via Ctrl+Z or SIGSTOP)
  • Done: Completed successfully
  • Exit: Terminated with error
  • Killed: Terminated by signal

Key Capabilities:

  • List all jobs in current shell
  • Show job status (running, stopped, done)
  • Display process IDs (PIDs)
  • Identify current and previous jobs
  • Monitor background processes
  • Manage multiple concurrent tasks
Jobs vs Processes:

Jobs are a shell abstraction - they're how the shell tracks and manages processes. The jobs command only shows jobs started by the current shell. Use ps to see all system processes. A single job can contain multiple processes if you use pipes (e.g., cat file | grep pattern | sort is one job with three processes).

Common Options

Option Description
-l List process IDs in addition to job information
-p List only process IDs
-n Show only jobs that have changed status since last notification
-r Show only running jobs
-s Show only stopped jobs
-x command Replace job specifications with process IDs in command

Job Control Basics

Essential Keyboard Shortcuts:
Ctrl+Z Suspend (stop) current foreground job
Ctrl+C Terminate (kill) current foreground job
Ctrl+D Send EOF (end of file) to current job
Related Job Control Commands:
jobs List all jobs
fg [%n] Bring job to foreground
bg [%n] Resume job in background
kill %n Send signal to job
disown %n Remove job from job table
command & Start job in background

Detailed Examples

Example 1

Basic Jobs Listing

Starting jobs and viewing them with the jobs command.

# Start a long-running job in background sleep 100 & # Start another background job find / -name "*.log" 2>/dev/null > /tmp/logfiles.txt & # Start a job and suspend it with Ctrl+Z ping google.com # Press Ctrl+Z to suspend # List all jobs jobs
[1] Running sleep 100 & [2] Running find / -name "*.log" 2>/dev/null > /tmp/logfiles.txt & [3]+ Stopped ping google.com
Explanation: The jobs command lists all jobs in the current shell. Each job shows:
  • [1], [2], [3]: Job numbers
  • +: Current job (most recently started or stopped)
  • -: Previous job
  • Running/Stopped: Job state
  • Command: The command that was executed
Job References: You can refer to jobs by number: %1, %2, etc. Special references: %+ or %% (current job), %- (previous job), %?string (job with 'string' in command).
Example 2

Jobs with Process IDs (-l option)

Display both job numbers and process IDs for more detailed tracking.

# Start several background jobs sleep 200 & sleep 300 & dd if=/dev/zero of=/tmp/testfile bs=1M count=1000 & # List jobs with PIDs jobs -l
[1] 12345 Running sleep 200 & [2]- 12346 Running sleep 300 & [3]+ 12347 Running dd if=/dev/zero of=/tmp/testfile bs=1M count=1000 &
Explanation: The -l option includes process IDs (PIDs) in the output. This is useful when you need to:
  • Monitor the process with ps or top
  • Send specific signals with kill PID
  • Check resource usage with tools that require PIDs
  • Track processes in system logs
# Get only the PIDs (useful for scripting) jobs -p
12345 12346 12347
Using PIDs in Scripts:
# Kill all background jobs by PID for pid in $(jobs -p); do kill $pid done
Example 3

Filtering Jobs by State (-r and -s)

Show only running jobs or only stopped jobs.

# Create a mix of running and stopped jobs sleep 100 & sleep 200 & vim large_file.txt # Press Ctrl+Z to suspend vim cat bigfile.log # Press Ctrl+Z to suspend cat # Show only running jobs echo "=== Running Jobs ===" jobs -r echo "" echo "=== Stopped Jobs ===" jobs -s
=== Running Jobs === [1] Running sleep 100 & [2]- Running sleep 200 & === Stopped Jobs === [3]+ Stopped vim large_file.txt [4] Stopped cat bigfile.log
Explanation:
  • -r option: Shows only running jobs (useful to see what's actively executing)
  • -s option: Shows only stopped jobs (useful to resume suspended tasks)

This filtering is particularly useful in scripts or when managing many jobs.

Practical Use:
# Resume all stopped jobs in background for job in $(jobs -s | awk '{print $1}' | tr -d '[]+-'); do bg %$job done
Example 4

Moving Jobs Between Foreground and Background

Control where jobs execute using fg, bg, and jobs together.

# Start a job in foreground find /usr -name "*.conf" # Press Ctrl+Z to suspend it # Check jobs jobs
[1]+ Stopped find /usr -name "*.conf"
# Resume it in background bg %1 # Verify it's running in background jobs
[1]+ Running find /usr -name "*.conf" &
# Bring it back to foreground fg %1 # It now occupies your terminal # Press Ctrl+Z again to suspend # Start multiple jobs sleep 100 & sleep 200 & sleep 300 & jobs
[1] Running find /usr -name "*.conf" & [2] Running sleep 100 & [3]- Running sleep 200 & [4]+ Running sleep 300 &
# Bring job 2 to foreground fg %2 # Or bring previous job to foreground fg %- # Or bring current job to foreground fg %+
Explanation: Job control workflow:
  1. Ctrl+Z: Suspends current foreground job
  2. jobs: Check what jobs exist
  3. bg %n: Resume job n in background
  4. fg %n: Bring job n to foreground
Quick Switching: After suspending a job with Ctrl+Z, just type bg (no arguments) to resume it in the background. Similarly, fg brings the current job (%) to foreground.
Example 5

Killing and Terminating Jobs

Using kill with job specifications to terminate jobs.

# Start several jobs sleep 1000 & sleep 2000 & sleep 3000 & # List them jobs -l
[1] 15234 Running sleep 1000 & [2]- 15235 Running sleep 2000 & [3]+ 15236 Running sleep 3000 &
# Kill job 2 using job specification kill %2 # Wait a moment and check jobs
[1] Running sleep 1000 & [2]- Terminated sleep 2000 [3]+ Running sleep 3000 &
# Kill current job (the one with +) kill %+ # Or kill all jobs kill $(jobs -p) # Force kill if regular kill doesn't work kill -9 %1 # Check - all should be gone jobs
[No jobs]
Explanation: The kill command can accept job specifications:
  • kill %1: Kill job 1
  • kill %+: Kill current job
  • kill %-: Kill previous job
  • kill -9 %n: Force kill (SIGKILL)
  • kill $(jobs -p): Kill all jobs by PID
Kill Signals:
  • SIGTERM (15): Default, graceful termination
  • SIGKILL (9): Force kill, cannot be caught
  • SIGSTOP (19): Pause process (like Ctrl+Z)
  • SIGCONT (18): Resume process (like bg)
Example 6

Job Notifications (-n option)

Show only jobs that have changed status since last check.

# Start a short and a long job sleep 5 & sleep 100 & # Check all jobs jobs
[1]- Running sleep 5 & [2]+ Running sleep 100 &
# Wait 6 seconds for job 1 to complete sleep 6 # Check for changes only jobs -n
[1]- Done sleep 5
# Check again - nothing new jobs -n # (No output - no new changes) # Check all jobs - will show notification has been seen jobs
[2]+ Running sleep 100 &
Explanation: The -n option shows only jobs whose status has changed since the shell last reported their status. This is useful for:
  • Monitoring job completion without clutter
  • Scripts that need to react to job changes
  • Automated notification systems

Once you've checked with jobs -n, those changes are marked as "seen" and won't appear again.

Monitoring Script:
#!/bin/bash # Monitor jobs and notify when they complete while true; do changed=$(jobs -n) if [ -n "$changed" ]; then echo "Job status changed:" echo "$changed" # Send notification, email, etc. fi sleep 5 done
Example 7

Disowning Jobs (Persist After Logout)

Remove jobs from shell's job table so they survive shell exit.

# Start a long-running job ./long_running_script.sh & # Check jobs jobs -l
[1]+ 16789 Running ./long_running_script.sh &
# Disown the job (remove from job table) disown %1 # Check jobs - it's gone from the job table jobs
(No output - job is disowned)
# But the process still exists ps aux | grep long_running_script
craig 16789 0.0 0.1 12345 6789 ? S 14:23 0:00 /bin/bash ./long_running_script.sh
Explanation: When you disown a job:
  • It's removed from the shell's job table
  • The shell will NOT send SIGHUP when it exits
  • The process continues running even after logout
  • You can no longer use job control commands (%1, fg, bg) on it
  • Must use process management (ps, kill PID) instead
Better Alternative - nohup:
# Start with nohup from the beginning nohup ./long_running_script.sh & # Output goes to nohup.out by default # Process will survive logout automatically
Comparison:
  • disown: Use after starting job normally
  • nohup: Use when starting job (better practice)
  • screen/tmux: Best for interactive long-running tasks
Example 8

Job Management in Scripts

Using jobs command for parallel processing and workflow management.

#!/bin/bash # parallel-backup.sh - Run multiple backups in parallel MAX_JOBS=3 BACKUP_DIRS=("/home" "/etc" "/var/www" "/opt" "/srv") backup_directory() { local dir="$1" local backup_file="/backup/$(basename $dir)_$(date +%Y%m%d).tar.gz" echo "Starting backup of $dir..." tar -czf "$backup_file" "$dir" 2>/dev/null if [ $? -eq 0 ]; then echo "✓ Completed backup of $dir" else echo "✗ Failed backup of $dir" fi } # Export function so background jobs can use it export -f backup_directory # Process directories with job limit for dir in "${BACKUP_DIRS[@]}"; do # Wait if we're at max jobs while [ $(jobs -r | wc -l) -ge $MAX_JOBS ]; do sleep 1 done # Start backup in background backup_directory "$dir" & done # Wait for all background jobs to complete echo "" echo "Waiting for all backups to complete..." wait echo "" echo "All backups finished!" echo "Summary:" jobs -l
Starting backup of /home... Starting backup of /etc... Starting backup of /var/www... ✓ Completed backup of /etc Starting backup of /opt... ✓ Completed backup of /home Starting backup of /srv... ✓ Completed backup of /var/www ✓ Completed backup of /opt ✓ Completed backup of /srv Waiting for all backups to complete... All backups finished! Summary: (No jobs - all completed)
Explanation: This script demonstrates:
  • Running multiple tasks in parallel
  • Limiting concurrent jobs with jobs -r | wc -l
  • Using wait to wait for all jobs to complete
  • Monitoring job completion for status reporting
Real-World Applications:
  • Parallel data processing
  • Batch file conversions
  • Multi-server deployments
  • Distributed testing
  • Concurrent database queries
Example 9

Job Completion Monitoring

Building a job status dashboard for real-time monitoring.

#!/bin/bash # job-monitor.sh - Monitor and display job status # Start several jobs with different durations echo "Starting jobs..." sleep 10 & sleep 20 & sleep 30 & find / -name "*.conf" 2>/dev/null > /tmp/configs.txt & # Monitor jobs until all complete echo "" echo "Monitoring jobs..." echo "========================================" while [ $(jobs -r | wc -l) -gt 0 ]; do clear echo "JOB STATUS DASHBOARD" echo "========================================" echo "Time: $(date '+%H:%M:%S')" echo "" # Count jobs by status total=$(jobs | wc -l) running=$(jobs -r | wc -l) stopped=$(jobs -s | wc -l) echo "Total jobs: $total" echo "Running: $running" echo "Stopped: $stopped" echo "" echo "Job Details:" echo "----------------------------------------" jobs -l | while read line; do jobnum=$(echo "$line" | awk '{print $1}') pid=$(echo "$line" | awk '{print $2}') status=$(echo "$line" | awk '{print $3}') # Add visual indicator if [ "$status" = "Running" ]; then indicator="🔄" elif [ "$status" = "Stopped" ]; then indicator="⏸️ " else indicator="✓" fi echo "$indicator $line" done echo "========================================" echo "Press Ctrl+C to exit monitor" sleep 2 done clear echo "All jobs completed!" jobs -l
JOB STATUS DASHBOARD ======================================== Time: 14:23:45 Total jobs: 4 Running: 4 Stopped: 0 Job Details: ---------------------------------------- 🔄 [1] 17234 Running sleep 10 & 🔄 [2] 17235 Running sleep 20 & 🔄 [3] 17236 Running sleep 30 & 🔄 [4]+ 17237 Running find / -name "*.conf" 2>/dev/null > /tmp/configs.txt & ======================================== Press Ctrl+C to exit monitor [Updates every 2 seconds until jobs complete] All jobs completed!
Explanation: This monitoring script demonstrates:
  • Real-time job status updates
  • Visual dashboard with clear screen
  • Status categorization and counting
  • Visual indicators for job states
  • Continuous monitoring loop
Enhanced Version Ideas:
  • Add CPU/memory usage per job
  • Show elapsed time per job
  • Log job completions to file
  • Send notifications on completion
  • Color-code by status
Example 10

Complete Job Management System

A comprehensive job control utility with full management capabilities.

#!/bin/bash # jobctl - Complete job management utility set -euo pipefail # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' log_info() { echo -e "${BLUE}[INFO]${NC} $*"; } log_success() { echo -e "${GREEN}[OK]${NC} $*"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } log_error() { echo -e "${RED}[ERROR]${NC} $*"; } # List all jobs with detailed information list_jobs() { echo "" echo "=========================================" echo "JOB LIST" echo "=========================================" if [ $(jobs | wc -l) -eq 0 ]; then log_info "No jobs running" return fi jobs -l | while read line; do jobnum=$(echo "$line" | awk '{print $1}' | tr -d '[]+-') pid=$(echo "$line" | awk '{print $2}') status=$(echo "$line" | awk '{print $3}') cmd=$(echo "$line" | cut -d' ' -f4-) # Get CPU and memory usage cpu=$(ps -p $pid -o %cpu= 2>/dev/null || echo "N/A") mem=$(ps -p $pid -o %mem= 2>/dev/null || echo "N/A") echo "----------------------------------------" echo "Job: [$jobnum]" echo "PID: $pid" echo "Status: $status" echo "CPU: ${cpu}%" echo "Memory: ${mem}%" echo "Command: $cmd" done echo "=========================================" } # Resume job in background resume_job() { local jobnum="$1" if ! jobs %$jobnum >/dev/null 2>&1; then log_error "Job $jobnum not found" return 1 fi bg %$jobnum log_success "Job $jobnum resumed in background" } # Bring job to foreground foreground_job() { local jobnum="$1" if ! jobs %$jobnum >/dev/null 2>&1; then log_error "Job $jobnum not found" return 1 fi log_info "Bringing job $jobnum to foreground..." fg %$jobnum } # Kill a job kill_job() { local jobnum="$1" local signal="${2:-TERM}" if ! jobs %$jobnum >/dev/null 2>&1; then log_error "Job $jobnum not found" return 1 fi kill -$signal %$jobnum log_success "Sent SIG$signal to job $jobnum" } # Kill all jobs kill_all_jobs() { if [ $(jobs | wc -l) -eq 0 ]; then log_info "No jobs to kill" return fi read -p "Kill all jobs? (y/N): " confirm if [[ $confirm =~ ^[Yy]$ ]]; then for pid in $(jobs -p); do kill $pid 2>/dev/null done log_success "All jobs terminated" else log_info "Cancelled" fi } # Suspend job (send SIGSTOP) suspend_job() { local jobnum="$1" if ! jobs %$jobnum >/dev/null 2>&1; then log_error "Job $jobnum not found" return 1 fi kill -STOP %$jobnum log_success "Job $jobnum suspended" } # Resume all stopped jobs resume_all_stopped() { local stopped=$(jobs -s) if [ -z "$stopped" ]; then log_info "No stopped jobs" return fi echo "$stopped" | awk '{print $1}' | tr -d '[]+-' | while read jobnum; do bg %$jobnum log_success "Resumed job $jobnum in background" done } # Show detailed job info job_info() { local jobnum="$1" if ! jobs %$jobnum >/dev/null 2>&1; then log_error "Job $jobnum not found" return 1 fi local pid=$(jobs -l %$jobnum | awk '{print $2}') echo "" echo "=========================================" echo "DETAILED JOB INFORMATION" echo "=========================================" echo "Job Number: $jobnum" echo "" # Job status from jobs command jobs -l %$jobnum echo "" echo "Process Information:" echo "----------------------------------------" ps -f -p $pid echo "" echo "Resource Usage:" echo "----------------------------------------" ps -o pid,ppid,%cpu,%mem,vsz,rss,stat,start,time,command -p $pid echo "" echo "Open Files:" echo "----------------------------------------" lsof -p $pid 2>/dev/null | head -10 || echo "Unable to list open files" echo "=========================================" } # Interactive menu show_menu() { echo "" echo "=========================================" echo "JOB CONTROL UTILITY" echo "=========================================" echo "" echo "1. List all jobs" echo "2. Resume job in background" echo "3. Bring job to foreground" echo "4. Kill job (SIGTERM)" echo "5. Force kill job (SIGKILL)" echo "6. Suspend job" echo "7. Kill all jobs" echo "8. Resume all stopped jobs" echo "9. Detailed job info" echo "10. Exit" echo "" } # Main program if [ $# -eq 0 ]; then # Interactive mode while true; do show_menu read -p "Select option [1-10]: " choice case $choice in 1) list_jobs ;; 2) read -p "Job number: " jobnum resume_job "$jobnum" ;; 3) read -p "Job number: " jobnum foreground_job "$jobnum" ;; 4) read -p "Job number: " jobnum kill_job "$jobnum" TERM ;; 5) read -p "Job number: " jobnum kill_job "$jobnum" KILL ;; 6) read -p "Job number: " jobnum suspend_job "$jobnum" ;; 7) kill_all_jobs ;; 8) resume_all_stopped ;; 9) read -p "Job number: " jobnum job_info "$jobnum" ;; 10) log_info "Exiting" exit 0 ;; *) log_error "Invalid option" ;; esac done else # Command-line mode case "$1" in list|ls) list_jobs ;; resume|bg) [ $# -lt 2 ] && { log_error "Usage: $0 resume <jobnum>"; exit 1; } resume_job "$2" ;; fg) [ $# -lt 2 ] && { log_error "Usage: $0 fg <jobnum>"; exit 1; } foreground_job "$2" ;; kill) [ $# -lt 2 ] && { log_error "Usage: $0 kill <jobnum> [signal]"; exit 1; } kill_job "$2" "${3:-TERM}" ;; killall) kill_all_jobs ;; info) [ $# -lt 2 ] && { log_error "Usage: $0 info <jobnum>"; exit 1; } job_info "$2" ;; *) echo "Usage: $0 {list|resume|fg|kill|killall|info} [args]" exit 1 ;; esac fi
========================================= JOB CONTROL UTILITY ========================================= 1. List all jobs 2. Resume job in background 3. Bring job to foreground 4. Kill job (SIGTERM) 5. Force kill job (SIGKILL) 6. Suspend job 7. Kill all jobs 8. Resume all stopped jobs 9. Detailed job info 10. Exit Select option [1-10]: 1 ========================================= JOB LIST ========================================= ---------------------------------------- Job: [1] PID: 18234 Status: Running CPU: 2.3% Memory: 0.5% Command: sleep 100 & ---------------------------------------- Job: [2] PID: 18235 Status: Stopped CPU: 0.0% Memory: 0.3% Command: vim largefile.txt =========================================
Explanation: This comprehensive utility provides:
  • Full job listing with resource usage
  • Interactive and command-line modes
  • Resume, suspend, kill operations
  • Batch operations (kill all, resume all)
  • Detailed job information with ps and lsof
  • Signal control (TERM, KILL, STOP)
  • Error handling and validation
  • User-friendly colored output
Production Applications:
  • System administration task management
  • Development environment workflow
  • Training tool for job control concepts
  • Troubleshooting background processes
  • Managing long-running maintenance tasks

Job States Reference

State Description How to Achieve
Running Job is currently executing Start with & or use bg
Stopped Job is suspended (paused) Press Ctrl+Z or use kill -STOP
Done Job completed successfully (exit 0) Job finishes normally
Exit Job terminated with error (exit non-zero) Job fails or returns error code
Terminated Job killed by signal Use kill command
Killed Job force-killed (SIGKILL) Use kill -9

Additional Information

Job Specification Shortcuts:
  • %1, %2 - Job by number
  • %% or %+ - Current job (most recent)
  • %- - Previous job
  • %string - Job starting with "string"
  • %?string - Job containing "string"
Job Limitations:
  • Jobs are shell-specific - different terminals have different jobs
  • Jobs disappear when shell exits (unless disowned or nohup)
  • Can't manage other users' jobs
  • Job numbers are reused after jobs finish
  • Some shells limit number of jobs (typically ~100)
Common Job Control Patterns:
# Start job, realize you want it in background command # Press Ctrl+Z bg # Temporarily suspend job to check something # Press Ctrl+Z # Do your check fg # Start multiple jobs, wait for all job1 & job2 & job3 & wait # Kill all background jobs before logout kill $(jobs -p) # Run job immune to hangup nohup long_job & disown
Behind the Scenes:
  • Jobs are tracked via process groups (PGID)
  • Foreground job has control of terminal
  • Background jobs can't read from terminal
  • SIGHUP sent to jobs when shell exits
  • Job control requires terminal support (not in pipes)
When to Use Jobs vs Other Tools:
  • jobs/fg/bg: Short-term interactive tasks
  • nohup: Long-running tasks, survive logout
  • screen/tmux: Persistent interactive sessions
  • systemd services: Permanent background services
  • cron: Scheduled recurring tasks

Quick Reference

Task Command
List all jobs jobs
List with PIDs jobs -l
Only running jobs jobs -r
Only stopped jobs jobs -s
Start job in background command &
Suspend current job Ctrl+Z
Resume in background bg %1
Bring to foreground fg %1
Kill job kill %1
Wait for all jobs wait