Skip to content
VLSI Mentor

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

Conditional Chaining with && and ||

Two operators turn the number from the last two chapters into control flow: run this only if that worked, or only if it did not. They are the shortest correct way to express a dependency — and there is exactly one way of combining them that nearly everybody gets wrong.

Foundation18 min readLinuxPart IFundamentalsExit StatusBash

Part I · Module 11 · Chapter 11.3 · Conditional Chaining with && and ||

Chapter 11.2 named the fix three times and did not teach it. Here it is.

1. Two Operators

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ true  && echo "  ran"
  ran

$ false && echo "  ran"

&& runs the next command only if the previous one succeeded. The second line printed nothing.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ false || echo "  ran"
  ran

$ true  || echo "  ran"

|| runs the next command only if the previous one failed.

That is the whole mechanism. Both read the exit status from Chapter 11.1 — zero or not zero — and decide whether to continue.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cd build && make                    # make, but only if we got there
make || echo "build failed" >&2     # complain, but only if it broke

2. Short-Circuiting

The second command is not run at all when the condition is not met. That matters more than it sounds.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ false && rm -rf "$dir"

rm never started. No process, no arguments evaluated, nothing. Compare with Chapter 11.2's semicolon, which would have run it.

This is what makes the cd guard actually safe:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ cd /tmp/demo && cd buidl && ./fake_make.sh; echo "  stopped, status $?"
bash: cd: buidl: No such file or directory
  stopped, status 1

Three commands, the second failed, the third never ran, and the chain's status is 1 — the failure, preserved.

3. The Four Outcomes

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ false && true;  echo "false && true  -> $?"
false && true  -> 1
$ true  && false; echo "true  && false -> $?"
true  && false -> 1
$ false || true;  echo "false || true  -> $?"
false || true  -> 0
$ false || false; echo "false || false -> $?"
false || false -> 1
ChainSecond runs?Status
false && trueno1 — the first command's
true && falseyes1 — the second command's
false || trueyes0 — the second command's
false || falseyes1 — the second command's

The rule: the chain's status is that of the last command that actually ran.

Which gives && the property that makes it worth using in a build:

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

This line's exit status is zero only if every stage succeeded. With semicolons it would report ctest's status alone, telling you nothing about the four steps before it — Chapter 11.2 §7's point, now with the mechanism behind it.

4. The Trap

Here is the construct almost everyone writes, believing it is an if-then-else:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
command && echo "ok" || echo "failed"

It usually behaves correctly, which is what makes it dangerous.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ true && echo "  B ran" || echo "  C ran"
  B ran

So far so good. Now make the middle command fail while the first still succeeds:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ true && false || echo "  C ran even though A SUCCEEDED"
  C ran even though A SUCCEEDED

And the realistic version, using Chapter 11.1's fact that grep returns 1 when it simply finds nothing:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ true && grep zzz f1.txt || echo "  C ran because grep found nothing"
  C ran because grep found nothing
Why chaining and with or is not the same as if and else. When the middle command succeeds, the last one is skipped and the construct appears to behave like a conditional. But when the middle command fails, the last one runs as well — even though the first command succeeded — so the error branch fires on a success.The middle one succeedsthe ordinary caseThe last one is skippednothing after the or needs torunIt looks like aconditionalwhich is why people trust itThe middle one failsand the first one stillsucceededThe last one runs toothe or sees only the failurebefore itThe error branch fireson a command that did not fail12
Figure 1 — why chaining and with or is not the same as if and else. When the middle command succeeds the last one is skipped, and the construct appears to behave exactly like a conditional — which is why people trust it. But when the middle command fails, the last one runs as well, even though the first command succeeded, so the error branch fires on something that did not fail.

5. Precedence: There Isn't Any

&& and || have equal precedence and associate left to right. There is no "and binds tighter than or" as there is in most languages.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ false || echo A && echo B
A
B

Read strictly left to right: false || echo A — the false failed, so echo A ran and succeeded. The chain so far has status 0, so && echo B runs too.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ true || echo A && echo B
B

true || echo A — succeeded, so echo A is skipped. The chain's status is still 0, so echo B runs.

echo A is skipped and echo B runs in both cases where the chain succeeded. If you expected A && B to be a unit, neither result matches.

6. The Idioms Worth Knowing

Four patterns that cover most real use.

Guard a step

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cd "$build_dir" && make
command -v vcs >/dev/null && echo "simulator found"
[ -f "$config" ] && source "$config"

Fail loudly

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cd "$build_dir" || { echo "cannot enter $build_dir" >&2; exit 1; }
command -v vcs >/dev/null || { echo "vcs not on PATH" >&2; exit 3; }

|| { …; exit N; } is the standard guard in a script. The braces are needed because two things happen — a message and an exit — and the semicolon before the closing brace is required (Chapter 11.4).

Say you do not care

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grep ERROR run.log || true
rm -f /tmp/scratch.$$ || true

Chapter 11.1 showed set -e stopping a script because grep found nothing. || true is the explicit "a non-zero here is expected and fine", and it is the right way to say it — far better than disabling set -e for the whole script.

Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ cat se.sh
#!/bin/bash
set -e
grep zzz f1.txt || true
echo "survived the grep that found nothing"
grep zzz f1.txt
echo "NOT REACHED"

