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 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
| Ctrl+Z | Suspend (stop) current foreground job |
| Ctrl+C | Terminate (kill) current foreground job |
| Ctrl+D | Send EOF (end of file) to current job |
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
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], [2], [3]: Job numbers
- +: Current job (most recently started or stopped)
- -: Previous job
- Running/Stopped: Job state
- Command: The command that was executed
%1, %2, etc. Special references: %+ or %% (current job), %- (previous job), %?string (job with 'string' in command).
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
-l option includes process IDs (PIDs) in the output. This is useful when you need to:
- Monitor the process with
psortop - 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
# Kill all background jobs by PID
for pid in $(jobs -p); do
kill $pid
done
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
- -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.
# Resume all stopped jobs in background
for job in $(jobs -s | awk '{print $1}' | tr -d '[]+-'); do
bg %$job
done
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
# Resume it in background
bg %1
# Verify it's running in background
jobs
# 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
# Bring job 2 to foreground
fg %2
# Or bring previous job to foreground
fg %-
# Or bring current job to foreground
fg %+
- Ctrl+Z: Suspends current foreground job
- jobs: Check what jobs exist
- bg %n: Resume job n in background
- fg %n: Bring job n to foreground
bg (no arguments) to resume it in the background. Similarly, fg brings the current job (%) to foreground.
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
# Kill job 2 using job specification
kill %2
# Wait a moment and check
jobs
# 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
- 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
- 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)
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
# Wait 6 seconds for job 1 to complete
sleep 6
# Check for changes only
jobs -n
# Check again - nothing new
jobs -n
# (No output - no new changes)
# Check all jobs - will show notification has been seen
jobs
-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.
#!/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
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
# Disown the job (remove from job table)
disown %1
# Check jobs - it's gone from the job table
jobs
# But the process still exists
ps aux | grep long_running_script
- 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
# Start with nohup from the beginning
nohup ./long_running_script.sh &
# Output goes to nohup.out by default
# Process will survive logout automatically
- disown: Use after starting job normally
- nohup: Use when starting job (better practice)
- screen/tmux: Best for interactive long-running tasks
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
- Running multiple tasks in parallel
- Limiting concurrent jobs with
jobs -r | wc -l - Using
waitto wait for all jobs to complete - Monitoring job completion for status reporting
- Parallel data processing
- Batch file conversions
- Multi-server deployments
- Distributed testing
- Concurrent database queries
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
- Real-time job status updates
- Visual dashboard with clear screen
- Status categorization and counting
- Visual indicators for job states
- Continuous monitoring loop
- Add CPU/memory usage per job
- Show elapsed time per job
- Log job completions to file
- Send notifications on completion
- Color-code by status
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
- 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
- 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
%1,%2- Job by number%%or%+- Current job (most recent)%-- Previous job%string- Job starting with "string"%?string- Job containing "string"
- 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)
# 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
- 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)
- 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 |