Learn › Linux Foundations › Chaining
exit - a hands-on Linux lab on a real virtual machine.
Every command reports a hidden number when it finishes: 0 for success, non-zero for failure. Make it visible with true, false, and a real ls, prove it is overwritten on every read, and see how the && and operators you already know were reading it all along.
For the last five lessons you have been chaining commands. The and-operator && ran the next command *only if the one before it worked*. The or-operator || ran a fallback *only when something failed*. Which raises a question worth stopping on: how does the shell know whether a command worked?
Here is the answer, and it is the quiet fact this whole module stands on: every command reports a number when it finishes. You never see it on screen, but it is always there. 0 means the command succeeded. Any other number means it failed. That single number is what && and || have been reading behind your back this whole time.
In this lesson you make that invisible number visible, on guaranteed successes, guaranteed failures, and a real command doing both.
The black boxes below are a practice terminal: a safe sandbox that checks the one command each step teaches, so you cannot break anything. On the real machine at the end of the module, the chaining mission builds this same setup and you read these verdicts for real.
That finishing number has a name: the exit code (also called the exit status). By long-standing convention, 0 means success and any non-zero number means failure. The oddity to get used to now: 0 is the *good* one.
The convention goes back to the earliest Unix in the 1970s, and the logic still holds: there is only one way to succeed, but many different ways to fail. So success got the single number 0, and every other number was left free to describe a kind of failure.
Two commands exist for the sole purpose of demonstrating this. true does nothing except succeed, so it always exits 0. false does nothing except fail, so it always exits 1. To peek at the code, you read the special variable $?, which holds the exit code of the command that just ran. $? gets its own full lesson next. For now you only need one move: echo $? prints it.
Before you run this, decide: what number should a guaranteed success leave behind? Then run true and read its code:
true; echo $?
prompt: student@linuxcamp:~$ answer: true; echo $? output: 0 hint: Type the word true, a semicolon, then echo $? to print the code it left behind: true; echo $?
0. That is true reporting success. The semicolon is doing exactly what you learned in the semicolon lesson: run both commands on one line, left to right, no questions asked. The point is the number. A successful command leaves a 0 behind, every time.
Now the opposite. false always fails. Commit to a prediction first: will its code be 0, or something else? Same move, read it:
false; echo $?
prompt: student@linuxcamp:~$ answer: false; echo $? output: 1 hint: Same shape as before, but with false: false; echo $?
1. Failure. So there is the whole scale in two commands: true gives 0 for success, false gives 1 for failure. Keep that pair in mind. It is a reliable ruler, because you always know exactly what code each one produces, on any machine, every time.
One thing will trip you up if you do not see it now: the exit code always reflects the single most recent command, and reading it counts as running a command. So the code you care about gets overwritten fast.
Watch what happens when you read it twice in a row after a false. Before you run it, decide: will the second read still show the failure?
prompt: student@linuxcamp:~$ answer: false; echo $?; echo $? output: 1 0 hint: One line, three commands separated by semicolons: false; echo $?; echo $?
The first echo $? printed 1, the failure code from false. But the second echo $? printed 0. Why? Because that first echo was itself a command, and it succeeded, so it reset the code to 0. The lesson: read the exit code once, right after the command you care about. The next lesson, all about $?, shows you how to save a code you want to keep.
true and false are teaching props. Real commands set the same codes. ls is a good one to watch, because you can make it succeed or fail on demand. In the practice setup there is a file that exists, present.txt, and a name that does not, missing.txt.
List the file that is really there. Decide first: what code should follow the listing?
ls present.txt; echo $?
prompt: student@linuxcamp:~$ answer: ls present.txt; echo $? output: present.txt 0 hint: Ask ls for a file that exists, then read the code: ls present.txt; echo $?
ls printed the name it found, then echo $? printed 0. The listing worked, so the exit code is success, exactly like true. A command's exit code is its verdict on its own work, not the text it printed.
Now point ls at the name that is not there. Two things happen: ls prints an error to the screen, and it leaves behind a non-zero code. This one is a read-along, no typing needed. Here is the exact exchange, captured on the lab machine:
ls missing.txt; echo $?
ls: cannot access 'missing.txt': No such file or directory
2
The error line is what ls writes when it cannot find something. The exact non-zero number a real command uses for failure is its own choice: this ls reports 2, while many other commands report 1 for their failures. Do not memorize the specific number. The rule that always holds is the one you can rely on: 0 is success, anything non-zero is failure. When you want a code you can count on exactly, reach for the true->0 and false->1 pair.
Now rewind to the mystery this lesson opened with. You already know the behavior of every chaining operator. What you have now is the mechanism underneath it.
The semicolon ; ignores the exit code completely: it runs the next command no matter what number the last one left. The and-operator && checks the code and runs the next command only when it sees 0. The or-operator || checks the same code and fires only when it sees anything non-zero. That is why, back in the and-operator lesson, false && echo skipped printed nothing: the 1 from false told && to stop.
The ( ) and { } groups play the same game as a team: a whole group reports one exit code, the code of the last command inside it, so an entire block can sit on the left side of && or ||. Every chain you have built in this module was really a conversation in these silent numbers.
Scaffolding off. No command is shown from here on.
Prove that a real command reports success the same way true does. List the file that exists in the practice setup, and on the same line read the exit code it leaves behind.
prompt: student@linuxcamp:~$ answer: ls present.txt; echo $? output: present.txt 0 hint: The ls of the existing file, a semicolon, then the echo that prints the last exit code.
The listing, then 0. A real tool and a teaching prop speak the same language: finish the job, report 0. That shared language is what lets the operators treat every command the same way.
Put both ends of the scale on screen in a single line: run the command that always succeeds and read its code, then run the command that always fails and read its code. Four commands, one line, exactly two numbers on screen.
prompt: student@linuxcamp:~$ answer: true; echo $?; false; echo $? output: 0 1 hint: The always-succeed command, a code read, the always-fail command, another read, semicolons between all four.
0 then 1, the whole convention in one line. Notice the timing: each read came immediately after the command it was checking, before anything else could overwrite the code. That read-it-right-away instinct is the habit to keep.
Last one, from memory. A teammate does not believe that reading the exit code replaces it. Prove it in one line: run the command that always fails, then read the code twice in a row. If you are right, the two reads will disagree.
prompt: student@linuxcamp:~$ answer: false; echo $?; echo $? output: 1 0 hint: The always-fail command first, then the same code read twice, semicolons between.
First read 1, second read 0. The disagreement is the proof: the first read reported the failure, then its own success set the code to 0 before the second read looked. Three challenges, no command shown, all driven by a number you could not even see at the start of this lesson.
You earned this cheat sheet. Every row is something you just ran, or watched land, in the practice terminal:
The single rule under all of it: 0 for success, non-zero for failure. && acts on success, || acts on failure, ; ignores both, and echo $? lets you read the last verdict yourself. Read it once, right away, because every command overwrites it.
When a chain does not behave the way you expect, stop and read the exit code by hand. Run the command on its own, then echo $?. Seeing the actual 0 or non-zero number almost always explains why the && or || after it did what it did.
You just made the invisible number visible. true left a 0, false left a 1, a real ls reported both verdicts, and you proved the code is overwritten by the very act of reading it. You also solved the opening mystery: && and || were never guessing, they were reading this number all along.
Next in this module, $? gets its own full lesson, followed by the two ever-changing PIDs $ and $!. Then the Chaining module ends with one real Linux machine: the chaining mission lab. There a VM boots just for you on this same setup, and the mission hands you objectives that use exactly what you just ran. It shows no commands. You read each objective, recall the move, and type it. That recall is what makes it stick.
Finish the other Chaining lessons, then go read the verdicts for real.
Practice Exit Codes in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.