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.