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
fiGood
if ! run_thing; then
echo "failed" >&2
exit 1
fiExplanation
`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.