In bash, `[[ $file == *.txt ]]` and `[[ $file == "*.txt" ]]` behave differently. What does quoting the right-hand side of `==` inside `[[ ]]` do, and where else in bash does quoting turn a pattern into a literal string?
answer
- one side is not an ordinary string
- quotes flip pattern into literal
- the effect is per-character, not per-side
- case labels obey the same rule
- the regex operator inverts the habit
basics
~20 sQuoting the right-hand side turns a pattern into literal text. Unquoted, *.txt is a glob matched against the value; quoted, it matches only the exact four-character-plus string *.txt. The same rule applies to case patterns and to the =~ regex operand.
solid answer
~50 sInside `[[ ]]`, the right-hand side of `==` and `!=` is a **pattern**, not a string — so `[[ $file == *.txt ]]` is a glob match and succeeds for `report.txt`. Quoting any part of that side removes the special meaning from the quoted characters, so `[[ $file == "*.txt" ]]` asks whether the value is literally `*.txt` and fails for `report.txt`. The same rule governs `case`: the labels are patterns, and `"*.txt")` matches only the literal text. It also governs the regex operator: in `[[ $s =~ "a+b" ]]` the quoted part is matched literally rather than as a regular expression, which is why regexes are normally put in a variable and used unquoted. Note the asymmetry — the left-hand side is not a pattern and is never word-split or globbed inside `[[ ]]`, so quoting it changes nothing.
code
bash · 7 linesfile=report.txt
[[ $file == *.txt ]] && echo 'glob: match'
[[ $file == "*.txt" ]] && echo 'quoted: match' || echo 'quoted: no match'
suffix='.txt'
[[ $file == *"$suffix" ]] && echo 'wildcard plus literal suffix: match'go deeper
Know that the right side of == inside [[ ]] is a wildcard pattern, and that putting quotes around *.txt makes it match only that exact text rather than any .txt file.
Explain that quoting is a per-character switch between pattern and literal, demonstrate the *"$suffix" idiom, and note that case labels follow the same rule.
Point to the silent failure this causes in review — a quoted pattern in a branch guard that never fires and disables a step with no error — and use deliberate quoting to say whether user input is data or pattern.
Treat it as a readability hazard worth a convention: pin regexes in named variables, keep pattern-matching guards obvious, and prefer an explicit case over clever quoted comparisons in scripts other teams maintain.
## The right-hand side is a pattern `[[ ]]` is a shell keyword, not a command, so bash parses its contents specially. For the `==` and `!=` operators, the word on the right is treated as a **pattern** — the same pattern language used for filename globbing, though here it is matched against a string rather than against the filesystem. `*` matches any sequence, `?` matches one character, `[abc]` matches a character class. ```bash file=report.txt [[ $file == *.txt ]] # true - glob match [[ $file == "*.txt" ]] # false - literal match against the text *.txt [[ $file == '*.txt' ]] # false - same, single quotes are equally literal ``` Quoting is the switch between those two modes, and the switch is per-character: quoting *part* of the pattern makes only that part literal. ```bash suffix='.txt' [[ $file == *"$suffix" ]] # true - * is a wildcard, the suffix is literal text [[ $file == "*$suffix" ]] # false - the * is literal now too ``` That partial form is the idiomatic one when the pattern is built from a variable. If the variable might itself contain `*` or `?` and you want those treated as data, quote it: `[[ $name == "$user_input" ]]` is an exact-equality test, whereas `[[ $name == $user_input ]]` lets the user's value act as a pattern — a real and easily missed behaviour change. ## The left-hand side is different Inside `[[ ]]` no word splitting and no pathname expansion is performed on expansions, so the left-hand side does not need quotes to be safe: ```bash f='My Report.txt' [[ $f == 'My Report.txt' ]] && echo match # works, unquoted, despite the space ``` This is one of the few places in bash where an unquoted expansion is genuinely safe. Many teams still quote it for consistency, which is harmless — quoting the left side never changes the meaning. ## case works the same way `case` labels are patterns from the same language, and quoting them has the same effect: ```bash case $file in "*.txt") echo 'the file is literally named *.txt' ;; *.txt) echo 'any .txt file' ;; esac ``` The word after `case` is likewise not word-split or globbed, so `case $file in` is safe unquoted for the same reason the left side of `==` is. ## The regex operator `=~` matches an extended regular expression, and since bash 3.2 quoting a part of the right-hand side forces that part to be matched literally: ```bash s='aab' [[ $s =~ a+b ]] # true - a+ is a regex quantifier [[ $s =~ "a+b" ]] # false - looks for the literal three characters a+b ``` This surprises people who quote out of habit and then wonder why their regex stopped working. The usual convention is to keep the regex in a variable and use it unquoted, which also sidesteps the parsing pitfalls of writing a regex inline: ```bash re='^[0-9]{3}-[0-9]{4}$' [[ $phone =~ $re ]] && echo 'valid' ``` Conversely, when you want a user-supplied string matched *literally*, quote it deliberately so their metacharacters cannot act as pattern syntax. ## Why this matters in review The failure mode is quiet. A guard like `[[ $branch == "release/*" ]]` never fires and the release-only step is silently skipped in every run — no error, no exit status anyone checks, just a branch of the script that is dead. Reading it, the quotes look like good hygiene, which is exactly what makes the bug durable. When you see a `[[ ]]` comparison, ask whether the right-hand side is meant as data or as a pattern, and make the quoting say so. ## Rule of thumb Quote the right-hand side when you mean an exact string; leave the wildcard characters unquoted and quote only the interpolated data when you mean a pattern. `case` labels follow the identical rule, and `=~` operands invert your instincts, so treat them deliberately rather than by habit.
- How would you match a filename against a suffix held in a variable?Quote only the variable and leave the wildcard bare: `[[ $file == *"$suffix" ]]`. The `*` keeps its pattern meaning while the variable's contents are treated as literal text, so a suffix containing a `.` or a `?` cannot accidentally act as pattern syntax.
- Does the left-hand side of `==` inside `[[ ]]` need quoting?Not for correctness — `[[ ]]` performs no word splitting or pathname expansion on expansions, so `[[ $f == ... ]]` is safe even when the value contains spaces or a `*`. Many style guides still quote it so the habit stays unconditional and does not have to be re-derived when the code moves somewhere less forgiving.
- Why do people put a regex in a variable before using it with `=~`?Two reasons. Quoting the regex inline would make it match literally, so it must be unquoted, and an unquoted inline regex is awkward to write because spaces and other shell metacharacters must be escaped. Assigning `re='^[0-9]+$'` in single quotes and testing `[[ $s =~ $re ]]` keeps the regex readable and shell-safe.
saying these in an interview costs you the question
- Says the right-hand side of == is an ordinary string
- Adds quotes around a glob and expects matching to be unchanged
- Thinks quoting a regex for =~ is harmless hygiene
- Believes case labels are compared as literal text
- Assumes the left-hand side must be quoted to survive spaces