How To Use Bash’s Job Control to Manage Foreground and Background Processes
Table of Contents
Introduction #
In a previous tutorial, we discussed how the ps
, kill
, and nice
commands can be used to control processes on your system. This guide highlights how bash
, the Linux system, and your terminal come together to offer process and job control.
This article will focus on managing foreground and background processes and will demonstrate how to leverage your shell’s job control functions to gain more flexibility in how you run commands.
Prerequisites #
To follow along with this guide, you will need access to a computer running the bash
shell interface. bash
is the default shell on many Linux-based operating systems, and it is available on many Unix-like operating systems, including macOS. Note that this tutorial was validated using a Linux virtual private server running Ubuntu 20.04.
If you plan to use a remote server to follow this guide, we encourage you to first complete our Initial Server Setup guide. Doing so will set you up with a secure server environment — including a non-root user with sudo
privileges and a firewall configured with UFW — which you can use to build your Linux skills.
Managing Foreground Processes #
Most processes that you start on a Linux machine will run in the foreground. The command will begin execution, blocking use of the shell for the duration of the process. The process may allow user interaction or may just run through a procedure and then exit. Any output will be displayed in the terminal window by default. We’ll discuss the basic way to manage foreground processes in the following subsections.
Starting a Process #
By default, processes are started in the foreground. This means that until the program exits or changes state, you will not be able to interact with the shell.
Some foreground commands exit very quickly and return you to a shell prompt almost immediately. For instance, the following command will print Hello World
to the terminal and then return you to your command prompt:
echo "Hello World"
Hello World
Other foreground commands take longer to execute, blocking shell access for their duration. This might be because the command is performing a more extensive operation or because it is configured to run until it is explicitly stopped or until it receives other user input.
A command that runs indefinitely is the top
utility. After starting, it will continue to run and update its display until the user terminates the process:
top
You can quit top
by pressing q
, but some other processes don’t have a dedicated quit function. To stop those, you’ll have to use another method.
Terminating a Process #
Suppose you start a simple bash
loop on the command line. As an example, the following command will start a loop that prints Hello World
every ten seconds. This loop will continue forever, until explicitly terminated:
while true; do echo "Hello World"; sleep 10; done
Unlike top
, loops like this have no “quit” key. You will have to stop the process by sending it a signal. In Linux, the kernel can send signals to running processes as a request that they exit or change states. Linux terminals are usually configured to send the “SIGINT” signal (short for “signal interrupt”) to current foreground process when the user presses the CTRL + C
key combination. The SIGINT signal tells the program that the user has requested termination using the keyboard.
To stop the loop you’ve started, hold the CTRL
key and press the C
key:
CTRL + C
The loop will exit, returning control to the shell.
The SIGINT signal sent by the CTRL + C
combination is one of many signals that can be sent to programs. Most signals do not have keyboard combinations associated with them and must instead be sent using the kill
command, which will be covered later on in this guide.
Suspending Processes #
As mentioned previously, foreground process will block access to the shell for the duration of their execution. What if you start a process in the foreground, but then realize that you need access to the terminal?
Another signal that you can send is the “SIGTSTP” signal. SIGTSTP is short for “signal terminal stop”, and is usually represented as signal number 20. When you press CTRL + Z
, your terminal registers a “suspend” command, which then sends the SIGTSTP signal to the foreground process. Essentially, this will pause the execution of the command and return control to the terminal.
To illustrate, use ping
to connect to google.com
every 5 seconds. The following command precedes the ping
command with command
, which will allow you to bypass any shell aliases that artificially set a maximum count on the command:
command ping -i 5 google.com
Instead of terminating the command with CTRL + C
, press CTRL + Z
instead. Doing so will return output like this:
[1]+ Stopped ping -i 5 google.com
The ping
command has been temporarily stopped, giving you access to a shell prompt again. You can use the ps
process tool to show this:
ps T
PID TTY STAT TIME COMMAND
26904 pts/3 Ss 0:00 /bin/bash
29633 pts/3 T 0:00 ping -i 5 google.com
29643 pts/3 R+ 0:00 ps t
This output indicates that the ping
process is still listed, but that the “STAT” column has a “T” in it. Per the ps
man page, this means that a job that has been “stopped by [a] job control signal”.
This guide will outline how to change process states in greater depth, but for now you can resume execution of the command in the foreground again by typing:
fg
Once the process has resumed, terminate it with CTRL + C
:
Managing Background Processes #
The main alternative to running a process in the foreground is to allow it to execute in the background. A background process is associated with the specific terminal that started it, but does not block access to the shell. Instead, it executes in the background, leaving the user able to interact with the system while the command runs.
Because of the way that a foreground process interacts with its terminal, there can be only a single foreground process for every terminal window. Because background processes return control to the shell immediately without waiting for the process to complete, many background processes can run at the same time.
Starting Processes #
You can start a background process by appending an ampersand character (&
) to the end of your commands. This tells the shell not to wait for the process to complete, but instead to begin execution and to immediately return the user to a prompt. The output of the command will still display in the terminal (unless redirected), but you can type additional commands as the background process continues.
For instance, you can start the same ping
process from the previous section in the background by typing:
command ping -i 5 google.com &
The bash
job control system will return output like this:
[1] 4287
You’ll then receive the normal output from the ping
command:
PING google.com (74.125.226.71) 56(84) bytes of data.
64 bytes from lga15s44-in-f7.1e100.net (74.125.226.71): icmp_seq=1 ttl=55 time=12.3 ms
64 bytes from lga15s44-in-f7.1e100.net (74.125.226.71): icmp_seq=2 ttl=55 time=11.1 ms
64 bytes from lga15s44-in-f7.1e100.net (74.125.226.71): icmp_seq=3 ttl=55 time=9.98 ms
However, you can also type commands at the same time. The background process’s output will be mixed among the input and output of your foreground processes, but it will not interfere with the execution of the foreground processes.
Listing Background Processes #
To list all stopped or backgrounded processes, you can use the jobs
command:
jobs
If you still have the previous ping
command running in the background, the jobs
command’s output will be similar to this:
[1]+ Running command ping -i 5 google.com &
This indicates that you currently have a single background process running. The [1]
represents the command’s job spec or job number. You can reference this with other job and process control commands, like kill
, fg
, and bg
by preceding the job number with a percentage sign. In this case, you’d reference this job as %1
.
Stopping Background Processes #
You can stop the current background process in a few ways. The most straightforward way is to use the kill
command with the associated job number. For instance, you can kill your running background process by typing:
kill %1
Depending on how your terminal is configured, either immediately or the next time you hit ENTER
, the job termination status will appear in your output:
[1]+ Terminated command ping -i 5 google.com
If you check the jobs
command again, there won’t be any current jobs.
Changing Process States #
Now that you know how to start and stop processes in the background, you can learn about changing their state.
This guide already outlined one way to change a process’s state: stopping or suspending a process with CTRL + Z
. When processes are in this stopped state, you can move a foreground process to the background or vice versa.
Moving Foreground Processes to the Background #
If you forget to end a command with &
when you start it, you can still move the process to the background.
The first step is to stop the process with CTRL + Z
again. Once the process is stopped, you can use the bg
command to start it again in the background:
bg
You will receive the job status line again, this time with the ampersand appended:
[1]+ ping -i 5 google.com &
By default, the bg
command operates on the most recently-stopped process. If you’ve stopped multiple processes in a row without starting them again, you can reference a specific process by its job number to move the correct process to the background.
Note that not all commands can be backgrounded. Some processes will automatically terminate if they detect that they have been started with their standard input and output directly connected to an active terminal.
Moving Background Processes to the Foreground #
You can also move background processes to the foreground by typing fg
:
fg
This operates on your most recently backgrounded process (indicated by the +
in the jobs
command’s output). It immediately suspends the process and puts it into the foreground. To specify a different job, use its job number:
fg %2
Once a job is in the foreground, you can kill it with CTRL + C
, let it complete, or suspend and move it to the background again.
Dealing with SIGHUPs #
Whether a process is in the background or in the foreground, it is rather tightly tied with the terminal instance that started it. When a terminal closes, it typically sends a SIGHUP signal to all of the processes (foreground, background, or stopped) that are tied to the terminal. This signals for the processes to terminate because their controlling terminal will shortly be unavailable.
There may be times, though, when you want to close a terminal but keep the background processes running. There are a number of ways of accomplishing this. One of the more flexible ways is to use a terminal multiplexer like screen
or tmux
. Another solution is to use a utility that provides the detach functionality of screen
and tmux
, like dtach
.
However, this isn’t always an option. Sometimes these programs aren’t available or you’ve already started the process you need to continue running. Sometimes these could even be overkill for what you need to accomplish.
nohup
>Using nohup
#
If you know when starting the process that you will want to close the terminal before the process completes, you can start it using the nohup
command. This makes the started process immune to the SIGHUP signal. It will continue running when the terminal closes and will be reassigned as a child of the init system:
nohup ping -i 5 google.com &
This will return a line like the following, indicating that the output of the command will be written to a file called nohup.out
:
nohup: ignoring input and appending output to ‘nohup.out’
This file will be placed in your current working directory if writeable, but otherwise it will be placed in your home directory. This is to ensure that output is not lost if the terminal window is closed.
If you close the terminal window and open another one, the process will still be running. You will not find it in the output of the jobs
command because each terminal instance maintains its own independent job queue. Closing the terminal will cause the ping
job to be destroyed even though the ping
process is still running.
To kill the ping
process, you’ll have to find its process ID (or “PID”). You can do that with the pgrep
command (there is also a pkill
command, but this two-part method ensures that you are only killing the intended process). Use pgrep
and the -a
flag to search for the executable:
pgrep -a ping
7360 ping -i 5 google.com
You can then kill the process by referencing the returned PID, which is the number in the first column:
kill 7360
You may wish to remove the nohup.out
file if you don’t need it anymore.
disown
>Using disown
#
The nohup
command is helpful, but only if you know you will need it at the time you start the process. The bash
job control system provides other methods of achieving similar results with the built-in disown
command.
The disown
command, in its default configuration, removes a job from the jobs queue of a terminal. This means that it can no longer be managed using the job control mechanisms discussed previously in this guide, like fg
, bg
, CTRL + Z
, CTRL + C
. Instead, the job will immediately be removed from the list in the jobs
output and no longer associated with the terminal.
The command is called by specifying a job number. For instance, to immediately disown job 2, you could type:
disown %2
This leaves the process in a state not unlike that of a nohup
process after the controlling terminal has been closed. The exception is that any output will be lost when the controlling terminal closes if it is not being redirected to a file.
Usually, you don’t want to remove the process completely from job control if you aren’t immediately closing your terminal window. You can pass the -h
flag to the disown
process instead in order to mark the process to ignore SIGHUP signals, but to otherwise continue on as a regular job:
disown -h %1
In this state, you could use normal job control mechanisms to continue controlling the process until closing the terminal. Upon closing the terminal, you will, once again, be stuck with a process with nowhere to output if you didn’t redirect to a file when starting it.
To work around that, you can try to redirect the output of your process after it is already running. This is outside the scope of this guide, but this post provides an explanation of how you could do that.
huponexit
Shell Option>Using the huponexit
Shell Option
#
bash
has another way of avoiding the SIGHUP problem for child processes. The huponexit
shell option controls whether bash
will send its child processes the SIGHUP signal when it exits.
Note: The huponexit
option only affects the SIGHUP behavior when a shell session termination is initiated from within the shell itself. Some examples of when this applies are when the exit
command or CTRL + D
is pressed within the session.
When a shell session is ended through the terminal program itself (through closing the window, etc.), the command huponexit
will have no affect. Instead of bash
deciding on whether to send the SIGHUP signal, the terminal itself will send the SIGHUP signal to bash
, which will then correctly propagate the signal to its child processes.
Despite the aforementioned caveats, the huponexit
option is perhaps one of the easiest to manage. You can determine whether this feature is on or off by typing:
shopt huponexit
To turn it on, type:
shopt -s huponexit
Now, if you exit your session by typing exit
, your processes will all continue to run:
exit
This has the same caveats about program output as the last option, so make sure you have redirected your processes’ output prior to closing your terminal if this is important.
Conclusion #
Learning job control and how to manage foreground and background processes will give you greater flexibility when running programs on the command line. Instead of having to open up many terminal windows or SSH sessions, you can often get by with stopping processes early or moving them to the background as needed.