Job Control: the Bash Feature You Only Think You Don't Need
There are basically three types of people in the world: those who know little or nothing about bash job control, those who know enough to believe that it's nothing that they would ever use, and those who can just skim the rest of this post. Now, don't get me wrong, I'm not saying that bash's job control is going to change your world, but there are a couple simple everyday scenarios where job control can be useful, and often, it even can eliminate an "oh crap" moment.
So, what are jobs, and what is job control? Any script or program that you run from a bash prompt is a job. If the script or program starts other processes (for example, a pipeline in a bash script), those processes are part of the same job as the main script or program. Job control is what allows you to suspend jobs, move jobs from the background to the foreground, and vice versa, from the foreground to the background. Running a script with script & creates a background job. Running a script with just script creates a foreground job.
Job control consists of the following commands:
- The fg command moves a background job into the foreground.
- The bg command moves a suspended foreground job into the background.
- The jobs command shows the current list of jobs.
- The kill command can kill jobs or send signals to them.
- The disown command removes a job from the list of jobs (without killing it).
- A foreground job can be suspended by typing ^Z (Control-Z). A suspended job is temporarily stopped.
Until recently, for me, all of that just evoked images of main frame operators and big-time sysadmins; it didn't seem like it would be of much use to me. But it turns out that job control actually can help you day to day.
For example, say you're editing something with vi and you decide you want to look at another file. You can do this by suspending your first vi session and starting another one:
$ vi somefile ^Z + Stopped vi somefile $ vi otherfile
Note that suspending vi will causes its "window" to disappear, not the bash window, just "full screen" output of vi.
If you want to return to the first file for a moment, you can suspend the second vi session and bring the first one back to the foreground, which restores the vi window for the first session:
^Z + Stopped vi otherfile $ jobs - Stopped vi somefile + Stopped vi otherfile # Bring fist vi session to the foreground. $ fg 1
I know all the vi users out there are screaming about just splitting the window and opening both files in the same vi session. But unless you're a vi power user, that means first you have to go to the internet, search for how to split a window in vi, get distracted reading about the most recent Trump Tweet, and well, it's probably just going to go downhill from there....
Of course, you also can just open another shell prompt and run a new copy of vi, or whatever program you want to run, in the new shell. And sometimes that makes sense, but sometimes job control is just quicker and easier.
Suspending and moving programs between the foreground and background also works for GUI applications that you start from the command line. This capability is mainly useful for getting a GUI program out of the foreground. For example, often while working at the command line I want to open a GUI editor or maybe a spreadsheet to look at something at the same time as the shell prompt. Rather than use the desktop menu to start the program, I just type its name at the command line, for example, kate for the KDE GUI Editor:
$ ls # ... bash stuff # Now open a file with kate, the KDE GUI editor $ kate somefile
And here is one of those "oh crap" moments, because what I really wanted to do was type kate somefile & to start kate in the background without tying up my terminal. Now I have to quit kate and re-run it with the & or I have to open another terminal to continue whatever I was doing in the shell (and lose access to my current bash history in the process). But with job control, I don't have to do either of those things. I just suspend kate (which is in the foreground) and move it to the background:
^Z + Stopped kate $ bg 3 + kate & $ # and now the bash prompt returns
When you suspend a GUI application, its window doesn't disappear, but if you try to type in it or use the mouse with it, you'll see that it does not respond.
You've probably noticed the number arguments to the "fg" and "bg" commands. These are the job numbers that you want to act move between the foreground and the background. When you use the jobs command, the number in square brackets is the job number:
$ jobs + Stopped vi somefile + Stopped vi otherfile + Running kate &
Often you don't even need to specify a job number, since the one you want is usually the one you just suspended or the one you just restarted, and bash refers to this as the "current job". Bash has other ways of referring to jobs; see the bash man page for more information (search for "^job" in the man page to get to the relevant section).
Getting back to the "kate" example, since it's unlikely that I will want to move a GUI program back into the foreground again, I can "disown" it and remove it from the list of jobs:
$ jobs + Stopped vi somefile + Stopped vi otherfile + Running kate & $ disown 3 $ jobs + Stopped vi somefile + Stopped vi otherfile $
So far, I've been moving foreground stuff to the background or resuming suspended tasks, but you might also want to to move a task that you started in the background to the foreground. Now it's unlikely that you're accidentally going to type "script &" when you just wanted to type "script", but the need could arise if you run a program in the background and it needs input from the terminal (background scripts are automatically suspended if they try to read from the terminal). Consider the following script to dump the system log to a file:
#!/bin/bash sudo journalctl >log
Tip: Stop Background Jobs from Writing to the Terminal.
Background jobs can write to the terminal by default. You may have seen that if you've ever started something in the background and had its output unexpectedly appear in your terminal window. You can cause background tasks to be suspended automatically on writes, just as with reads, by issuing the stty tostop command.
If you don't want to wait for this to complete, you could run it in the background. However, after starting it, you're likely to discover that it's been suspended:
$ bash dumplog.sh & $ jobs + Stopped bash dumplog.sh
That's because the first thing the script does is execute "sudo", and sudo needs to prompt you for a password (assuming it doesn't have your password cached). Once again, job control comes to the rescue. You can just bring the script back to the foreground, enter the password, suspend the job, and then move it back into the background:
$ bash dumplog.sh & $ jobs + Stopped bash dumplog.sh $ fg 1 [sudo] password for root: ****** ^Z $ bg 1 $
One final note, very recent versions of bash (I have 5.0.7) or possibly very recent versions of the kernel (I have 5.1.10) appear to have broken job control. When testing the "dumplog" example above, after moving the job from the foreground to background and then back to the foreground again twice, Control-Z (and Control-C) stopped working. It works fine for the other examples, and it works fine on older systems.
Any code found in my articles that is not taken from other sources, should be considered licensed as follows:
# Copyright 2019 Mitch Frazier <mitch -at- linuxjournal.com> # # This software may be used and distributed according to the terms of the # MIT License or the GNU General Public License version 2 (or any later version).