Skip to content
VLSI Mentor

Linux & Bash · Part I · Module 11 · Exit Status and Command Chaining

Running Commands in Sequence

Putting two commands on one line is the first thing everybody learns after single commands, and the separator they learn is the wrong one for most of what they use it for. It runs the second command whatever happened to the first — which is fine until the first one was the part that mattered.

Foundation16 min readLinuxPart IFundamentalsExit StatusBash

Part I · Module 11 · Chapter 11.2 · Running Commands in Sequence

Chapter 11.1 established that every command returns a number saying whether it worked. This chapter is about the separator that ignores it completely.

1. The Semicolon

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ echo one; echo two; echo three
one
two
three

A semicolon separates commands. The shell runs them strictly in order, waiting for each to finish before starting the next.

And a newline does exactly the same job:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ false
$ echo "after a newline -> $?"
after a newline -> 1

A script is therefore just a list of commands separated by newlines, and ; is what you use when you want several on one line. There is no other difference — this is why you can take any three lines of a script, join them with semicolons, and get identical behaviour.

A trailing semicolon is legal and redundant:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ echo one;
one

The one place it is required is before a closing brace, which is Chapter 11.4.

2. The Status of a Sequence

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ false; true;  echo "false; true  -> $?"
false; true  -> 0

$ true;  false; echo "true; false  -> $?"
true; false  -> 1

The status of a sequence is the status of the last command in it. Everything earlier is discarded.

That is reasonable on its own terms — the shell has one $? and something has to go in it. But combined with the next section it is the source of a great deal of trouble.

3. The Problem

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ cd /nonexistent 2>/dev/null
$ echo "  (cd failed, but this still ran, in $(pwd))"
  (cd failed, but this still ran, in /tmp/m11v)

The cd failed. The next command ran anyway, in the directory you were already in.

Now the version that costs people real time:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ cd build; make

Read it the way the shell does: change directory, then run make, regardless. If build does not exist — a typo, a clean checkout, a different machine — the cd fails, prints an error that scrolls past, and make runs in whatever directory you happened to be standing in.

Measured, with a stand-in for make:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ cd /tmp/demo
$ cd build; ./fake_make.sh
  MAKE RAN in /tmp/demo/build

$ cd /tmp/demo
$ cd buidl; ./fake_make.sh
bash: cd: buidl: No such file or directory
  MAKE RAN in /tmp/demo

Same two commands, one typo, and the build ran somewhere it should never have run. Depending on what that build does, the results range from "nothing happened" to object files scattered through your source tree to a clean target deleting the wrong directory.

The difference between sequencing two commands and chaining them. A semicolon runs the second command whatever happened to the first, so a failed directory change is followed by a build in the wrong place. Two ampersands run the second only if the first succeeded, so the same failure stops the sequence.Separated by asemicoloncd build; makeThe first one failsthe directory name wasmistypedThe second runs anywaybuilding in whatever directoryyou were inChained with twoampersandscd build && makeThe first one failsexactly the same mistakeThe second never startsthe sequence stops where itwent wrong12
Figure 1 — the difference between sequencing two commands and chaining them. A semicolon runs the second command whatever happened to the first, so a mistyped directory name is followed by a build in whatever directory you were already standing in. Two ampersands run the second only if the first succeeded, so the same mistake stops the sequence at the point where it went wrong.

4. When a Semicolon Is Right

It is not a defective operator. It is the right one whenever the commands are genuinely independent — where the second makes just as much sense whether or not the first worked.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
date; uptime; df -h /scratch           # three unrelated readings
echo "=== logs ==="; ls -lt *.log      # a label and then a listing
cd /tmp; pwd                           # a cd that cannot meaningfully fail

It is also the natural separator for several commands sent somewhere as one line:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ssh buildhost 'cd /work && make'       # && inside, because they are dependent
ssh buildhost 'date; uptime'           # ; because they are not

And in a for loop written on one line, where the semicolons are part of the syntax rather than a choice:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
for f in *.log; do echo "$f"; done

(Chapter 17.1 covers loops; the semicolons before do and done are required there.)

5. The Other Separators

; is one of four things that can sit between two commands, and it is worth seeing them together because they are easy to confuse by eye.

SeparatorRuns the next oneWaits?
;AlwaysYes
newlineAlwaysYes
&&Only if the previous succeededYes
||Only if the previous failedYes
&AlwaysNo

The last one you already know from Chapter 7.5: & both separates commands and backgrounds the one before it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ sleep 5 & echo "this printed immediately"
[1] 4821
this printed immediately

That is a genuine difference in kind, and it causes a specific confusion: & and && look alike and behave nothing alike.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make & test.sh          # start make in the BACKGROUND, then immediately run test.sh
make && test.sh         # run make, wait, and run test.sh only if it succeeded