$ ./se.sh; echo "  script exited $?"
survived the grep that found nothing
  script exited 1

The guarded grep was survivable; the unguarded one ended the script.

Provide a fallback

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
command -v nvim >/dev/null && EDITOR=nvim || EDITOR=vi
mkdir -p "$d" || { echo "cannot create $d" >&2; exit 1; }

The first is the a && b || c shape — and it is safe here, because a plain assignment cannot fail.

7. Chaining in Real Work

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
#!/bin/bash
set -uo pipefail

run_dir="${1:?usage: $0 <run-dir>}"

cd "$run_dir" || { echo "no such run directory: $run_dir" >&2; exit 2; }

command -v vcs >/dev/null || { echo "vcs not on PATH — source the tool setup" >&2; exit 3; }

# Build, elaborate and run, stopping at the first failure.
vcs -full64 -sverilog -f filelist.f -o simv \
  && ./simv +UVM_TESTNAME=smoke_test \
  && echo "simulation completed"

status=$?

# A clean run must say PASSED and must not say UVM_FATAL.
if [ "$status" -eq 0 ]; then
  grep -q 'TEST PASSED' sim.log \
    && ! grep -q 'UVM_FATAL' sim.log \
    && echo "RESULT: pass" \
    || { echo "RESULT: fail — log checks did not hold" >&2; status=1; }
fi

exit "$status"

Four things in there worth pointing at:

  • || { …; exit N; } on both preconditions, each with its own documented code from Chapter 11.1.
  • The three-stage && chain means simv runs only on a successful compile, and the status afterwards reflects the whole thing.
  • status=$? on the very next line, because Chapter 11.1 §2.
  • The log check uses ! to negate — ! grep -q UVM_FATAL succeeds when the pattern is absent, which is the idiomatic way to assert that something is not in a file.

And note that the final chain is an a && b && c || d — which is the trap from §4. It is acceptable here only because the preceding commands are grep -q and echo, whose failure modes are understood and are exactly what the || is meant to catch. In anything less carefully reasoned it should be an if.

Practice

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
mkdir -p /tmp/cc && cd /tmp/cc
printf 'alpha\nbeta\n' > f1.txt

1 — Watch both operators short-circuit.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
true  && echo "  &&  after success"
false && echo "  &&  after failure (should not print)"
false || echo "  ||  after failure"
true  || echo "  ||  after success (should not print)"

2 — Confirm the four statuses.

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

3 — Spring the trap.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
true && echo "  B ran" || echo "  C ran"
true && false || echo "  C ran even though A succeeded"
true && grep zzz f1.txt || echo "  C ran because grep found nothing"

4 — Confirm equal precedence, left to right.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
false || echo A && echo B
true  || echo A && echo B

Predict each before running it.

5 — Build the standard guard.

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

6 — Make set -e survive a clean grep.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
printf '#!/bin/bash\nset -e\ngrep zzz /tmp/cc/f1.txt || true\necho "reached"\ngrep zzz /tmp/cc/f1.txt\necho "NOT REACHED"\n' > se.sh
chmod +x se.sh; ./se.sh; echo "  exited $?"

7 — Negate a check.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grep -q alpha f1.txt   && echo "  alpha is present"
! grep -q zzz  f1.txt  && echo "  zzz is absent"

8 — Write the chain that reports everything.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
mkdir -p out && cd out && touch a && ls a && echo "  all four ok -> $?"
cd /tmp/cc
mkdir -p out && cd nowhere && touch a && ls a; echo "  stopped -> $?"

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

Check Your Understanding

1. What is the difference between cd build; make and cd build && make?

2. What is the exit status of true && false, and which command does it come from?

3. deploy.sh && echo "deployed" || echo "deploy failed" printed both messages. How?

4. false || echo A && echo B prints A and B. Walk through why.

5. Why is || true after a grep sometimes necessary?

6. When should a chain become an if?

8. Remember

  • && runs the next command only if the previous one succeeded; || only if it failed.
  • Read them as "and then, if that worked" and "or else, if that failed" — they are control flow, not boolean operators.
  • The second command does not run at all when the condition is not met.
  • A chain's status is that of the last command that actually ran.
  • An && chain reports zero only if every stage succeeded — which is what makes it right for a build, and what a semicolon chain cannot do.
  • a && b || c is not an if-then-else. It is three commands, each looking only at the one before it.
  • It is safe only when b cannot fail. cmd && echo ok || echo failed is fine; almost anything else is a latent bug.
  • && and || have equal precedence and run left to right. There is no "and binds tighter".
  • More than one operator means you should be grouping.
  • || { echo "..." >&2; exit N; } is the standard guard in a script.
  • || true says a non-zero status is expected — the right fix for grep under set -e.
  • ! cmd negates a status, which is how you assert that a pattern is absent.
  • Keep chains to two or three commands. Beyond that you want if or a function.

The last two sections both reached for braces and parentheses without explaining them. The final chapter of the module is those: what each one groups, which of them starts a separate shell, and why that difference decides whether your directory change survives.

Where this fits

Part of the Linux & Bash curriculum.