Globbing and Regex
Two pattern-matching systems that look similar but aren't - shell globs expand filenames before a command ever runs, while regular expressions are matched by individual tools like grep, sed, and awk.
Mixing these up is one of the most common sources of "why isn't my pattern
matching" confusion: a glob and a regex can use the same character (*)
to mean two completely different things.
Globbing: expanded by the shell, before the command runs
When you type ls *.txt, the shell - not ls - expands *.txt into a
list of matching filenames before ls ever sees them. Run echo *.txt
in a directory with matching files to see this directly: echo never gets
the string *.txt, it gets the already-expanded list.
| Pattern | Matches |
|---|---|
* |
Any characters (not including a leading dot, by default) |
? |
Exactly one character |
[abc] |
One character: a, b, or c |
[a-z] |
One character in the range a to z |
[^abc] |
One character that is not a, b, or c |
{jpg,png} |
Brace expansion (Bash extension, not strictly a glob): one of the listed alternatives |
ls *.log # every file ending in .log
ls report-??.csv # report-01.csv, report-42.csv, not report-1.csv
ls image.{jpg,png} # image.jpg and image.png, if either exists
If a glob matches nothing, Bash's default behavior is to pass the literal
pattern string through unchanged - a frequent source of confusing "No such
file" errors when a command receives *.txt as a literal argument because
nothing matched. shopt -s nullglob changes that behavior to expand to
nothing instead, which is often what scripts actually want - see
Shell Scripting.
Regex: matched by the tool, against text content
A regular expression is evaluated by whatever program you pass it to
(grep, sed, awk, find -regex) against the content of lines or
strings - not filenames, and not expanded by the shell first.
| Pattern | Matches |
|---|---|
. |
Any single character |
* |
Zero or more of the previous character |
+ |
One or more (extended regex only - see below) |
^ / $ |
Start / end of line |
[abc] |
One character: a, b, or c (same idea as globs, different context) |
| |
Alternation - "either side" (extended regex only) |
grep "^root:" /etc/passwd
root:x:0:0:root:/root:/bin/bash
Note that . in a regex means "any character," while in a glob it has no
special meaning at all - *.txt as a glob matches files ending in
.txt literally, but .*\.txt as a regex means "any characters, then a
literal dot, then txt."
Basic vs. extended regex
grep defaults to basic regular expressions (BRE), where +, ?,
and | are literal characters unless escaped (\+, \?, \|).
Extended regular expressions (ERE), used by grep -E (or egrep,
deprecated in favor of -E) and awk, treat them as metacharacters
without escaping:
grep 'colou\?r' file.txt # BRE: matches "color" or "colour"
grep -E 'colou?r' file.txt # ERE: same result, no backslash needed
sed follows BRE by default too, with a -E (GNU) or -r flag for ERE
depending on version - check man sed on the system you're using, since
this is one of the more common cross-distro inconsistencies.
Quoting matters for both
Always quote glob and regex patterns you don't want the shell to expand itself:
grep "[0-9]+" file.txt # safe: the shell leaves [0-9]+ untouched
grep [0-9]+ file.txt # risky: if a matching filename exists, the shell expands it first