Bash test operators
Bash conditional expressions decide which branch of an if, while or &&
chain runs. They live inside the test command, its [ ] alias, or the more
capable [[ ]] keyword. The operators fall into clear families: file-attribute
tests, string emptiness checks, string and numeric comparisons, regex matching,
and logical combinators. This page is a searchable, offline reference with a
working example for every operator.
How it works
A test operator evaluates to a true (exit status 0) or false (non-zero) result, which the surrounding control structure uses:
- File tests are unary operators like
-f(regular file),-d(directory) and-x(executable) that inspect a path, plus binary comparisons-nt,-otand-efthat compare two files by timestamp or inode. - String tests
-zand-ncheck for an empty or non-empty value. - Comparison uses
=/==/!=for strings and-eq,-ne,-lt,-le,-gt,-gefor integers — never mix the two. - Regex uses
[[ s =~ re ]], available only in double brackets, storing captures inBASH_REMATCH. - Logical operators negate (
!) or combine (&&,||, or the legacy-a,-o).
The most important rule: prefer [[ ]] for new scripts. It avoids word-splitting
surprises, supports glob and regex matching, and lets you use &&/|| inside a
single test.
Tips and examples
Always quote variables in [ ] to avoid syntax errors when a value is empty or
contains spaces:
if [ -z "$name" ]; then
echo "name is empty"
fi
Combine conditions cleanly inside double brackets:
if [[ -f "$config" && -r "$config" ]]; then
source "$config"
fi
Capture parts of a match with a regex:
if [[ "$ver" =~ ^([0-9]+)\.([0-9]+)$ ]]; then
major=${BASH_REMATCH[1]}
minor=${BASH_REMATCH[2]}
fi
Remember the numeric-versus-string distinction: use -eq family operators for
integers and =/!= for text, or comparisons will silently give the wrong
answer.
Edge cases and common mistakes
Empty-variable pitfalls. Inside [ ], an unquoted empty variable becomes
nothing at all, turning [ $x = "hello" ] into [ = "hello" ], which is a
parse error. Always quote: [ "$x" = "hello" ]. Inside [[ ]] this is not an
issue because the keyword does not word-split.
Combining conditions in [ ]. The -a and -o operators ([ a -a b ])
are POSIX but deprecated because they are ambiguous when an operand looks like
an operator. Use two separate tests joined by && or || at the shell level
instead, or switch to [[ ]].
-e versus -f. -e is true for any existing path — file, directory,
symlink, or device. -f is true only for regular files. A common mistake is
using -f to test whether something exists and then being surprised when a
directory returns false.
String comparison versus pattern matching. Inside [[ ]], the == and
!= operators support glob patterns on the right-hand side ([[ $file == *.log ]]), which is different from the =~ regex operator. Do not quote the
pattern or it is treated as a literal string.
Operator quick-reference
| Family | Operators | Works in |
|---|---|---|
| File existence | -e, -f, -d, -L, -s | [ ] and [[ ]] |
| File permissions | -r, -w, -x | [ ] and [[ ]] |
| File comparison | -nt, -ot, -ef | [ ] and [[ ]] |
| String empty/non-empty | -z, -n | [ ] and [[ ]] |
| String comparison | =, ==, !=, <, > | [ ] (some) / [[ ]] |
| Regex match | =~ | [[ ]] only |
| Integer comparison | -eq, -ne, -lt, -le, -gt, -ge | [ ] and [[ ]] |
| Logical | !, -a, -o, &&, || | mixed — use &&/|| in [[ ]] |