test / [ ] / [[ ]]
| Since: | test / [ ] | POSIX(sh互換) |
|---|---|---|
| [[ ]] | Bash(bash拡張) |
The test command in shell scripts is a built-in command that evaluates a condition and returns exit status 0 (true) or 1 (false). [ ] is an alias for test and behaves identically. In bash and zsh, the extended compound command [[ ]] is available. [[ ]] supports combining multiple conditions with && and ||, glob pattern matching with ==, and regular expression matching with =~. Use test / [ ] when POSIX compliance is required, and use [[ ]] in bash-specific scripts for safer, more readable code.
Equivalence of test and [ ]
test condition and [ condition ] are completely equivalent. [ is a shell built-in command that requires ] as its last argument. A space before ] is always required.
| Syntax | Description |
|---|---|
test "$a" = "$b" | Evaluates whether strings a and b are equal. POSIX-compliant. |
[ "$a" = "$b" ] | Completely equivalent to test. Spaces inside the brackets are required. |
[ "$a" = "$b" ] ; echo $? | Checks the exit status. Displays 0 for true and 1 for false. |
# Confirm that test and [ ] behave identically name="Tsunemori Akane" # Using test test "$name" = "Tsunemori Akane" echo "test result: $?" # 0 (true) # Using [ ] (equivalent to test) [ "$name" = "Tsunemori Akane" ] echo "[ ] result: $?" # 0 (true) # Example that evaluates to false [ "$name" = "Kogami Shinya" ] echo "false result: $?" # 1 (false)
$ bash test_same.sh test result: 0 [ ] result: 0 false result: 1
bash Extensions with [[ ]]
[[ ]] is a compound command built into bash and zsh. Unlike [ ], it is processed by the shell as a keyword, so &&, ||, <, and > can be used directly inside it. Variables do not undergo word splitting even without quoting, making scripts safer to write.
| Feature | Syntax | Description |
|---|---|---|
| AND combination | [[ condA && condB ]] | True when both A and B are true. Can be written directly inside [[ ]]. |
| OR combination | [[ condA || condB ]] | True when either A or B is true. Can be written directly inside [[ ]]. |
| Glob pattern | [[ "$str" == pattern* ]] | Evaluates the right-hand side of == as a glob pattern. It is important not to quote the pattern. |
| Regex match | [[ "$str" =~ regex ]] | Evaluates using POSIX extended regular expressions. Matched portions are stored in the BASH_REMATCH array. |
| Lexicographic comparison | [[ "$a" < "$b" ]] | < and > can be used directly for lexicographic string comparison. |
| No word splitting | [[ $var = "value" ]] | $var is not subject to word splitting even without quoting (which is dangerous with [ ]). |
Choosing Between [ ] and [[ ]]
| Feature | [ ] (test) | [[ ]] (bash extension) |
|---|---|---|
| POSIX compliance | Compliant. Works with sh and Alpine Linux. | bash / zsh only. Cannot be used with #!/bin/sh. |
| AND / OR with multiple conditions | Must split into separate brackets: [ condA ] && [ condB ]. | Can be written inside a single [[ ]]: [[ condA && condB ]]. |
| Glob pattern matching | Not available. | Glob patterns can be used on the right-hand side of == (no quoting needed). |
| Regex matching | Not available. | POSIX extended regular expressions are available via =~. |
| Variable quoting | Quoting is always required to prevent word splitting on empty values. | Works safely without quoting. |
< / > | Must be escaped as \< / \> to avoid being interpreted as redirections. | Can be used directly as lexicographic string comparison operators. |
Sample Code
psychopass_test_basic.sh
#!/bin/bash
# -----------------------------------------------
# Demonstrates the equivalence of test and [ ]
# using PSYCHO-PASS characters
# -----------------------------------------------
# -----------------------------------------------
# Set character information into variables
# -----------------------------------------------
inspector="Tsunemori Akane"
enforcer="Kogami Shinya"
division=1
hue="clear"
echo "=== test and [ ] are completely equivalent ==="
echo ""
# String comparison using test
if test "$inspector" = "Tsunemori Akane"; then
echo "[test] ${inspector} is an Inspector in Division 1."
fi
# Same condition written with [ ] (behaves identically)
if [ "$inspector" = "Tsunemori Akane" ]; then
echo "[ ] ${inspector} is an Inspector in Division 1."
fi
echo ""
echo "=== Check string length with -z / -n ==="
echo ""
# Check if a string is empty with -z
alias_name=""
if [ -z "$alias_name" ]; then
echo "[test -z] alias_name is empty."
fi
# Check if a string is non-empty with -n
if [ -n "$enforcer" ]; then
echo "[test -n] enforcer is set to \"${enforcer}\"."
fi
echo ""
echo "=== Numeric comparison ==="
echo ""
# Numeric equality comparison with -eq
if [ "$division" -eq 1 ]; then
echo "Assigned to: Public Safety Bureau Criminal Investigation Division ${division}"
fi
# Numeric less-than comparison with -lt
crime_coefficient=40
threshold=100
if [ "$crime_coefficient" -lt "$threshold" ]; then
echo "Crime coefficient ${crime_coefficient}: Clear. Not subject to enforcement."
fi
$ chmod +x psychopass_test_basic.sh $ ./psychopass_test_basic.sh === test and [ ] are completely equivalent === [test] Tsunemori Akane is an Inspector in Division 1. [ ] Tsunemori Akane is an Inspector in Division 1. === Check string length with -z / -n === [test -z] alias_name is empty. [test -n] enforcer is set to "Kogami Shinya". === Numeric comparison === Assigned to: Public Safety Bureau Criminal Investigation Division 1 Crime coefficient 40: Clear. Not subject to enforcement.
psychopass_double_bracket.sh
#!/bin/bash
# -----------------------------------------------
# Demonstrates bash extensions of [[ ]]
# using PSYCHO-PASS characters
# (&&, ||, glob patterns, regex matching)
# -----------------------------------------------
# -----------------------------------------------
# Set character information into an associative array
# -----------------------------------------------
declare -A role
role["Tsunemori Akane"]="Inspector"
role["Ginoza Nobuchika"]="Inspector"
role["Kogami Shinya"]="Enforcer"
role["Makishima Shogo"]="Latent Criminal"
role["Masaoka Tomomi"]="Enforcer"
echo "=== Combine multiple conditions in one [[ ]] with && ==="
echo ""
name="Kogami Shinya"
division=1
# With [ ], AND requires two separate brackets:
# [ "$role[$name]" = "Enforcer" ] && [ "$division" -eq 1 ]
# With [[ ]], && can be written inside a single bracket
if [[ "${role[$name]}" = "Enforcer" && "$division" -eq 1 ]]; then
echo "${name} is an Enforcer in Division ${division}."
fi
echo ""
echo "=== Write OR conditions with || ==="
echo ""
target="Makishima Shogo"
# Check if the character is an Inspector or Enforcer
if [[ "${role[$target]}" = "Inspector" || "${role[$target]}" = "Enforcer" ]]; then
echo "${target} is a member of the Public Safety Bureau."
else
echo "${target} is not a member of the Public Safety Bureau. Role: ${role[$target]}"
fi
echo ""
echo "=== Glob pattern matching with == ==="
echo ""
# The right-hand side must not be quoted to be interpreted as a glob
for chara in "${!role[@]}"; do
# Extract characters whose role ends with "er"
if [[ "${role[$chara]}" == *er ]]; then
echo " [match] ${chara}: ${role[$chara]}"
fi
done
echo ""
echo "=== Regex matching with =~ ==="
echo ""
# Sample data in "name:coefficient" format
data_list=(
"Tsunemori Akane:40"
"Ginoza Nobuchika:55"
"Kogami Shinya:139"
"Makishima Shogo:undefined"
"Masaoka Tomomi:170"
)
# Extract entries with a numeric crime coefficient using a regex
echo " Characters with a numeric crime coefficient:"
for entry in "${data_list[@]}"; do
# Match using POSIX extended regex with =~
# BASH_REMATCH[1] holds the first capture group
if [[ "$entry" =~ ^([^:]+):([0-9]+)$ ]]; then
chara_name="${BASH_REMATCH[1]}"
coefficient="${BASH_REMATCH[2]}"
echo " ${chara_name}: crime coefficient ${coefficient}"
fi
done
$ chmod +x psychopass_double_bracket.sh
$ ./psychopass_double_bracket.sh
=== Combine multiple conditions in one [[ ]] with && ===
Kogami Shinya is an Enforcer in Division 1.
=== Write OR conditions with || ===
Makishima Shogo is not a member of the Public Safety Bureau. Role: Latent Criminal
=== Glob pattern matching with == ===
[match] Tsunemori Akane: Inspector
[match] Ginoza Nobuchika: Inspector
[match] Kogami Shinya: Enforcer
[match] Masaoka Tomomi: Enforcer
=== Regex matching with =~ ===
Characters with a numeric crime coefficient:
Tsunemori Akane: crime coefficient 40
Ginoza Nobuchika: crime coefficient 55
Kogami Shinya: crime coefficient 139
Masaoka Tomomi: crime coefficient 170
psychopass_comparison.sh
#!/bin/bash
# -----------------------------------------------
# Compares the differences between [ ] and [[ ]]
# using PSYCHO-PASS characters
# -----------------------------------------------
# -----------------------------------------------
# Set up data for testing
# -----------------------------------------------
# List of character names
characters=("Tsunemori Akane" "Kogami Shinya" "Makishima Shogo" "Ginoza Nobuchika" "Masaoka Tomomi")
echo "=== Multiple conditions with [ ] (brackets must be separated) ==="
echo ""
name="Tsunemori Akane"
coefficient=40
# With [ ], AND requires connecting two brackets with && on the outside
if [ "$name" = "Tsunemori Akane" ] && [ "$coefficient" -lt 100 ]; then
echo "[ ] AND: ${name} (coefficient ${coefficient}) is not subject to enforcement."
fi
echo ""
echo "=== Multiple conditions with [[ ]] (&& can be written inside the brackets) ==="
echo ""
# With [[ ]], && and || can be written inside a single bracket
if [[ "$name" = "Tsunemori Akane" && "$coefficient" -lt 100 ]]; then
echo "[[ ]] AND: ${name} (coefficient ${coefficient}) is not subject to enforcement."
fi
echo ""
echo "=== Glob pattern: find names starting with 'Kog' ==="
echo ""
for chara in "${characters[@]}"; do
# Glob pattern matching with == in [[ ]] (right-hand side is not quoted)
if [[ "$chara" == Kog* ]]; then
echo " [glob match] ${chara}"
fi
done
echo ""
echo "=== Regex: check if a name consists of two words ==="
echo ""
for chara in "${characters[@]}"; do
# Check if the name has exactly two space-separated words
if [[ "$chara" =~ ^[^ ]+\ [^ ]+$ ]]; then
echo " [2 words] ${chara}"
else
echo " [other] ${chara}"
fi
done
echo ""
echo "=== Lexicographic comparison: [ ] requires \<, [[ ]] allows < directly ==="
echo ""
a="Tsunemori Akane"
b="Kogami Shinya"
# With [ ], < must be escaped or it is interpreted as a redirect
if [ "$a" \< "$b" ]; then
echo "[ ] lexicographic: \"${a}\" comes before \"${b}\"."
fi
# With [[ ]], < can be used directly
if [[ "$a" < "$b" ]]; then
echo "[[ ]] lexicographic: \"${a}\" comes before \"${b}\"."
fi
$ chmod +x psychopass_comparison.sh $ ./psychopass_comparison.sh === Multiple conditions with [ ] (brackets must be separated) === [ ] AND: Tsunemori Akane (coefficient 40) is not subject to enforcement. === Multiple conditions with [[ ]] (&& can be written inside the brackets) === [[ ]] AND: Tsunemori Akane (coefficient 40) is not subject to enforcement. === Glob pattern: find names starting with 'Kog' === [glob match] Kogami Shinya === Regex: check if a name consists of two words === [2 words] Tsunemori Akane [2 words] Kogami Shinya [2 words] Makishima Shogo [2 words] Ginoza Nobuchika [2 words] Masaoka Tomomi === Lexicographic comparison: [ ] requires \<, [[ ]] allows < directly === [ ] lexicographic: "Tsunemori Akane" comes before "Kogami Shinya". [[ ]] lexicographic: "Tsunemori Akane" comes before "Kogami Shinya".
Using BASH_REMATCH
When a regex match with =~ succeeds, the match results are stored in the array variable BASH_REMATCH. BASH_REMATCH[0] holds the entire matched string, and BASH_REMATCH[1] onward correspond to capture groups (the portions enclosed in parentheses).
| Variable | Contents | Description |
|---|---|---|
BASH_REMATCH[0] | Entire matched string | Contains the portion of the string matched by the whole regex. |
BASH_REMATCH[1] | First capture group | Contains the portion matched by the first () in the regex. |
BASH_REMATCH[2] | Second capture group | Contains the portion matched by the second () in the regex. |
| No match | Array is empty | =~ returns false and nothing is stored in BASH_REMATCH. |
#!/bin/bash
# -----------------------------------------------
# Extract capture groups using BASH_REMATCH
# -----------------------------------------------
# Extract each part from a string in "name:crime_coefficient" format
record="Kogami Shinya:139"
# Define capture groups with ()
if [[ "$record" =~ ^([^:]+):([0-9]+)$ ]]; then
echo "Full match: ${BASH_REMATCH[0]}" # Kogami Shinya:139
echo "Name: ${BASH_REMATCH[1]}" # Kogami Shinya
echo "Crime coefficient: ${BASH_REMATCH[2]}" # 139
else
echo "Format does not match."
fi
$ bash bash_rematch.sh Full match: Kogami Shinya:139 Name: Kogami Shinya Crime coefficient: 139
Notes on Glob Patterns
When using glob pattern matching with [[ "$var" == pattern ]], quoting the right-hand side pattern causes it to be treated as a string literal. To make the pattern function as a glob, it is important to leave the right-hand side unquoted.
| Syntax | Behavior | Description |
|---|---|---|
[[ "$str" == *er ]] | Glob pattern match | *er is evaluated as a glob. Matches strings ending with "er". |
[[ "$str" == "*er" ]] | Exact string match | Quoting makes it an exact match against the literal string *er. |
[[ "$str" == Kog* ]] | Prefix match | Matches strings starting with "Kog". |
[[ "$str" == *go ]] | Suffix match | Matches strings ending with "go". |
[[ "$str" == *ami* ]] | Substring match | Matches strings containing "ami". |
Summary
The test command and [ ] in shell scripts are completely equivalent and provide POSIX-compliant condition evaluation. Because they work in any shell including sh, use [ ] when portability is a priority. The bash/zsh-specific [[ ]] is processed as a compound command, so && and || can be used directly inside the brackets, and both glob pattern matching with == and regular expression matching with =~ are supported. Variables are not subject to word splitting, making scripts safer. In bash scripts, using [[ ]] as the default and switching to [ ] only when portability is needed is good practice. Regex match results are stored in the BASH_REMATCH array. For combining these constructs with if statements, see also if statement.
If you find any errors or copyright issues, please contact us.