Skip to content
shellmap

double-bracket[[ ]] extended test in bash/zsh with regex and patterns across all 5 shells

Equivalents in every shell

Bashunix
[[ "$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.

Zshunix
[[ $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.

Fishunix
string match alice $name

Fish 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.

PowerShellwindows
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.

cmd.exewindows
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

Bash
[[ $name == "alice" ]] && echo match
Zsh
[[ $name == "alice" ]] && echo match
Fish
test "$name" = "alice"; and echo match
PowerShell
if ("$name" -eq "alice") { "match" }
cmd.exe
if /i "%name%"=="alice" echo match

Glob / pattern matching

Bash
[[ "$file" == *.log ]]
Zsh
[[ "$file" == *.log ]]
Fish
string match -q "*.log" -- $file
PowerShell
$file -like "*.log"

Regex matching with captured groups

Bash
[[ "$ip" =~ ^([0-9]+)\.([0-9]+)\. ]] && echo "${BASH_REMATCH[1]}.${BASH_REMATCH[2]}"
Zsh
[[ "$ip" =~ '^([0-9]+)\.([0-9]+)\.' ]] && echo "$match[1].$match[2]"
PowerShell
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.

WSL & PowerShell Core notes

pwshPowerShell's `-eq`/`-like`/`-match` family works identically on every platform. The bash idiom `[[ -e file ]]` (file-existence test inside extended-test) has no direct port — use `Test-Path file` instead. All string comparison operators are case-INSENSITIVE by default; prefix with `c` for case-sensitive (`-ceq`, `-clike`, `-cmatch`).
WSLInside WSL bash, `[[ ]]` works exactly like native Linux. Comparing a WSL path against a Windows path (`[[ /mnt/c/Users == C:* ]]`) is a frequent bug — neither side is canonicalised. Run `wslpath` first to convert to one representation before comparing.

Related commands