Inside bash's `[[ ]]`, how do the `==` and `=~` operators differ, and what changes when you quote the right-hand side, as in `[[ $v == "$p" ]]`?
answer
- the unquoted side is a pattern
- quotes turn matching into equality
- one speaks wildcards, one speaks regex
- captures land in an array
- keep the regex in a variable
basics
~20 sIn bash's [[ ]], == matches the left value against the right side as a shell pattern, while =~ matches it against an extended regular expression and fills the BASH_REMATCH array. Quoting the right side makes it a literal string, so neither kind of matching happens.
solid answer
~50 s`==` (and its synonym `=`) inside `[[ ]]` matches the left operand against the right operand as a **shell pattern**, so `[[ $s == *.txt ]]` is a wildcard test rather than an equality test. `=~` matches against a **POSIX extended regular expression** and stores the match plus its capture groups in the `BASH_REMATCH` array. Both read the right-hand side as a pattern only while it is unquoted: since bash 3.2, quoting any part of it makes that part literal, so `[[ $s == "*.txt" ]]` asks whether the value is exactly those five characters. That is the rule people trip over — quote when you mean equality, leave it bare when you mean matching. Because escaping a regex inline is painful, the idiom is `re='^v([0-9]+)'; [[ $s =~ $re ]]` with the variable unquoted. Note `=~` is unanchored: add `^` and `$` yourself.
code
bash · 3 liness=report.txt
[[ $s == *.txt ]] && echo "unquoted right side: pattern match"
[[ $s == "*.txt" ]] || echo "quoted right side: literal, no match"go deeper
Remember two things: == in [[ ]] matches a pattern, and quotes on the right make it a literal comparison. If you need equality, quote it.
Explain the pattern-versus-regex split, the bash 3.2 rule that quoted portions are literal, and the variable idiom for regexes. Show BASH_REMATCH pulling capture groups out of a version string.
Bring the failure modes: an unquoted right operand that came from data turns a comparison into a match, a quoted regex silently disables a branch, and an unanchored =~ lets junk pass validation. Say how you would test each one.
Own the boundary: shell pattern matching is fine for shape checks, but validation and extraction rules that grow capture groups and alternation are a signal the logic has outgrown a conditional and belongs in a program with tests.
## Two matching operators, two languages The `[[ ]]` keyword gives you two ways to ask "does this value look like that?", and they speak different pattern languages. `==` (spelled `=` if you prefer) matches the left operand against the right operand interpreted as a **shell pattern** — the same wildcard language the shell uses elsewhere. `[[ $file == *.log ]]` is true for `app.log`. If the right side happens to contain no wildcard characters, the match degenerates into plain equality, which is why so many people use `==` for years without noticing it is a matcher. `=~` matches against a **POSIX extended regular expression**, the same flavour `grep -E` accepts. `[[ $s =~ ^v[0-9]+ ]]` is true for `v12`. It is a different language with different metacharacters: `*` means "zero or more of the previous item" rather than "anything", and `.` means "any character" rather than a literal dot. ## The quoting rule that decides everything The right-hand side is treated as a pattern **only where it is unquoted**. From bash 3.2 onward, any quoted portion of the right operand is matched literally. ```bash s=report.txt [[ $s == *.txt ]] # true — wildcard match [[ $s == "*.txt" ]] # false — asks whether $s is literally *.txt ``` This cuts both ways, and both directions are real bugs: - You meant equality and left the right side unquoted, so a value containing `*`, `?` or `[` silently becomes a pattern. `[[ $input == $expected ]]` is a match, not a comparison. - You meant matching and quoted the right side, so nothing ever matches and the branch is dead. This is the classic "my regex does not work" report: `[[ $s =~ "^v[0-9]+" ]]` compares against those characters literally. The left operand is never a pattern. It is always the subject being matched, and — because `[[ ]]` is a keyword — it is never word-split or glob-expanded either, so it needs no quotes for safety. ## Keeping the regex in a variable Writing a regex inline forces you to escape every character that also means something to the shell parser, and you cannot quote your way out because quotes make it literal. The standard idiom is therefore to build the regex in a variable and reference it **unquoted**: ```bash re='^v([0-9]+)\.([0-9]+)' v=v1.22.3 if [[ $v =~ $re ]]; then echo "major=${BASH_REMATCH[1]} minor=${BASH_REMATCH[2]}" fi ``` Single quotes protect the regex at assignment time, where quoting is harmless, and the unquoted expansion at match time keeps it a regex. As a bonus the pattern gets a name, which makes the condition readable and the regex reusable. ## BASH_REMATCH A successful `=~` fills the array `BASH_REMATCH`: index 0 is the entire matched text, and indexes 1..n are the parenthesised capture groups, left to right. That makes `[[ ]]` a small parser as well as a test — you can validate a string and pull its pieces out in one step, without spawning another process. Two cautions. The array is overwritten by the next successful match anywhere in the shell, so read it immediately. And it is only meaningful after a match that succeeded — check the exit status first, which the `if` above does naturally. ## Anchoring `=~` searches for the pattern **anywhere** in the string. `[[ xv12y =~ v[0-9]+ ]]` is true. If you are validating input rather than searching it, anchor both ends explicitly with `^` and `$`; a missing anchor is the most common reason a "validation" regex accepts junk with a valid substring buried in it. ## Exit statuses 0 means matched, 1 means did not match, and 2 means the regex itself was invalid — bash prints a syntax error from the regex compiler. That third case is worth handling when the pattern comes from configuration rather than from the script, because a bad pattern otherwise looks exactly like "no match" to an `if`. ## Choosing between them Use `==` with an unquoted pattern for simple shape checks: a suffix, a prefix, a wildcard. Use `=~` when you need alternation, character classes or extraction. Use `==` with a **quoted** right side whenever you mean plain equality — and make that quoting a deliberate, visible choice, since it is the difference between comparing and matching.
- Why do people put the regex in a variable instead of writing it inline?Because you cannot quote an inline regex — since bash 3.2 quoted parts match literally — so every character that is also shell-significant would need a backslash. Assigning `re='^v([0-9]+)'` lets single quotes protect the pattern where quoting is harmless, and referencing `$re` unquoted keeps it a regex at match time. It also names the pattern, which makes the condition readable and reusable.
- Is `[[ $v =~ ^v[0-9]+$ ]]` anchored by default if you leave off the `^` and `$`?No. `=~` looks for the pattern anywhere in the subject, so without anchors `v[0-9]+` matches inside `xxv12yy`. For validation always anchor both ends; for searching, leave them off deliberately. Forgetting the anchors is the single most common reason a regex-based input check accepts values it should reject.
- What ends up in BASH_REMATCH, and how long does it stay valid?Index 0 holds the whole matched text and indexes 1..n hold the capture groups in left-to-right order of their opening parentheses. It is a normal shell array, overwritten by the next successful `=~` anywhere in the shell, so read it immediately after the match and copy anything you need into your own variables. Only trust it when the match returned status 0.
saying these in an interview costs you the question
- Quotes the regex and wonders why nothing ever matches
- Thinks =~ takes shell wildcards rather than a regex
- Assumes =~ is anchored, so a substring cannot sneak through
- Expects a quoted right side to still do wildcard matching
- Reads BASH_REMATCH long after the match that filled it