A single missing & turns "build then test" into "build and test simultaneously, testing whatever binary was there before". Worth a second look whenever you type either.

6. Sequences in Scripts

In a script the separator is usually a newline, so the question becomes invisible — and the problem does not go away:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
#!/bin/bash
cd /work/project/build
make clean
make all
cp sim.out /results/

Four lines, no semicolons, and exactly the hazard from §3. If the cd fails, the other three run in whatever directory the script was started from — make clean in particular is capable of deleting things in a directory that is not the build directory.

Three ways to fix it, in increasing order of care:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cd /work/project/build || exit 1       # stop if the cd failed
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set -e                                 # stop on ANY failure
cd /work/project/build
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cd /work/project/build && make clean && make all && cp sim.out /results/

The first is explicit and local. The second is a blanket policy with caveats that Chapter 20.2 covers. The third is Chapter 11.3 and is the subject of the next chapter.

7. Reading Somebody Else's One-Liner

Long chained lines are common in build instructions and continuous-integration files, and reading them is a matter of finding the separators first:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
mkdir -p build; cd build; cmake ..; make -j8; ctest

Five commands, four semicolons, and not one of them checks anything. If cmake fails, make runs on a configuration that does not exist, ctest runs on binaries that were not built, and the line's overall status — the status of ctest — is the only one anybody sees.

The same line, written to stop at the first problem:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
mkdir -p build && cd build && cmake .. && make -j8 && ctest

Now the status of the whole line is meaningful: zero means every stage succeeded.

That is the real argument for &&, and it is not about typos. A chain of semicolons reports the status of its last command, so it tells you whether the final step worked and nothing more. A chain of && reports whether all of it worked, which is the thing a build system actually needs to know.

Practice

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
mkdir -p /tmp/seq/build && cd /tmp/seq
printf '#!/bin/bash\necho "  MAKE RAN in $(pwd)"\n' > build/fake_make.sh
chmod +x build/fake_make.sh
cp build/fake_make.sh ./fake_make.sh

1 — Confirm a sequence takes its last status.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
false; true;  echo "false; true -> $?"
true;  false; echo "true; false -> $?"

2 — Confirm a newline is the same separator.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
false
echo "-> $?"

3 — Reproduce the hazard.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cd /tmp/seq; cd build; ./fake_make.sh
cd /tmp/seq; cd buidl; ./fake_make.sh

The second runs in the wrong directory. Read the error that scrolled past.

4 — See the fix work, before reading 11.3.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cd /tmp/seq; cd buidl && ./fake_make.sh; echo "  nothing ran, status $?"

5 — Tell & from &&.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sleep 2 & echo "  with &  : printed immediately"
sleep 2 && echo "  with && : printed after two seconds"

6 — Count the unchecked steps. Take this line and say what happens if the third command fails:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
mkdir -p out; cd out; false; echo "still here in $(pwd)"

7 — Guard a cd.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
( cd /nonexistent || { echo "  guard caught it"; exit 1; }; echo "  NOT REACHED" )

8 — Apply the "regardless" test to five lines from your own shell history. How many should have been &&?

9 — Clean up. cd /tmp && rm -rf /tmp/seq

Check Your Understanding

1. What is the exit status of false; false; true?

2. cd reports; ./summarise.sh and the script runs but produces nothing useful. What is the likely cause?

3. What is the difference between a semicolon and a newline between two commands?

4. When is a semicolon the right separator?

5. make & test.sh and make && test.sh — what does each do?

6. A five-command line joined by semicolons ends with status 0. What does that tell you about the first four?

8. Remember

  • A semicolon separates commands and runs them in order, waiting for each.
  • A newline is the same separator. A script is a list of commands separated by newlines.
  • A sequence's exit status is its last command's. Everything earlier is discarded.
  • A semicolon ignores whether the previous command worked, which is the whole problem.
  • cd build; make runs make in the wrong directory the moment cd fails. Measured, not hypothetical.
  • A semicolon means "then", not "and then, if it worked".
  • Say the line with "regardless" in it. If that sounds careless, the commands are dependent and you want &&.
  • It is the right separator for independent commands — date; uptime — and required by loop syntax.
  • & is also a separator and does not wait. make & test.sh and make && test.sh are one character and a world apart.
  • In a script the separator is a newline and the hazard is invisible. Guard every cd with || exit.
  • A chain of semicolons reports only its final step. A chain of && reports whether all of it worked — which is what a build system needs.

The fix has now been named three times without being taught. The next chapter is the two operators that read the number from Chapter 11.1 and act on it — along with the one way of combining them that looks like an if-then-else and is not.

Where this fits

Part of the Linux & Bash curriculum.