Lab 07: Background Jobs & nohup

Time: 30 minutes | Level: Practitioner | Docker: docker run -it --rm ubuntu:22.04 bash


Overview

When you run a command in a terminal, it owns your prompt until it finishes. Background jobs let you reclaim the terminal, run multiple tasks simultaneously, and keep processes alive after logout. This lab covers job control, nohup, disown, and parallel execution patterns.


Step 1: Running Commands in the Background with &

Append & to any command to run it in the background immediately.

# Run a job in the background
sleep 30 &
echo "Background PID: $!"

# Run multiple background jobs
sleep 60 &
sleep 60 &
echo "Two more jobs started"

# List all background jobs
jobs

📸 Verified Output:

💡 $! is your friend: After launching a background job, $! holds its PID. Save it to a variable (MY_PID=$!) so you can wait on it, kill it, or check its status later.


Step 2: jobs, fg, and bg — Managing Job Control

📸 Verified Output:

💡 Job notation: %1 = job number 1, %+ or %% = current job (most recent), %- = previous job. fg with no argument brings the current (%+) job to the foreground.


Step 3: Ctrl+Z — Suspending a Foreground Process

Ctrl+Z sends SIGTSTP to the foreground process, suspending it.

📸 Verified Output:

💡 T state = stopped: When you see T in the STAT column, the process is suspended (frozen). It uses no CPU but stays in memory. fg or bg resumes it. Don't leave processes stuck in T state — they hold resources.


Step 4: nohup — Surviving Logout

Normally, when you close a terminal, the shell sends SIGHUP to all its child processes — killing them. nohup makes a command immune to SIGHUP.

📸 Verified Output:

💡 nohup.out: If you don't redirect output, nohup writes to nohup.out in the current directory. Always redirect explicitly: nohup ./script.sh >> /var/log/script.log 2>&1 &. This avoids surprise large files and makes logs findable.


Step 5: disown — Releasing Jobs from the Shell

disown removes a job from the shell's job table, so closing the terminal won't kill it — without needing nohup.

📸 Verified Output:

💡 nohup vs disown: Use nohup cmd & when starting fresh — it redirects output too. Use disown for processes already running that you forgot to nohup. Both achieve logout-survival, but disown doesn't handle stdio redirection.


Step 6: wait — Synchronizing with Background Jobs

wait blocks until background jobs complete. Essential for scripts that parallelize work.

📸 Verified Output:

💡 Exit code from wait: wait $PID returns the exit code of that process. Use this to check if background jobs succeeded: wait $PID && echo "Success" || echo "Failed with $?".


Step 7: Parallel Execution with &

One of the most powerful shell patterns: run multiple tasks simultaneously.

📸 Verified Output:

💡 Limit parallelism: Spawning too many background jobs (thousands) can overwhelm the system. Use a semaphore pattern: (( $(jobs -r | wc -l) >= MAX_JOBS )) && wait -n. Or use xargs -P N for controlled parallelism: printf '%s\n' file{1..100} | xargs -P 8 -I{} process_file.sh {}.


Step 8: Capstone — Parallel Backup Script

Scenario: You have three directories to back up. Instead of doing them sequentially, parallelize them for speed, then verify all succeeded.

📸 Verified Output:

💡 Production pattern: Wrap parallel jobs with nohup ... & and save PIDs to a file (echo $! >> /tmp/job.pids). On the next check, read the PID file and use wait or poll /proc/PID to verify completion. This survives shell restarts.


Summary

Concept
Command
Example

Run in background

cmd &

./backup.sh &

List background jobs

jobs

jobs -l (show PIDs)

Bring to foreground

fg %N

fg %2

Send to background

bg %N

bg %1

Suspend foreground

Ctrl+Z

Survive logout

nohup cmd &

nohup server.py >> app.log 2>&1 &

Release from shell

disown PID

disown 4521

Wait for job(s)

wait [PID]

wait $PID1 $PID2

Parallel execution

cmd1 & cmd2 & wait

See Step 7

Get last background PID

$!

MY_PID=$!

Last updated