Learn › Linux Foundations › Job Control
Final test: Chapter 9, Job Control. Twenty-five objectives on a real Debian 12 server: a job in the foreground and a job in the background with &, the job list in four forms, the job spec %n, Ctrl-Z, jobs -s, bg, fg with and without a spec, kill with a job spec and with the TERM signal named, kill -l, and nohup with nohup.out. Fifty minutes, graded from the commands you run and from the job list your shell keeps.
log-watch occupies the prompt, but you need to run another command while it works. How can you keep the report running and still use this shell? Start it in the background, inspect its job row, then pause, resume, and stop the right job. Later, start a report with nohup and inspect the output it writes.
Route: free the prompt, identify the right job, change its state, then read the later report's output.
What you already know: Chapter 2 listed directories and changed between them, Chapter 5 read files with cat, head and wc, and Chapter 8 asked the shell what a name means with type. This chapter is about the programs you start and what happens to them while they run.
Objectives
After this chapter you can:
Before you start a machine, each exercise shows a rehearsal terminal with recorded output. It checks the requested command but does not change Linux files. Job examples were captured in separate shells, so a printed job number or process ID may differ in your shell. Start one real practice machine and use its admin@server1 shell for this chapter. Commands run there, so an unintended command can still have an effect even if the lesson does not mark the exercise complete.
Keep one connection open for the sequence that starts, stops and resumes a job: its job list belongs to that shell. A new connection may start a fresh shell on the same machine with a different job list. The final Test Room starts a separate prepared server and checks objectives automatically; rehearsal completion is not that result.
Five questions before the material. A wrong answer costs nothing: the explanation states the fact and names the section that teaches it.
>>> A job is a command the shell started and still keeps track of. The list belongs to that one shell: a second shell, even in the same account on the same machine, has a list of its own. Section 9.3 proves it.
>>> A command started the ordinary way runs in the foreground: the shell waits for it, and the prompt does not come back until it is done. Section 9.2.
>>> A stopped job is frozen. It uses no processor time, it keeps everything it had in memory, and one command starts it again where it left off. Section 9.7.
>>> kill sends a signal, a short numbered message the kernel delivers to a process. The default signal is TERM, which asks a program to end. Section 9.9.
>>> When the terminal a session was using goes away, the shell passes the hangup signal on to the jobs it started, and most programs end on it. Section 9.11 starts a job that ignores it.
This chapter works in one directory of its own, ~/jobs. It holds two small programs written for the chapter, and both of them take time to run, which is what makes them useful here. Your prompt says you are standing in your home directory, ~. Every exercise in this chapter is run from ~/jobs, so move into it first and stay there.
One word appears twice in this chapter with two meanings, so it is worth separating them now. jobs is the name of the directory: a place on the disk. jobs is also the name of a shell command that lists what your shell is running. The directory is a noun on the filesystem; the command is a word you type. Nothing connects them.
prompt: admin@server1:~$ answer: cd ~/jobs ||| cd jobs ||| cd /home/admin/jobs output: hint: Section 9.1. The change-directory command from Chapter 2, then the path ~/jobs, which reaches it from any directory.
Nothing is printed, and the prompt now ends in ~/jobs. Chapter 2 established that: cd says nothing when it works.
You are standing in ~/jobs now. A long listing of that directory shows the two files and, in its first column, that both of them may be run.
prompt: admin@server1:~/jobs$ answer: ls -l ||| ls -l . ||| ls -l ~/jobs output: total 8 -rwxr-xr-x 1 admin admin 405 Jan 15 2026 log-watch -rwxr-xr-x 1 admin admin 457 Jan 15 2026 slow-report hint: Section 9.1. The listing command from Chapter 2 with its long option -l, aimed at ~/jobs: no path is needed from inside that directory, or write ~/jobs from anywhere.
Two files, and the x letters in the permission column say both are executable: files the system will run as programs. log-watch runs until you stop it. slow-report takes about twelve seconds and then ends by itself.
There is no log file here yet. log-watch writes one when it runs, and Section 9.4 lists this directory again.
Before running a program, read it. Both programs in ~/jobs are shell scripts, which means they are ordinary text files, and cat from Chapter 5 prints one. Read the endless one, log-watch.
prompt: admin@server1:~/jobs$ answer: cat log-watch ||| cat ./log-watch ||| cat ~/jobs/log-watch output: #!/bin/bash
#
LOG="$HOME/jobs/log-watch.log" : > "$LOG" n=1 while true; do lines=$(wc -l < "$HOME/logs/access.log") echo "log-watch: check $n, access.log holds $lines lines" >> "$LOG" n=$((n + 1)) sleep 2 done hint: Section 9.1. The file-reading command from Chapter 5, then the program file log-watch (or ~/jobs/log-watch from anywhere).
The parts that matter for this chapter are the last five lines. while true; do ... done repeats forever. Inside it, the script counts the lines of ~/logs/access.log, appends one numbered line to ~/jobs/log-watch.log, and waits two seconds. Nothing in it ever exits, so the only thing that ends this program is you.
The loop, the counting and the appending are taught in later chapters. What you need here is the shape: a program that runs for a long time and leaves a trace while it does.
A command you start the ordinary way runs in the foreground. The shell hands the terminal over to it, waits, and prints the prompt again only when the program has finished. While that program runs, the shell is not reading anything you type.
You are in ~/jobs, where both programs live. slow-report takes about twelve seconds. Run it from there, and watch the prompt stay away until the last line is printed.
prompt: admin@server1:~/jobs$ answer: ./slow-report ||| ~/jobs/slow-report ||| /home/admin/jobs/slow-report output: slow-report: orders.csv has 8 lines slow-report: customers.csv has 7 lines slow-report: done hint: Section 9.2. A program in the directory you are standing in is run by its path, not its name alone: ./slow-report from ~/jobs, or ~/jobs/slow-report from anywhere. Nothing goes after the name.
Three lines, four seconds apart, and only after the third one does the prompt come back. Twelve seconds is a short wait. A backup, a build or a report over real data can hold a terminal for an hour, and for that hour the shell reads nothing you type.
The ./ in front of the name is the path from Chapter 2: a program in the directory you are standing in is not found by name alone, so you say where it is.
A job is a command the shell started and still keeps track of. The shell numbers its jobs, remembers what state each one is in, and lets you name them by number. That record is the job table, and it lives inside one shell.
Chapter 8 taught type, which says what kind of thing a name is. Ask it about the word jobs. It reads no path, so it answers the same from any directory.
prompt: admin@server1:~/jobs$ answer: type jobs output: jobs is a shell builtin hint: Section 9.3. The command from Chapter 8 that says what kind of thing a name is, then the word jobs. It reads no path, so any directory does.
A builtin is a command the shell carries inside itself instead of running a program from the disk. The job table is the shell's own, so only the shell that started a job can use its %n job number to list, stop, resume or foreground it. Another shell could send a signal to a process if it knew the process ID, but it cannot bring that job into its own foreground. Open a second terminal to the same server and its job list is empty. Log out, and the first shell's table is gone with it.
A job and a process are two different things. A process is the machine's unit: one running program, counted and scheduled by the kernel. A job is the shell's record of what it started, and the process ID it lists is the program you named on the line. A command line that joins two programs into one, which Chapter 11 teaches, is still a single job, and it covers a process for each program.
Put an ampersand, &, at the end of a command line and the shell starts the program in the background: it does not wait, and the prompt comes straight back. The program keeps running beside your shell.
One thing a background job cannot do is read your keyboard. The terminal belongs to whatever is in the foreground, so a background job that tries to read from it is stopped where it stands, and the job list says so. Programs meant to be run this way take what they need from their command line or from files.
You are still in ~/jobs. Start log-watch, the endless one, this way, and leave it running: the rest of the chapter uses it.
prompt: admin@server1:~/jobs$ answer: ./log-watch & ||| ~/jobs/log-watch & output: [1] 2126 hint: Section 9.4. The endless program by the same path form as Exercise 9-4, ./log-watch (or ~/jobs/log-watch from anywhere), with the ampersand & last on the line.
One line, then your prompt. It carries two numbers.
[1] is the job number: the shell counts the jobs of this shell from 1. The second number is the process ID, the number the kernel gave this program for the whole machine. The kernel is the core of the operating system: it starts programs, shares the processor between them, and gives each one that number.
Job numbers are small and start again at 1 in every shell. Process IDs are handed out machine-wide and are different on every run, so the number on your screen will not be the one printed above.
The program is now running. Nothing on the screen says so, because log-watch writes to a file rather than to the terminal. A background job that writes to the terminal prints straight over whatever you are typing, so long jobs are usually written to send their output to a file.
The job is working. Chapter 5 taught head, which prints the first lines of a file, and the file this job is writing is ~/jobs/log-watch.log. Print its first three lines.
prompt: admin@server1:~/jobs$ answer: head -n 3 log-watch.log ||| head -3 log-watch.log ||| head -n 3 ~/jobs/log-watch.log output: log-watch: check 1, access.log holds 10000 lines log-watch: check 2, access.log holds 10000 lines log-watch: check 3, access.log holds 10000 lines hint: Section 9.4. The start-of-file tool from Chapter 5 with -n 3, then the log log-watch.log (or ~/jobs/log-watch.log from anywhere).
The file did not exist when you listed this directory in Section 9.1. The job made it, and it adds a line every two seconds while it runs. If you read the file before three writes have happened, wait a moment and run head -n 3 log-watch.log again. The counter starts at 1 on every run, because the program empties the log when it starts.
A background job is not a finished job. Count the lines of the same file, ~/jobs/log-watch.log, with wc -l from Chapter 5, and the number tells you how long the job has been alive.
prompt: admin@server1:~/jobs$ answer: wc -l log-watch.log ||| wc -l ~/jobs/log-watch.log output: 4 log-watch.log hint: Section 9.4. The counting tool from Chapter 5 with its line option -l, then the same log log-watch.log (or ~/jobs/log-watch.log from anywhere).
Your number will not be 4. It depends on how many seconds passed between starting the job and running the count, and it grows by one every two seconds for as long as the job runs. If a second count is unchanged, wait longer than two seconds and check again. Growth is evidence that the background job is doing work while you use the same terminal.
jobs prints the job table of the shell you type it in: one row per job. It reads the shell rather than the disk, so this exercise and the four after it print the same list whatever directory you are standing in.
prompt: admin@server1:~/jobs$ answer: jobs output: [1]+ Running ./log-watch & hint: Section 9.5. The job-listing word on its own, no options and no arguments. It reads the shell table rather than the disk, so your directory does not matter.
One row, four fields.
[1] is the job number. + marks the current job, the one a command with no job spec acts on; a second job would be marked - for the one before it. Running is the state. The rest of the row is the command line as you typed it, ampersand included, so a row always says what it is.
-l, the long option, adds one column: the process ID of each job.
prompt: admin@server1:~/jobs$ answer: jobs -l output: [1]+ 2257 Running ./log-watch & hint: Section 9.5. The job-listing word with the long option -l, the one that adds the process ID column.
The new number sits between the job number and the state. In one real run it matches the process ID printed when that job started. The number here differs from the earlier captured example because these practice outputs came from separate runs. Job numbers belong to one shell. A process ID identifies a running process on the machine; a later run may receive another number, and old numbers can eventually be reused.
-p prints the process IDs alone, one per line, with no other field.
prompt: admin@server1:~/jobs$ answer: jobs -p output: 2306 hint: Section 9.5. The job-listing word with the option that prints the numbers alone, -p.
One number. In your own shell, compare it with jobs -l: both refer to the same running job. The printed practice numbers differ because their captures came from separate runs. This form is useful when another command needs a process ID, which later chapters build on.
Two options filter the list by state. -r lists only the jobs that are running, and -s lists only the jobs that are stopped. Section 9.7 uses the second one.
prompt: admin@server1:~/jobs$ answer: jobs -r output: [1]+ Running ./log-watch & hint: Section 9.5. The job-listing word with the option named after the running state, -r.
The same row, because the one job in this shell is running. The filter changes which rows are listed, never how a row reads. In a shell holding several jobs, jobs -r lists only the ones still doing work.
Every command in the rest of this chapter has to be told which job to act on. The shell's way of naming one is the job spec: a percent sign and the job number. %1 is job 1.
Three other spellings exist and are worth recognizing: %% and %+ both mean the current job, the one marked +, and %- means the one before it. Given no job spec at all, fg and bg act on the current job, jobs lists every job, and kill has to be told what to end. The one job in this shell is job 1, so ask the list about %1.
prompt: admin@server1:~/jobs$ answer: jobs %1 ||| jobs %+ ||| jobs %% output: [1]+ Running ./log-watch & hint: Section 9.6. The job-listing word, then the spec that names job 1: a percent sign and the number, %1.
The same row, asked for by name. With one job the answer looks identical to the whole list; with several, jobs %2 is how you look at one of them.
A job in the foreground holds the terminal, and the shell is not reading your keys. Two keystrokes still take effect, because the part of the system that drives the terminal turns them into signals before the running program sees them as input.
Ctrl-Z, written by holding the Ctrl key and pressing z, sends the stop signal SIGTSTP, number 20, and stops the foreground job. Stopped means frozen: the program is still there, with everything it had in memory, using no processor time, until something starts it again.
On the real machine, the ./log-watch & from Exercise 9-6 is already running as a background job. Check jobs, then type fg %1 to bring that same job to the foreground. Press Ctrl-Z while it runs. Do not start a second ./log-watch: that would create another job and empt
Practice Chapter 9. Job Control: &, jobs, fg, bg, kill and nohup in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.