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$xis 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 toif/then/elseafter 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…