← Back to Bash Course | Chapter 12: Error Handling & Debugging | Lesson 1 of 12

Exit Codes and Checking $? Correctly

Every command reports back a number when it finishes, called an exit code, where zero means success and anything else means some kind of failure.

Reading $? Immediately

$? holds the exit status of the last command that ran, and it is overwritten the instant another command runs, including something as innocuous as echo. Capture it into a variable right away if you need it later.

Example: Reading $? Immediately

bash
#!/bin/bash
true
status=$?
echo "true exited with: $status"

false
status=$?
echo "false exited with: $status"

Testing a Command Directly

Rather than running a command and then inspecting $?, it's simpler and safer to put the command straight inside an if or while condition, since bash evaluates its exit status automatically.

Example: Testing a Command Directly

bash
#!/bin/bash
if grep -q "root" <<< "root:x:0:0"; then
    echo "pattern matched, no need to check \$? separately"
fi

Setting a Script's Own Exit Code

exit N immediately stops the script and sets its exit status to N, which any caller (another script, or the shell) can check afterward. Falling off the end of a script without an explicit exit uses the last command's status.

Example: Setting a Script's Own Exit Code

bash
#!/bin/bash
check_even() {
    if (( $1 % 2 == 0 )); then
        return 0
    else
        return 1
    fi
}

if check_even 4; then
    echo "4 is even"
fi
if ! check_even 5; then
    echo "5 is not even"
fi

Exit Codes Beyond 0 and 1

Exit codes can be any value from 0 to 255, and many real tools use specific numbers to mean specific things (for example, grep uses 2 to mean an actual error occurred, distinct from 1 meaning "no match found").

Example: Exit Codes Beyond 0 and 1

bash
#!/bin/bash
grep "x" /no/such/file 2>/dev/null
ecode=$?
if [[ $ecode -eq 2 ]]; then
    echo "grep reported an error (code 2), not just a non-match"
fi
Common Mistakes
  1. Running another command between the one you care about and checking $?, which overwrites $? with the new command's own status.
  2. Assuming a non-zero exit code always means 0/1 only; commands can return any value 0-255, each with tool-specific meaning.
  3. Using $? to check success inside an if when you could just put the command directly in the if condition, which is simpler and avoids the overwrite trap entirely.
Chapter Summary
  • Exit code 0 means success; any non-zero value (1-255) means some kind of failure, with meaning specific to the command.
  • $? holds the exit status of the most recently executed command and is overwritten by every subsequent command.
  • Put a command directly inside if/while conditions rather than checking $? afterward, when possible.
  • exit N inside a script sets that script's own exit code, which its caller can then check.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.