1. Overview

Whenever we run a command in bash, it’s run as a job. Understanding how to manage jobs means we can better control our shell.

In this tutorial, we’ll explore what jobs are in bash, and how they’re related to processes. We’ll learn how to start, pause, resume, and run them in the background.

2. Processes and Jobs in bash Shell

A process in Linux means any running program. For example, when we run ls, it’s run as a process. If we search for a filename, by using ls -l and piping the result to grep, we’re actually running two processes:

$ ls -l | grep script
-rwxr-xr-x 1 ubuntu users 18 Aug  2 21:44 myscript.sh

Since one command may cause several processes to run, it’s represented as a job in the bash shell and is the logical grouping of processes run from a single command or script.

3. Pausing and Resuming Jobs

In our ls example, we usually expect execution to complete immediately and return us to the shell prompt. But if a command takes a while, we need to wait for it to terminate. And, if we want to run another command while we’re waiting, we would need to open another shell.

Let’s say we ran a find command and it has already displayed the file we think we’re looking for. We could press Ctrl+C to stop the command. But, maybe it would be preferable to pause find so we can double-check, and resume it if necessary.

We can pause the current job with Ctrl+Z:

$ find . -name "*.java"
./code/com/my/package/Main.java
^Z
[1]+  Stopped                 find . -name "*.java"

Here, we ran the find command and pressed Ctrl+Z (^Z) after we saw some output. Pausing the job caused a prompt showing us the job number [1] and a message that it has been Stopped. We can refer to the paused job by this job number as we’ll see later.

We should note that even though the shell says the job is Stopped, it is, in fact, paused – not terminated – and we’ll be able to resume it.

When we pause the find command, we’ll get back to the shell prompt to run further commands. If we wish to resume the find operation, we can use the fg command:

$ fg
find . -name "*.java"

Here, the last paused job is resumed and runs in the foreground, tying up the shell until it is completed or we choose to stop or pause it again.

4. Job Control

To understand what more we can do with jobs, let’s start two long-running jobs and press Ctrl+Z after each to pause them:

$ make -j4
^Z
[1]+  Stopped                 make -j4
$ find . -name "*.java"
^Z
[2]+  Stopped                 find . -name "*.java"

4.1. List Jobs

We list jobs using the jobs command:

$ jobs
[1]-  Stopped                 make -j4
[2]+  Stopped                 find . -name "*.java"

The jobs command marks the last paused job with the + sign and the immediate previous stopped job with the sign. If we use fg and other job commands without a job number, the last paused job is implied.

4.2. Resume a Specific Job in the Foreground

The fg command allows us to resume a specific job by its job number. The job’s name is output when it starts:

$ fg %1
make -j4

4.3. Run the Job in the Background

We can also resume a job in the background. A background job runs without tying up the shell. It doesn’t have access to the shell input, but it can still output to the shell:

$ bg %1
[1]+ make -j4 &

As with fg, we can omit the job number in the bg command to resume the last paused job in the background:

$ bg
[2]+ find . -name "*.java" &

4.4. Start a Job in the Background

We can also start a job directly as a background process by adding the ampersand (&) character to the end of its command line:

$ find . -name "*.java" &
[1] 1726

5. Message: There Are Stopped Jobs

Sometimes when we try to exit the shell by pressing Ctrl+D or by using logout, we may receive an error message:

$ logout
There are stopped jobs.

When bash shows this message, it also prevents logout.

This is because bash doesn’t allow us to exit the shell while there are paused jobs. The current shell’s process manages its jobs. When we pause a job, it’s left in an incomplete state. If we exit the shell with jobs paused, we might lose some critical data.

So, we need to take care of these paused jobs before we can exit the shell.

5.1. List Jobs to Handle

To decide what to do with our jobs, let’s list them:

$ jobs
[1]-  Stopped                 python
[2]+  Stopped                 find . -name "*.java" > javafiles.txt

Depending on these jobs, we may wish to keep them running or kill them.

5.2. Keeping the Job Running

We’ve already seen how to keep a job running in the background:

$ bg %2
[2]+ find . -name "*.java" > javafiles.txt &

As we earlier noted about background jobs, this job can still output to the shell while running in the background. If we exit the shell now, the job might terminate anyway if it tries to output to the shell as the shell doesn’t exist anymore. It can also get killed when it receives certain signals.

To avoid this, we can remove the background job from the current shell using the disown command:

$ disown %2
$ logout

This will make sure the background job keeps running in the background even after the shell exits.

5.3. Killing the Job

Alternatively, we can kill a job we don’t need by using the kill command:

$ kill %1
[1]+  Stopped                 python

kill allows us to use %1 as the job number, though it is also commonly used to terminate processes by their process id.

6. Conclusion

In this article, we saw how the bash shell treats every command we run, as a job – even if it contains multiple processes.

We learned how to list, pause, resume, and kill jobs.

We also looked at how to put a job in the background and keep it running even if the shell exits.