Check exit code directly, not via `$?`

Fix ShellCheck SC2181: checking $? separately is brittle and easy to break when a line is inserted. Test the command directly in the if instead.

Problem

Checking `$?` after a command works, but it's fragile: any intervening command resets it, and the intent is harder to read. Bash's `if` already operates on exit codes — there's no need to capture and compare them manually.

Bad

run_thing
if [ $? -ne 0 ]; then
  echo "failed"
  exit 1
fi

Good

if ! run_thing; then
  echo "failed" >&2
  exit 1
fi

Explanation

`if cmd; then` runs `cmd` and branches on its exit status (0 = success). `! cmd` negates it. This idiom is shorter, cannot be silently broken by an intervening command, and lets the reader see the condition and the recovery side-by-side.

Related

SC2164

SC2155

When It Matters

Checking [ $? -eq 0 ] after a command works only if nothing else ran in between — and something almost always creeps in during maintenance: a log line, an assignment, a debug echo. Each of those resets $?, so the check silently starts inspecting the wrong command’s status. Testing the command directly is immune to that, and it is shorter.

Second Example

Note

Wrapping the assignment in the if condition tests the command’s status, not the assignment’s, which is exactly what you want.

Exceptions

Inspecting $? is legitimate when a command has several meaningful exit codes and you need to distinguish them — grep returning 0, 1, or 2, or diff returning 1 for differences. Capture it immediately into a named variable: rc=$?; then branch on rc.

Faq

Q

How do I distinguish several exit codes?

A

Run the command, capture rc=$? on the very next line, then use a case statement on rc. Naming the value protects it from later commands.

Q

Does set -e make these checks unnecessary?

A

Partly. set -e aborts on an unchecked failure, but it does not fire inside conditions, in the left side of && or ||, or in most pipelines without pipefail — so explicit checks still matter.

Q

What is the status of a pipeline?

A

By default the status of the last command. Enable set -o pipefail to get the first non-zero status instead, or inspect the PIPESTATUS array in Bash.