Tests, Conditions, case, and Boolean Logic
Turn command exit status, file and string tests, arithmetic conditions, pattern matching, and case dispatch into explicit control flow without hiding failures.
Learning objectives
By the end of this lesson
- Use command success and failure directly as Bash conditions.
- Choose among
test,[ ],[[ ]], and(( )). - Combine conditions with
!,&&, and||without losing error meaning. - Dispatch modes and patterns with
case. - Build a configuration validator with precise diagnostics and exit status.
1. Bash conditions are commands and exit statuses
In Bash, truth is operational. A command that returns status zero is successful and therefore true in an if, while, or until condition. A nonzero status is false. The shell does not require a Boolean object; it evaluates command results.
flowchart TD
C["Run condition command"] --> S{"Exit status is 0?"}
S -- yes --> T["Execute then branch"]
S -- no --> E{"elif condition exists?"}
E -- yes --> C2["Run next condition command"]
E -- no --> F["Execute else branch if present"]
C2 --> S2{"Exit status is 0?"}
S2 -- yes --> T2["Execute elif branch"]
S2 -- no --> Fif systemctl is-active --quiet ssh.service; then
printf 'SSH service is active\n'
else
status=$?
printf 'SSH service is not active; status=%d\n' "$status" >&2
fi
# Prefer testing the command directly over running it and then examining $?.
if grep -q '^PermitRootLogin no$' /etc/ssh/sshd_config 2>/dev/null; then
printf 'Explicit root-login policy found\n'
fi$? is overwritten by the next command. Capture it immediately only when you need the numeric value. For ordinary branching, put the command directly in the conditional so the relationship remains visible.
2. Choose the conditional form for the data model
test expressionPortable file, string, and integer testsCommand syntax; arguments must be separated correctly[ expression ]Portable shell tests with familiar punctuation[ is a command; the closing ] is an argument[[ expression ]]Bash string logic, patterns, regex, and compound testsNo word splitting or pathname expansion on ordinary parameter expansions(( expression ))Integer arithmetic conditionsTrue when the arithmetic result is nonzeroconfig=/etc/ssh/sshd_config
service_name='api-worker'
replicas=3
if [[ -r $config && -s $config ]]; then
printf 'Readable, non-empty configuration: %s\n' "$config"
fi
if [[ $service_name == api-* ]]; then
printf 'API service family\n'
fi
if [[ $service_name =~ ^[a-z][a-z0-9-]{2,31}$ ]]; then
printf 'Valid service identifier\n'
fi
if (( replicas >= 2 && replicas <= 10 )); then
printf 'Replica count is in policy range\n'
fiInside [[ ]], an unquoted right-hand side of == is a pattern. Quoting it requests literal comparison. The right-hand side of =~ is a regular expression; store complex expressions in a variable rather than fighting multiple parsing layers.
3. File and string tests answer specific questions
Tests should correspond to the property the script actually needs. Existence alone does not prove readability, regular-file type, non-emptiness, ownership, or safe permissions. Likewise, a non-empty string is not necessarily a valid identifier, path, number, URL, or environment name.
path=${1:-}
if [[ -z $path ]]; then
printf 'A path argument is required\n' >&2
elif [[ ! -e $path ]]; then
printf 'Path does not exist: %s\n' "$path" >&2
elif [[ -L $path ]]; then
printf 'Symbolic link: %s -> %s\n' "$path" "$(readlink -- "$path")"
elif [[ -f $path && -r $path ]]; then
printf 'Readable regular file: %s\n' "$path"
elif [[ -d $path && -x $path ]]; then
printf 'Searchable directory: %s\n' "$path"
else
printf 'Path exists but does not satisfy the required policy: %s\n' "$path" >&2
fiTime-of-check/time-of-use races can still occur between a test and a later operation. Tests improve diagnostics and policy, but the operation itself must handle failure. Do not use pre-checks as a substitute for checking the actual command result.
4. Boolean operators also control command execution
&& executes the next command only after success; || executes it only after failure; ! reverses a command’s truth value. These are command-list operators, not merely punctuation inside expressions.
mkdir -p -- "$HOME/devops-academy/cache" &&
printf 'Cache directory is ready\n'
if ! command -v shellcheck >/dev/null 2>&1; then
printf 'shellcheck is not installed\n' >&2
fi
# Group recovery so the intended failure handling is clear.
if ! cp -- source.conf destination.conf; then
printf 'Copy failed; destination was not updated\n' >&2
exit 1
fi
# Avoid using "cmd1 && cmd2 || fallback" as a general if/else replacement.
# fallback also runs when cmd2 fails, even if cmd1 succeeded.When a failure matters, use an explicit if ! command; then ... fi block. It gives you room to capture context, choose an exit status, and distinguish expected absence from operational failure.
5. case expresses multi-way dispatch and shell patterns
case compares one word against ordered shell patterns. It is ideal for modes, file extensions, environment names, and compact option-like commands. Patterns are not regular expressions: *, ?, bracket expressions, and alternation with | follow shell pattern rules.
environment=${1:-}
case $environment in
development|dev)
log_level=debug
replicas=1
;;
staging|stage)
log_level=info
replicas=2
;;
production|prod)
log_level=warn
replicas=4
;;
'')
printf 'Environment is required\n' >&2
exit 2
;;
*)
printf 'Unsupported environment: %s\n' "$environment" >&2
exit 2
;;
esac
printf 'environment=%s log_level=%s replicas=%d\n' \
"$environment" "$log_level" "$replicas"6. Hands-on lab: build a deployment-input validator
The validator accepts an environment, service name, replica count, and configuration file. It writes no system state and returns a distinct usage or validation status.
lab="$HOME/devops-academy/linux/chapter14/lesson02"
mkdir -p "$lab"
cd "$lab"
cat > validate-deployment.sh <<'SCRIPT'
#!/usr/bin/env bash
usage() {
printf 'Usage: %s ENVIRONMENT SERVICE REPLICAS CONFIG_FILE\n' "${0##*/}" >&2
}
main() {
if (( $# != 4 )); then
usage
return 2
fi
local environment=$1 service=$2 replicas=$3 config_file=$4
local name_pattern='^[a-z][a-z0-9-]{2,31}$'
case $environment in
development|staging|production) ;;
*) printf 'Invalid environment: %s\n' "$environment" >&2; return 3 ;;
esac
if [[ ! $service =~ $name_pattern ]]; then
printf 'Invalid service name: %s\n' "$service" >&2
return 3
fi
if [[ ! $replicas =~ ^[0-9]+$ ]] || (( replicas < 1 || replicas > 20 )); then
printf 'Replicas must be an integer from 1 through 20: %s\n' "$replicas" >&2
return 3
fi
if [[ ! -f $config_file || ! -r $config_file || ! -s $config_file ]]; then
printf 'Configuration must be a readable, non-empty regular file: %s\n' "$config_file" >&2
return 3
fi
printf 'VALID env=%s service=%s replicas=%s config=%s\n' \
"$environment" "$service" "$replicas" "$config_file"
}
main "$@"
SCRIPT
chmod u+x validate-deployment.sh
printf 'listen=127.0.0.1\n' > 'service config.conf'
./validate-deployment.sh staging payments-api 3 'service config.conf'
./validate-deployment.sh prod 'Bad Name' 3 'service config.conf' || printf 'rejected as expected\n'
./validate-deployment.sh production payments-api 0 'service config.conf' || printf 'rejected as expected\n'
bash -n validate-deployment.shVerification checklist
7. Common conditional mistakes
“Square brackets are punctuation.”
[ is a command form. Missing spaces change its arguments and therefore its syntax.
“Existence means usable.”
Test the properties the operation needs, such as regular-file type, readability, size, or directory search permission.
“cmd1 && cmd2 || fallback is always if/else.”
The fallback also runs when cmd2 fails, which may conceal a partial success.
“Regex and glob patterns are interchangeable.”
case and [[ == ]] use shell patterns; [[ =~ ]] uses regular expressions.
8. Knowledge check
Question 1. Why can a command appear directly after if?
Question 2. When is (( expression )) preferable to [ "$n" -gt 3 ]?
Question 3. What is the key semantic difference between [[ $x == api-* ]] and [[ $x == "api-*" ]]?
9. Summary
Bash control flow is built on command status. Use direct command conditions, select the test form that matches the data, distinguish patterns from regular expressions, and keep failure handling explicit. Conditions should improve policy and diagnostics while the real operation still handles races and runtime errors.
10. Further reading
- GNU Bash Reference Manual — Conditional Constructs.
- GNU Bash Reference Manual — Conditional Expressions.
- GNU Bash Reference Manual — Lists of Commands.
help test,help [[,help ((, andhelp caseon the target Bash version.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this address.