100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Bash

Advanced Conditionals and Test Operators

Master Bash's test, [ ], and [[ ]] constructs with string, numeric, and file test operators to write robust, bug-free conditional logic.

Control FlowIntermediate9 min readJul 10, 2026
Analogies

From test to [[ ]]: Choosing the Right Conditional Construct

Bash offers three ways to evaluate a condition: the test command, the POSIX [ ] builtin, and the Bash-specific [[ ]] keyword. test expr and [ expr ] are functionally identical and behave like any other command, meaning the shell performs word splitting and pathname expansion on unquoted variables inside them, which is a frequent source of bugs. [[ expr ]] is parsed specially by Bash itself, so it never word-splits or globs unquoted variables, supports &&, ||, and </> without escaping, and adds the =~ regex-match operator. For any script that only needs to run under Bash, [[ ]] is the safer default.

🏏

Cricket analogy: Choosing [[ ]] over [ ] is like a captain picking DRS over the on-field umpire's naked eye for an lbw shout against Kohli — both can rule, but one has built-in safeguards against a bad call.

String Comparison Operators

Inside [[ ]], strings are compared with = or == for equality, != for inequality, < and > for lexicographic ordering (locale-dependent), -z to test for an empty string, and -n to test for a non-empty one. Because [[ ]] never word-splits, [[ $name == "" ]] is safe even if $name is unset, whereas the same check inside [ ] without quotes — [ $name == "" ] — can collapse to [ == "" ] and throw a syntax error. == inside [[ ]] also enables glob-style pattern matching on the right-hand side, so [[ $file == *.log ]] works without invoking case or grep.

🏏

Cricket analogy: Testing -z "$name" is like checking whether a batter's name is even on the scorecard before commentating on their strike rate — you can't compare = 0 if there's no entry at all.

Numeric Comparison Operators

Numeric comparisons use dedicated two-letter operators — -eq, -ne, -lt, -le, -gt, -ge — inside test, [ ], or [[ ]], because = and == always perform string comparison even when both operands look like numbers. A very common bug is writing [ $count = 10 ] expecting a numeric check; it works by accident for 10 but silently misbehaves for values like 010 versus 10 in some contexts, or fails entirely once whitespace sneaks in. For arithmetic-heavy conditionals, (( count > 10 )) is often clearer than [[ $count -gt 10 ]], since it uses familiar C-style operators and automatically treats bare variable names as numbers without needing $.

🏏

Cricket analogy: Using -gt instead of > for a run count is like insisting on comparing actual numeric totals — 45 not-out beats 9 — rather than comparing the players' names alphabetically, which would rank 'Ashwin' ahead of 'Kohli' for no cricketing reason.

File Test Operators

File test operators let a script inspect the filesystem before acting on it: -e checks existence of any kind, -f checks it's a regular file, -d checks it's a directory, -L checks it's a symbolic link, -r/-w/-x check read/write/execute permission for the current user, -s checks the file exists and is non-empty, and -nt/-ot compare modification times between two files. These are essential guards before operations like sourcing a config file, writing a log, or running an executable, because attempting the operation on a missing or wrong-type path produces a much less informative error than a clean upfront check does.

🏏

Cricket analogy: Checking -f "$pitchReport" before reading it is like a groundsman confirming the pitch report actually exists before the toss, rather than the captain guessing conditions and getting caught out by an unread wicket.

bash
#!/usr/bin/env bash
set -euo pipefail

config="/etc/myapp/config.yml"
logfile="/var/log/myapp.log"
count=${1:-0}

# File tests before acting
if [[ ! -f "$config" ]]; then
    echo "Error: config file not found at $config" >&2
    exit 1
fi

if [[ -w "$logfile" || ! -e "$logfile" ]]; then
    echo "Log target is writable or can be created."
fi

# String test
name=${2:-}
if [[ -z "$name" ]]; then
    echo "No name supplied, defaulting to 'guest'."
    name="guest"
fi

# Numeric test with (( ))
if (( count >= 10 )); then
    echo "$name triggered a high-count alert: $count"
elif [[ $count -gt 0 ]]; then
    echo "$name has a moderate count: $count"
fi

# Pattern match with ==
if [[ $config == *.yml ]]; then
    echo "Config is YAML formatted."
fi

Never write [ $var = value ] without quoting $var. If the variable is empty or unset, the shell reduces this to [ = value ], which is a syntax error; if it contains spaces or glob characters, word splitting and pathname expansion can silently change the comparison. Always write [ "$var" = value ], or switch to [[ $var = value ]], which is immune to word splitting even unquoted.

  • [[ ]] is a Bash keyword that avoids word splitting and globbing; [ ]/test are ordinary commands that do not.
  • Use =, !=, -z, -n for strings and -eq, -ne, -lt, -le, -gt, -ge for numbers — never mix the two.
  • [[ $x == pattern ]] supports glob matching on the right-hand side; =~ adds full regex matching.
  • (( expr )) uses familiar C-style numeric operators (>, <, >=) and treats bare variable names as numbers.
  • File test operators (-e -f -d -L -r -w -x -s -nt -ot) let you validate a path before acting on it.
  • Always quote variables inside [ ] to avoid syntax errors on empty values or corrupted comparisons from word splitting.
  • Prefer [[ ]] for Bash-only scripts; reserve [ ]/test for scripts that must remain POSIX-sh portable.

Practice what you learned

Was this page helpful?

Topics covered

#Bash#AdvancedBashScriptingStudyNotes#DevOps#AdvancedConditionalsAndTestOperators#Advanced#Conditionals#Test#Operators#Testing#StudyNotes#SkillVeris