Juggling Multiple Jobs: Handling Concurrent Tasks In Interactive Bash
2026-11-05
Tags: Programming, Bash
Have you ever launched a long-running command, only to realise, oops, there's actually more I wanted to do? Now you have to open a new instance of your terminal, cd into the directory you were in, it's a whole hassle. But it doesn't have to be! Using jobs, you can manage multiple concurrent processes from a single shell.
Terminology
- Job: A process that is spawned by a shell
- Running: A job that is actively doing work
- Stopped: A job that has been paused, and may resume
- Terminated: A job that has been terminated, and may not resume
- Disowned: A job that was spawned by a shell, but is no longer visible to it
Spawning a job in the foreground
Jobs are spawned in the foreground by default when a command is run. The terminal will attach to the job and detach when the job is completed or terminated.
Spawning a job in the background
Jobs can be put into the background immedately by using & at the end. For example, updatedb & will run updatedb in the background.
You can add more commands directly after &, and they will run as normal. For example, updatedb & echo "updatedb started".
To run multiple commands in sequence in the background, there are two options: wrap them in () to create a sub-shell (so variable changes will not persist in this scope), or in {} to create a command group (so changes do persist). For example:
( updatedb; echo "updatedb complete"; ) & echo "updatedb started"
Listing jobs
The jobs command presents a list of jobs beloning to this terminal.
[1] Running sleep 10
[2]- Stopped sleep 100
[3]+ Stopped sleep 1000
The number in the brackets is the job ID. Once a job is assigned an ID, its ID will never change. The ID will always be the next number after the highest ID of a running or stopped job (starting at 1 for the first job).
+ denotes the current job, that is, the default for fg and bg to act upon. - is the previous job, next in line to become the current job.
The text to the right is the invocation of the command as it appeared in the shell.
Stopping a foreground job
For jobs in the foreground, use CtrlZ to immediately stop the job. This will output the job to the console:
[1]+ Stopped sleep 10
Stopping a background job
To stop a background job, use kill -STOP %i, where i is the job ID (required).
Important
The % sign specifies that the following number is a job ID, not a process ID.
Resuming a stopped job
fg %i will bring a job to the foreground and resume it if it is stopped. If %i is omitted, it implies the most recently stopped job.
To resume a stopped job in the background, use bg %i - again, omitting %i implies the most recent job. This will output the job line.
Monitoring background jobs
A job in the background will report its status after completion after the next time the terminal flushes its input; use set -b to report immediately (set +b to restore behaviour).
Terminating a job
To terminate a foreground job, use CtrlC. This will simply stop the job.
To terminate a background job, use kill %i. You can send a signal from 1-9 using kill -n %i if the initial kill doesn't work. This will the job line.
Disowning a job
To disown a job, use disown %i.
By default, closing the terminal will still send SIGHUP (the hang-up signal), which some programs may take as an indicator to exit; add the -h flag to avoid this.
Notes on output streams
The job lines output by bg and kill aren't sent to /dev/stdout or /dev/stderr. They go directly to the terminal and cannot be redirected.
Breaking it all
set +m disables job control. & syntax and kill still work, but CtrlZ, fg and bg all stop working.