Learn › Linux Foundations › Chaining
Final test: Chapter 10, Chaining. Twenty-five objectives on a real Debian 12 server: the semicolon, plain runs of four check programs with fixed exit statuses, $?, && including a gate that must never open, , && with on one line, ( ) and { } grouping proved by what they move and what they leave behind, and $ and $!. Fifty minutes, graded from the commands you run and from the machine.
This chapter covers the following topics:
What you already know: every chapter so far ran one command at a time, and Chapter 9 sent a command to the background with a trailing ampersand. This chapter puts several commands on one line and lets the result of one decide whether the next one runs.
Objectives
After this chapter you can:
Before the practice machine starts, exercise cards show recorded examples and check the command for each step. Once it is running, the Terminal uses a real Debian 12 server. Run each command there and inspect its actual output. Files persist while that practice session is active, even if you open another lesson; a newly opened terminal starts a new shell. The chapter assessment starts a separate machine, and its checks inspect the commands and requested machine results.
Five questions before the material. A wrong answer costs nothing: the explanation states the fact and names the section that teaches it.
>>> Every command ends by handing the shell a single number called its exit status. That number, and nothing else, is what the operators in this chapter test. Section 10.3 covers it.
>>> A semicolon means "and then", nothing more. It runs each command in turn whatever the one before it reported. Section 10.2 covers it.
>>> Zero means success, and every other number means some kind of failure. It reads backwards at first: there is one way to succeed and many ways to fail, so the many numbers are on the failure side. Section 10.4 covers it.
>>> Two ampersands make a gate that opens only on success. The command behind it never runs after a failure, which is how one line refuses to continue after something went wrong. Section 10.5 covers it.
>>> Round brackets run their contents in a second shell called a subshell. The move happens in that shell and dies with it, so the shell you are typing in never moves. Section 10.8 covers it.
This chapter joins commands on one line and lets the result of one decide whether the next one runs. To see that clearly you need commands whose result never changes, so this server carries four of them in the directory ~/checks.
Each of the four prints one line and then reports success or failure. What each one reports is fixed: the disk check always succeeds, the link check always fails, the backup check always succeeds, and the service check answers according to the name you give it. Nothing in this chapter depends on the real state of the machine.
You start this chapter in your home directory, ~, and every exercise in it names its files by a path that starts at your home directory, such as ~/checks/disk-check.sh. Written that way a line runs the same wherever you happen to be standing, which matters here because one line can create a file.
prompt: admin@server1:~$ answer: cat ~/checks/README.txt ||| cat checks/README.txt ||| cat /home/admin/checks/README.txt output: server1: the check programs =========================== Four small programs live in this directory. Each one prints a single line and then reports whether it succeeded or failed. Nothing they print or report ever changes, so the same command always gives the same answer.
disk-check.sh reports free space. Always succeeds. link-check.sh reports the network link. Always fails. backup-check.sh reports the last backup. Always succeeds. service-check.sh takes the name of a service and reports whether it is running. The name web succeeds; any other name fails; no name at all is an error.
report.txt a short written report, 111 bytes. results/ an empty directory. Anything you create in this chapter belongs here.
The four programs exist so that you can join commands together and know in advance which of them will run. hint: Section 10.1. The file-reading command from Chapter 5, then the path ~/checks/README.txt, which reaches it from any directory.
Four programs, one report file and one empty directory. report.txt is the file a later section copies, and results/ is where everything this chapter creates belongs, so nothing you do lands among the working files of the server.
A semicolon between two commands means "and then". The shell runs the first command, waits for it to finish, and runs the second. Writing them on one line with a semicolon is exactly the same as typing them on two lines, one after the other.
prompt: admin@server1:~$ answer: whoami; hostname output: admin server1 hint: Section 10.2. The account-name command and the machine-name command from Chapter 1, in that order, with a semicolon between them. Neither takes a path, so your directory does not matter.
Two answers, in the order they were written. The shell did not merge them or run them at the same time: whoami finished before hostname started. A space either side of the semicolon is allowed but not required.
The semicolon carries no condition. It runs the next command whatever the one before it reported, including after something went wrong.
The link check on this server always reports a problem. Put it in front of the disk check and watch the disk check run anyway.
prompt: admin@server1:~$ answer: ~/checks/link-check.sh; ~/checks/disk-check.sh ||| checks/link-check.sh; checks/disk-check.sh output: link: no carrier on eth1 disk: ok, 42% used hint: Section 10.2. The same semicolon, this time between ~/checks/link-check.sh and ~/checks/disk-check.sh, in that order.
Both lines printed. The first program reported that the network link is down, and the second one ran regardless. That is the whole behaviour of the semicolon: order, and nothing else. Section 10.3 explains what "reported a problem" means to the shell, and Section 10.5 replaces the semicolon with an operator that does care.
When a command finishes it hands the shell one number, called its exit status. The number is not printed and is not part of the output. It is a separate report, and it is the only thing the operators in this chapter look at. An operator here is a punctuation mark written between two commands that decides whether the next one runs.
0 means success. Any other number means a failure of some kind. That is backwards from the way most people count, and it is worth reading twice. There is one way for a command to work and many different ways for it to fail, so the many numbers sit on the failure side.
Run the disk check on its own first, so you know what a success looks like.
prompt: admin@server1:~$ answer: ~/checks/disk-check.sh ||| checks/disk-check.sh ||| ./checks/disk-check.sh output: disk: ok, 42% used hint: Section 10.3. The path to the check that succeeds, ~/checks/disk-check.sh, and nothing else on the line.
One line of output. The exit status this program handed back is 0, and nothing on the screen says so. Section 10.4 asks the shell for it.
A failing command is not a broken one. link-check.sh works perfectly: it looks at the network link, finds no carrier, and reports that fact by handing back a number that is not zero. Failure here means "the thing I checked is not right", not "I crashed".
prompt: admin@server1:~$ answer: ~/checks/link-check.sh ||| checks/link-check.sh ||| ./checks/link-check.sh output: link: no carrier on eth1 hint: Section 10.3. The same shape as the last exercise, aimed at the check that always fails, ~/checks/link-check.sh.
On screen this looks exactly like the successful run: one line, no complaint from the shell. The difference is invisible until you ask for it, which is why the next section exists.
The fourth program takes an argument, a word written after the command name that tells the command what to work on. The argument here is the name of a service. The name web names a service that is running, so the program succeeds, and any other name fails.
prompt: admin@server1:~$ answer: ~/checks/service-check.sh web ||| checks/service-check.sh web output: service web: running hint: Section 10.3. The path ~/checks/service-check.sh, a space, then the name of the service that is running, web.
The same program can succeed or fail depending on what you ask it. That is normal: ls succeeds on a directory that exists and fails on one that does not. A command reports on the work it was asked to do.
The shell keeps the exit status of the last command it finished in a variable called $?. Chapter 8 defined a variable as a name holding a value; $? is one the shell maintains for you, and it changes after every command.
Because it changes after every command, read it on the SAME line that produced it. A semicolon does that: the check runs, then echo $? prints the number the check handed back.
prompt: admin@server1:~$ answer: ~/checks/disk-check.sh; echo $? ||| checks/disk-check.sh; echo $? output: disk: ok, 42% used 0 hint: Section 10.4. The succeeding check ~/checks/disk-check.sh, a semicolon, then the printing command and the status variable $?.
Two lines. The first is the program talking. The second is the shell telling you what the program reported: 0, success. Type echo $? again on its own and you would get 0 once more, but this time it would be the status of the echo that just succeeded, not of the disk check. The number is always about the command immediately before.
Now the same reading on the check that always fails.
prompt: admin@server1:~$ answer: ~/checks/link-check.sh; echo $? ||| checks/link-check.sh; echo $? output: link: no carrier on eth1 1 hint: Section 10.4. The same line aimed at ~/checks/link-check.sh, with the same semicolon and the same status variable $?.
1 is the most common failure number. Nothing forces a program to use it; 1 simply means "the general failure this program was written to report".
A program may choose its own failure numbers so that whoever runs it can tell one kind of failure from another. service-check.sh uses 3 when the service is not running.
prompt: admin@server1:~$ answer: ~/checks/service-check.sh mail; echo $? ||| checks/service-check.sh mail; echo $? output: service mail: not running 3 hint: Section 10.4. The path ~/checks/service-check.sh with the name that is not running, mail, then the same status read $?.
Three, and it still means failure. The operators in the rest of this chapter never look at which number it is: they ask one question, is it zero or is it not. The exact number matters only to a person reading a manual page, where a program lists what each of its numbers means.
Two ampersands between commands, &&, make a gate: a point in the line the shell passes only under a condition. The command behind the gate runs only if the command in front of it exited with status zero. If the first command failed, the second never runs at all.
That is how one line refuses to keep going after something went wrong. Start with the gate open: ~/checks/disk-check.sh succeeds, so the touch behind it runs and creates ~/checks/results/disk-ok.txt. Name that file by its full path, and it lands in the results directory whatever directory you are standing in.
prompt: admin@server1:~$ answer: ~/checks/disk-check.sh && touch ~/checks/results/disk-ok.txt ||| checks/disk-check.sh && touch checks/results/disk-ok.txt output: disk: ok, 42% used hint: Section 10.5. The check ~/checks/disk-check.sh, the success operator &&, then the file-creating command from Chapter 6 aimed at ~/checks/results/disk-ok.txt. Write that path in full and the file lands there from any directory.
One line of output, from the check. touch says nothing when it works, so the proof is in the directory. ls ~/checks/results answers:
disk-ok.txt
The gate opened because the status was zero, and the file exists.
The same line, aimed at the check that always fails. The gate stays shut, and the command behind it never runs.
prompt: admin@server1:~$ answer: ~/checks/link-check.sh && touch ~/checks/results/link-ok.txt ||| checks/link-check.sh && touch checks/results/link-ok.txt output: link: no carrier on eth1 hint: Section 10.5. The same shape with ~/checks/link-check.sh in front of && and ~/checks/results/link-ok.txt behind it. The file behind the gate must not appear.
The check printed its line and the shell stopped there. touch never ran, so the file was never created, and asking for it with ls ~/checks/results/link-ok.txt proves it:
ls: cannot access '/home/admin/checks/results/link-ok.txt': No such file or directory
Nothing announced that a command was skipped. && says nothing about its decision: it either runs the next command or it does not.
&& can appear as often as you like on one line. Each gate tests the command immediately in front of it, so the line advances only as long as everything keeps succeeding.
prompt: admin@server1:~$ answer: ~/checks/disk-check.sh && ~/checks/backup-check.sh && echo ALL CHECKS PASSED ||| checks/disk-check.sh && checks/backup-check.sh && echo ALL CHECKS PASSED output: disk: ok, 42% used backup: last run complete ALL CHECKS PASSED hint: Section 10.5. The success operator && twice on one line: ~/checks/disk-check.sh, then ~/checks/backup-check.sh, then the printing command with the three words ALL CHECKS PASSED.
Three lines, so all three stages ran. Put the link check anywhere in that line and everything after it would disappear from the output. This shape is how a line of shell says "do all of this, and stop at the first thing that goes wrong".
The command behind a gate is any command at all. Here it is cp from Chapter 6, copying ~/checks/report.txt to ~/checks/results/report-filed.txt, so a report is filed only when the check that justifies filing it has succeeded. Both paths start at your home directory, so the copy lands in the results directory from anywhere.
prompt: admin@server1:~$ answer: ~/checks/disk-check.sh && cp ~/checks/report.txt ~/checks/results/report-filed.txt ||| checks/disk-check.sh && cp checks/report.txt checks/results/report-filed.txt output: disk: ok, 42% used hint: Section 10.5. The check ~/checks/disk-check.sh, the success operator &&, then the copying command from Chapter 6 with the source ~/checks/report.txt and the destination ~/checks/results/report-filed.txt.
cp is silent when it works, so again the proof is the result. cat ~/checks/results/report-filed.txt shows the copy holds the same bytes as the original:
server1 weekly check report
===========================
disk ok
link no carrier on eth1
backup complete
Two vertical bars, ||, are the mirror image of &&. The command behind them runs only if the command in front of them FAILED. It is the operator to write when a failure needs a reaction.
prompt: admin@server1:~$ answer: ~/checks/link-check.sh || ~/checks/backup-check.sh ||| checks/link-check.sh || checks/backup-check.sh output: link: no carrier on eth1 backup: last run complete hint: Section 10.6. The failing check ~/checks/link-check.sh, the failure operator ||, then ~/checks/backup-check.sh.
The first program failed, so the second ran. Both lines printed, which looks like the semicolon result from Exercise 10-3, and it is not: here the second command ran BECAUSE the first one failed. The next exercise shows the difference.
Aim the same line at a check that succeeds and the fallback has no reason to run.
prompt: admin@server1:~$ answer: ~/checks/disk-check.sh || ~/checks/backup-check.sh ||| checks/disk-check.sh || checks/backup-check.sh output: disk: ok, 42% used hint: Section 10.6. The same line with the check that succeeds, ~/checks/disk-check.sh, in front of the failure operator ||.
One line only. The backup check never ran. Compare the two exercises: with a semicolon both commands always run, with || the second one runs only after a failure, and with && only after a success.
Skipping the rest of a l
Practice Chapter 10. Chaining: ; && || exit status and grouping in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.