Skip to content
Exit Status and Conditional Execution
step 1/7

Reading — step 1 of 7

Learn

~3 min readCommand Substitution and Exit Status

Every command returns an exit status — an integer 0-255 where 0 means success, non-zero means failure. The Bash Reference Manual makes this central to all control flow.

Exit status

ls /etc        # success → exit status 0
ls /nonexistent  # failure → exit status non-zero

echo $?         # exit status of the LAST command

$? holds the most-recent exit status. By convention:

  • 0 — success
  • 1-125 — script-defined errors
  • 126 — command not executable
  • 127 — command not found
  • 128+N — terminated by signal N (e.g., 130 = SIGINT from Ctrl+C, 137 = SIGKILL)

Setting exit status from a script

#!/bin/bash
if [ ! -f config.txt ]; then
    echo "missing config" >&2
    exit 1                   # script exits with status 1
fi
exit 0                       # explicit success

Without exit, the script exits with the status of its LAST command. Be explicit for clarity.

Conditional execution: && and ||

&& — run next ONLY IF previous succeeded:

mkdir backup && cp -r data backup/   # cp runs only if mkdir succeeded
make test && make deploy             # deploy only if tests pass

|| — run next ONLY IF previous failed:

command_that_might_fail || echo "that failed"
rm /tmp/file || true                 # ignore if file doesn't exist

Common idiom: || exit:

cd /important/dir || exit 1          # bail if cd fails
required_var="${VAR:?}" || exit 1

Chaining:

build && test && deploy || rollback   # whole pipeline; rollback on any failure

if vs && — when each fits

# &&: short, when failure is the unusual case:
grep -q "foo" file.txt && echo "found"

# if: when there's a meaningful else, or multiple commands:
if grep -q "foo" file.txt; then
    echo "found"
    do_something
else
    echo "not found"
    handle_missing
fi

test (a.k.a. [ ]) and [[ ]]

The [ command tests conditions:

if [ -f config.txt ]; then echo "file exists"; fi
if [ "$x" -gt 5 ]; then echo "big"; fi
if [ "$name" = "Ada" ]; then echo "hi"; fi

[[ ]] is the bash extension — safer (no word splitting), supports regex:

if [[ "$x" -gt 5 ]]; then echo "big"; fi
if [[ "$email" =~ @ ]]; then echo "has @"; fi      # regex match

In bash scripts, prefer [[ ]] — fewer surprises with quoting.

Common test operators

  • [ -f file ] — regular file exists
  • [ -d dir ] — directory exists
  • [ -r file ] — readable
  • [ -w file ] — writable
  • [ -x file ] — executable
  • [ -z "$str" ] — empty string
  • [ -n "$str" ] — non-empty
  • [ "$a" = "$b" ] — string equality
  • [ "$a" -eq "$b" ] — integer equality
  • [ "$a" -lt "$b" ] — integer less-than

Common mistakes

  • Forgetting that 0 is success — opposite of most languages.
  • Unquoted variables in [ ][ $x = foo ] fails if $x is empty (becomes [ = foo ]). Use [ "$x" = foo ].
  • Using == in [ ] — POSIX requires =. Bash's [[ ]] allows both. Stick to = for portability.
  • Mistaking $? for the previous variable's value$? is the last EXIT STATUS, not the value.
  • Long && || && chains becoming illegible — switch to if/then/else after 2-3 conditions.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…