double-bracket — [[ ]] extended test in bash/zsh with regex and patterns across all 5 shells
Equivalents in every shell
[[ "$name" == "alice" ]]Bash KEYWORD (not a builtin and NOT a synonym for `[`). Word splitting and pathname expansion are DISABLED inside, so `$var` need not be quoted to avoid the "empty arg" trap. `==` does glob pattern matching when the RHS is unquoted; `=~` does ERE regex.
[[ $name == "alice" ]]Same as bash. Zsh `=~` regex captures land in `$match[N]` (instead of bash's `${BASH_REMATCH[N]}`). Pattern matching uses extended-glob syntax if `setopt EXTENDED_GLOB` is active in the script.
string match alice $nameFish has NO `[[ ]]`. The replacement primitives are `string match` (literal/glob), `string match -r` (regex), and the `test` builtin with `and` / `or` / `not` keywords. Each maps roughly to one bash extended-test use case.
if ("$name" -eq "alice") { ... }PowerShell comparison operators: `-eq`, `-ne` (equality), `-like` (glob), `-match` (regex), `-contains` (collection membership). All return `$true`/`$false`. NO quote-bug equivalent — operators always treat operands as values, not tokens.
if /i "%name%"=="alice" (echo yes)Cmd `if` is the only test primitive. `/i` is case-insensitive string equality; `equ`/`gtr`/`lss` handle numeric comparison; `exist` handles file existence. No pattern, glob, or regex support of any kind.
Worked examples
String equality without quote-bug risk
[[ $name == "alice" ]] && echo match[[ $name == "alice" ]] && echo matchtest "$name" = "alice"; and echo matchif ("$name" -eq "alice") { "match" }if /i "%name%"=="alice" echo matchGlob / pattern matching
[[ "$file" == *.log ]][[ "$file" == *.log ]]string match -q "*.log" -- $file$file -like "*.log"Regex matching with captured groups
[[ "$ip" =~ ^([0-9]+)\.([0-9]+)\. ]] && echo "${BASH_REMATCH[1]}.${BASH_REMATCH[2]}"[[ "$ip" =~ '^([0-9]+)\.([0-9]+)\.' ]] && echo "$match[1].$match[2]"if ($ip -match '^(\d+)\.(\d+)\.') { "$($Matches[1]).$($Matches[2])" }Gotchas
- `[[ ]]` is BASH/ZSH ONLY. POSIX `/bin/sh`, dash, ash, and busybox sh ALL reject it. Scripts with `#!/bin/sh` MUST use `[ ]` (POSIX `test`) — using `[[` is a frequent cause of "works on Ubuntu, fails on Alpine" incident reports.
- Bash `[[ $var == foo* ]]` does GLOB matching when the RHS is UNQUOTED. Quoting the RHS turns it into LITERAL string comparison: `[[ $var == "foo*" ]]` only matches the literal text `foo*`. The quoting flip silently changes the operator's meaning.
- Bash `=~` regex matching: the REGEX itself should be UNQUOTED on the RHS or stored in a variable first. `[[ $s =~ "^foo$" ]]` quotes the pattern → matches the LITERAL string `^foo$` (anchors lose their meaning). Use `[[ $s =~ ^foo$ ]]` or `re='^foo$'; [[ $s =~ $re ]]`.
- Capture groups land in `${BASH_REMATCH[N]}` (bash) or `$match[N]` (zsh). They are OVERWRITTEN by the next `[[ ... =~ ... ]]` invocation, so save the result immediately into a local variable if you need it for any subsequent step.
- PowerShell `-match` POPULATES the AUTOMATIC variable `$Matches` with the capture groups; `$Matches[0]` is the full match, `$Matches[1]` is group 1. Like bash, the variable is OVERWRITTEN on the next `-match` — copy values out before re-matching.