Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Jobs and job control in the shell

By updated

Improve this page

A job that blocks your terminal is not inevitable. Ctrl+Z suspends it, bg resumes it in the background, fg brings it back in front of you. Job control is the shell's native mechanism to juggle several processes without opening a new terminal. The behaviours described here were checked with Bash 5.2 on Debian 13, Ubuntu 24.04 and AlmaLinux 10, in a pseudo-terminal.

What you will learn

  • Suspend a running job with Ctrl+Z
  • List the active jobs with jobs -l
  • Move a job to the background with bg
  • Bring a job back to the foreground with fg
  • Refer to a job by its number with %n
  • Kill a job with kill %n
  • Detach a job for good with disown

In which context?

This skill helps as soon as a command takes time and blocks your prompt. A job is a command started from your shell and tracked by it for as long as the session lasts:

  • a long compilation or copy,
  • an editor you want to put aside,
  • a process you started in the foreground by mistake,
  • a job you want to survive the terminal closing.

Start a process in the background

The & operator puts the process in the background right from launch. The shell prints the job number and the PID, then gives the prompt back immediately:

Terminal window
sleep 60 &
[1] 1842

[1] is the job number, local to your shell: it is what fg, bg and kill %1 expect. 1842 is the PID, the identifier of the process for the whole system. The special variable $! holds the PID of the last job started in the background: capture it right away, the next & will overwrite it.

Terminal window
# Capture the PID of the last job started in the background
sleep 60 &
MYPID=$!
echo "PID of the job: $MYPID"

List the jobs: jobs

jobs lists the jobs of the current session only, with their number and state; the -l option adds the PID. In the example below, sleep 60 runs in the background and vim has been suspended:

Terminal window
jobs -l
[1]- 2075842 Running sleep 60 &
[2]+ 2075843 Stopped vim file.txt

The current job (+) is the one fg and bg target without an argument: the last one suspended or started in the background, and Bash gives priority to a suspended job. Measured with Bash 5.2: a suspended sleep 200 stays marked + even when a sleep 201 & is started after it. Columns:

ElementMeaning
[1]Job number
+Current job (the one fg resumes by default)
-Previous job
Running / StoppedState
sleep 60 &Command

Suspend a job: Ctrl+Z

While a command runs in the foreground, Ctrl+Z sends it the suspend signal SIGTSTP and gives the prompt back:

vim file.txt
^Z
[1]+ Stopped vim file.txt

The process is suspended (state T in ps): it no longer uses the CPU but stays in memory, ready to resume exactly where it stopped. This is what makes Ctrl+Z handy to put an editor aside while you run a command.

Resume in the background: bg

bg (background) makes a suspended job resume in the background: it keeps working, and you keep the prompt. It is the way to rescue a long command started without & by mistake, without losing what has already been done.

Terminal window
bg %1

The suspended job [1] resumes running in the background. Without an argument, bg resumes the current job (marked +):

Terminal window
# Suspend then resume in the background
sleep 60
^Z
bg

One limit, measured with cat &: a program that reads the terminal cannot run in the background. It receives the SIGTTIN signal and stops again, and jobs then shows it as Stopped (tty input).

Bring back to the foreground: fg

fg (foreground) gives the foreground back to a job: your terminal is blocked again until it ends, but you regain its interactivity, essential for an editor you put aside.

Terminal window
fg %1
Terminal window
fg # resumes the current job (+)
fg %2 # resumes job number 2

A job that no longer exists answers fg: %2: no such job: it ended in the meantime, or the number is wrong.

Refer to jobs by name

Besides %n, you can name a job by the start of its command, which saves checking jobs before each action. The reference must stay unambiguous: if two jobs start with the same word, Bash refuses to pick one.

Terminal window
fg %vim # resumes the job whose command starts with "vim"
kill %sleep # kills the job whose command starts with "sleep"

Kill a job by its number

kill accepts a job number preceded by % as well as a PID. Without an option, it sends SIGTERM, the clean stop request; -9 sends SIGKILL, which the program cannot catch.

Terminal window
kill %1 # sends SIGTERM to job 1
kill -9 %2 # sends SIGKILL to job 2

To clean up every job of the session at once, jobs -p provides only their PIDs, which kill receives in a single line:

Terminal window
kill $(jobs -p)

Wait for a job to end: wait

wait blocks the shell until the named process ends, then puts its exit code in $?. It is the synchronisation building block of scripts that start several jobs in parallel.

Terminal window
sleep 3 &
MYPID=$!
echo "Waiting..."
wait $MYPID
echo "Finished with code: $?"

Without an argument, wait waits for every active background job of the shell.

Detach a job from the shell: disown

disown removes a job from the job table: the shell will no longer relay the SIGHUP it receives on disconnection to it, so the job survives the closing of the session. It accepts a job number (%1) as well as a PID (checked).

Terminal window
sleep 120 &
MYPID=$!
disown $MYPID
jobs # the job no longer appears
ps -p $MYPID # but the process is still alive

A plain exit does not kill a job that is running in the background, by the way: Bash's huponexit option is disabled by default (checked with shopt huponexit). The danger comes from the SIGHUP the shell receives when the SSH connection or the terminal closes: it then relays it to its jobs, and measured on Debian 13 and AlmaLinux 10, a sleep started with & dies with it.

Two variants cover the other needs: -h protects the job without removing it from the table, which keeps fg possible, and -a handles every job at once.

Terminal window
disown -h $! # protects the job from SIGHUP without removing it from jobs
disown -a # detaches every job

Shortcut summary

This table gathers the moves of the page in the order they are used: start, suspend, resume, then detach. Keep it at hand during your first sessions.

ActionShortcut / Command
Suspend (foreground → stopped)Ctrl+Z
Resume in the backgroundbg %n
Bring back to the foregroundfg %n
Start directly in the backgroundcommand &
Capture the PID of the last background job$!
List the jobsjobs -l
Kill by job numberkill %n
Wait for the endwait $PID
Detach from the shelldisown $PID

Troubleshooting

The symptoms below almost all come from a confusion between the three states of a job (foreground, background, suspended) or between the job number and the PID. Always start with jobs -l, which shows both.

SymptomLikely causeSolution
After bg, the job goes back to Stopped (tty input)The program reads the terminal: in the background, it receives SIGTTIN and stops (measured with cat &)Bring it back with fg, or restart it with redirected input (< /dev/null)
fg %2 answers fg: %2: no such jobThe job has ended, or the number is wrongCheck with jobs -l and use the right number
The job disappears on disconnection despite &SIGHUP relayed by the shell when the session closesUse disown -h or nohup
kill %1 does not end the jobProcess in state D or ignoring SIGTERMkill -9 %1; if state D, diagnose the blocked I/O
$! empty in the scriptNo command has been started with & yetAdd & at the end of the command

Key points

  • & puts a process in the background at launch; Ctrl+Z suspends a running process.
  • jobs -l lists the jobs with their number and PID; + marks the current job, preferably a suspended one.
  • bg %n resumes, fg %n brings back, kill %n kills, always by job number.
  • $! holds the PID of the last job started in the background.
  • An exit does not kill the running jobs; the SIGHUP of a disconnection does.
  • disown detaches a job already started so that it survives the session closing.

Knowledge check

Check that the essentials of this guide stuck. The questions cover only what has just been explained here.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
6 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn