Exit codes and conditions
Use exit statuses, && and ||, if with [[ ]] and (( )), and case to make decisions.
- Read and set exit statuses, and chain commands with && and ||
- Write if/elif/else with [[ ]] string, number and file tests and (( )) arithmetic
- Match patterns with case and regular expressions with =~
Every command finishes with an exit status: a number from 0 to 255. 0 means success; anything else is a kind of failure (grep uses 1 for “no match” and 2 for “error”). The last one is in $?. Your scripts set theirs with exit 0 or exit 1.
This turns the shell into a logic machine:
a && b- runbonly ifasucceeded:mkdir build && cd builda || b- runbonly ifafailed:ping -c1 relay || echo "relay down"! a- flip success and failure
1true; echo "true -> $?"
2false; echo "false -> $?"
3grep -q coffee <<< "coffee machine"; echo "found -> $?"
4grep -q tea <<< "coffee machine"; echo "not found -> $?"
5ls /no/such/dir 2>/dev/null; echo "ls error -> $?"
6mkdir -p /tmp/demo && echo "made it"
7grep -q tea <<< "coffee" || echo "no tea aboard"true -> 0 false -> 1 found -> 0 not found -> 1 ls error -> 2 made it no tea aboard
if: run commands, branch on their status
if doesn’t test “true or false” - it runs a command and checks whether it succeeded:
1if grep -q ERROR station.log; then
2 echo "Errors found"
3elif grep -q WARN station.log; then
4 echo "Warnings only"
5else
6 echo "All clear"
7fiFor comparisons, the command is usually [[ ... ]], Bash’s test syntax (the spaces inside the brackets are required):
| Test | True when |
|---|---|
[[ $a == $b ]] / != | strings are equal / differ (an unquoted right side is a glob pattern: [[ $f == *.log ]]) |
[[ -z $s ]] / [[ -n $s ]] | string is empty / not empty |
[[ $n -eq 5 ]] | numbers: -eq -ne -lt -le -gt -ge |
[[ -e $p ]] -f -d | path exists / is a file / is a directory |
[[ -r $p ]] -w -x -s | readable / writable / executable / not empty |
[[ $s =~ ^[0-9]+$ ]] | matches a regex; groups land in BASH_REMATCH |
[[ a && b ]], [[ a || b ]], [[ ! a ]] | combine tests |
For numbers, (( ... )) reads more naturally: if (( fuel < 20 )); then.
Try it
Succeed or fail?
Predict the exit status of each test. Remember: 0 is success.
“[[ 10 -lt 9 ]]”
“[[ 10 < 9 ]]”
“[[ -z "" ]]”
“[[ reactor.log == *.log ]]”
“[[ reactor.log == "*.log" ]]”
“(( 3 * 4 > 10 ))”
“[[ KS-042 =~ ^KS-[0-9]+$ ]]”
“! true”
1cd "$(mktemp -d)"
2touch manifest.txt
3mkdir cargo
4for path in manifest.txt cargo missing; do
5 if [[ -f $path ]]; then echo "$path: file"
6 elif [[ -d $path ]]; then echo "$path: directory"
7 else echo "$path: not found"
8 fi
9done
10code="KS-204"
11if [[ $code =~ ^KS-([0-9]+)$ ]]; then
12 echo "ticket number ${BASH_REMATCH[1]}"
13fimanifest.txt: file cargo: directory missing: not found ticket number 204
case: many patterns, one value
case compares one value against glob patterns, top to bottom, and runs the first match. | separates alternatives, *) catches everything else, and each branch ends with ;;. It’s the natural way to handle commands and options:
1for command in status open close self-destruct; do
2 case $command in
3 status) echo "All systems nominal" ;;
4 open|close) echo "Airlock: ${command}d" ;;
5 self-*) echo "Nice try." ;;
6 *) echo "Unknown: $command" ;;
7 esac
8doneAll systems nominal Airlock: opend Airlock: closed Nice try.
Spotted the bug? ${command}d makes “opend”. Real scripts are full of tiny surprises like this - which is why you test them with real input.
Key takeaways
Exit status 0 is success;
$?holds the last one;exit Nsets your script’s.&&runs on success,||on failure.ifruns a command;[[ ]]tests strings, numbers, files and regexes;(( ))compares numbers.casematches one value against glob patterns.
Lesson quiz
7 questions · pass with 5 correct · up to 50 XP
Passing this quiz completes the lesson and keeps your streak going. Questions you miss come back in review sessions later.
Practice: write Bash scripts
Write a script in the editor and run it for real against sample input. Each run gets a fresh Linux sandbox with Bash 5.2 and the GNU tools on Wandbox, a free public service - so experiment freely, even with rm. Your script and test input are sent there.
Fuel gauge
stdin is a fuel reading. Print exactly one word:
INVALIDif it isn’t a whole number from 0 to 100 (check digits with a regex first!)CRITICALbelow 10LOWbelow 30OKotherwise
- 7 is critical
- 29 is low
- 30 is ok
- 100 is ok
- 140 is invalid
- Text is invalid
Your script runs with Bash 5.2 and GNU tools on Wandbox, a free public service, in a fresh sandbox each time. Your script and test input are sent to that service.
Airlock command handler
stdin is one command line: a command and maybe an argument. Use read -r command target and a case statement:
openorclose→Airlock 2 opening/Airlock 2 closing(the target is the airlock number; use1if none is given)status→All airlocks sealed- anything else →
Unknown command: <command>
- open 2
- close
- status
- dance
Your script runs with Bash 5.2 and GNU tools on Wandbox, a free public service, in a fresh sandbox each time. Your script and test input are sent to that service.
Questions about this lesson
Stuck? Ask. Figured something out? Share it. Explaining is one of the best ways to learn.
Loading posts…