Dualo
Bash / Shell Scripting

Control flow — if, for, while, case

Branch on exit codes, loop over lists or streams, match patterns. Bash control flow is quirky — exit code 0 means true.

2 min read

[ vs [[: [ is a POSIX builtin that parses arguments like any command, so unquoted empty variables cause syntax errors ([ -z $x ][ -z ] → error). [[ is a Bash keyword with its own parser — safer with unquoted vars, supports pattern matching ([[ $f == *.log ]]) and regex ([[ $s =~ ^[0-9]+$ ]]), and uses &&/|| internally. Prefer [[ in Bash scripts; use [ only when targeting POSIX sh.

Arithmetic contexts: (( expr )) and $(( expr )) are C-like arithmetic, no $ needed on vars inside, </> are comparison (not redirection), and exit status follows C (0 → shell-false, non-zero → shell-true — OPPOSITE of normal $?!). ((i++)) increments. n=$((a + b)) assigns. Use these instead of [[ $a -lt $b ]] when doing real math.

Loop pitfall — reading files: for line in $(cat file) splits on whitespace (not newlines) and globs. The correct way is while IFS= read -r line; do ...; done < file: IFS= prevents leading/trailing whitespace trimming; -r disables backslash interpretation. This idiom is required for POSIX-correct line reading.

case patterns: use shell globs (*, ?, [abc]), not regex. Pipe | separates alternatives. ) closes each pattern; ;; terminates each branch. Variations: ;& falls through to the next branch (Bash 4+), ;;& continues testing remaining patterns. case is O(n) in patterns but each pattern is O(1) for string match — faster than chained if for many alternatives.

Short-circuit gotcha: cmd1 && cmd2 || cmd3 is NOT an if/else. If cmd1 succeeds and cmd2 fails, cmd3 still runs. Prefer explicit if cmd1; then cmd2; else cmd3; fi when the branches are not independent.

Exit code semantics per construct: if/while care only about the LAST command in their condition. Functions return the exit code of their last command unless return N is used. Pipelines return the last stage's exit (unless pipefail). Subshells ( ... ) return their own last exit. These rules compound — combined with set -e, surprising failures cascade.

Grounded on https://www.gnu.org/software/bash/manual/bash.html#Conditional-Constructs

Next up

Functions & arguments

Group commands into reusable units, accept positional parameters, and return exit codes. Not quite like functions in 'real' languages.