Exit status is the condition
In a shell, exit status 0 means success or true; a nonzero value means failure or false. if examines the status of a command or compound command and does not require a separate Boolean value. That means any command can be a condition:
if grep -q 'ERROR' app.log; then
echo "errors found"
elif [[ -s app.log ]]; then
echo "log is clean"
else
echo "log is empty"
fi
flowchart LR
C["Command or test<br/>[[ -f config.yml ]]"] --> S{"Exit status<br/>is 0?"}
S -- "yes" --> T["then branch"]
S -- "no" --> E["elif / else"]
T --> AND["cmd1 && cmd2<br/>runs on success"]
E --> OR["fallback branch<br/>runs on failure"]
%% step C: if runs a command; [[ ]] is just a command that tests strings, numbers or files.
%% step S: Exit status 0 counts as true, anything else as false.
%% step T: The then branch runs on success.
%% step E: Otherwise elif and else are checked in order.
%% step AND: cmd1 && cmd2 runs cmd2 only if cmd1 succeeded.
%% step OR: cmd1 || cmd2 runs cmd2 only if cmd1 failed, useful for error messages.
Test syntax
[ ... ] is the POSIX test command and works in sh; [[ ... ]] is a Bash keyword that handles empty variables more safely and adds pattern and regex matching. In Bash scripts prefer [[ ]].
| Kind | Correct example |
|---|---|
| Empty string | [[ -z $name ]] |
| Non-empty string | [[ -n $name ]] |
| Equal strings | [[ $a == $b ]] |
| Different strings | [[ $a != $b ]] |
| Glob pattern | [[ $file == *.log ]] |
| Regular expression | [[ $ver =~ ^v[0-9]+$ ]] |
Number inside [[ ]] |
[[ $x -gt $y ]] |
| Arithmetic number | (( x > y )) |
| Readable regular file | [[ -f $p && -r $p ]] |
Numeric operators are -eq, -ne, -lt, -le, -gt, and -ge. < and > inside [[ ... ]] compare strings lexicographically; use -lt or -gt for numbers or switch to (( ... )). A compound command needs spaces: [[ -f file ]]; writing [[ -f file]] leaves no valid closing token.
File tests
File tests distinguish -e exists, -f regular file, -d directory, -L symlink, and -s nonempty. -r, -w, and -x check whether the current user can read, write, or execute. -nt and -ot compare modification times, not the time a file was last opened.
Short-circuit operators
&& runs the next command only if the previous one succeeded, and || only if it failed: mkdir -p out && cd out and cd /data || exit 1 are common one-line guards. Combine conditions with && and || inside [[ ]], and negate with !, as in [[ ! -d $dir ]].
Finish a script with an explicit exit 0 or exit 1 when other tools depend on its result.