A glob is not a regular expression
◈ 9 cardsThe same punctuation means different things to the shell and to a search tool — and only one of them ever touches your filenames.
Two languages that share an alphabet
A glob is a filename pattern, and the shell expands it, before the command runs, into a list of names that exist on disk. A regular expression is a text pattern, and a tool — grep, sed, awk, vi — applies it to lines of input. They borrow the same punctuation and they mean different things by it. That is the entire source of the confusion, and it is worth being blunt about the two differences that actually bite:
*in a glob means zero or more of any character.*in a regex means zero or more of the single item before it.- A glob must match the whole filename. A regex matches anywhere in the line unless you pin it with
^and$.
Worked example — the same six characters, twice
A directory holds six files:
lab1.c lab2.c lab10.c labA.c lab1.o notes.txt
Run the two commands side by side.
ls *.c
ls | grep '*.c'
The first lists the four C files. The shell noticed the unquoted *, expanded *.c against the directory, and handed ls four ready-made arguments — ls itself never saw a pattern at all.
The second lists nothing. grep was handed the pattern *.c, and in a regular expression a * repeats the item in front of it — here there is no item in front of it. GNU grep resolves that by treating the leading * as a literal asterisk character, so the search becomes: find lines containing an asterisk, then any one character, then a c. No filename in this directory contains an asterisk, so grep finds nothing and exits with status 1. (POSIX leaves a leading * undefined; some implementations report a syntax error instead of guessing. Either way you get no results.)
The regex you actually wanted is \.c$: a literal dot — escaped, because a bare . is the any-character operator — followed by the end of the line.
ls | grep '\.c$'
Translating a glob into the equivalent regex
Take the glob lab[0-9]??.c and convert it character by character.
lab— three literals. They carry over unchanged.[0-9]— one character from a range. Bracket sets mean the same thing in both languages, so this carries over unchanged too.??— two glob wildcards, each exactly one character. The regex spelling of exactly one character is., so this becomes....— an ordinary dot in a glob, with no special meaning. In a regex a bare dot is an operator, so it must be escaped:\..c— a literal.- And because the glob had to match the entire name, the regex needs anchors at both ends.
The result is ^lab[0-9]..\.c$. Every glob translates the same way: wildcards become their regex equivalents, ordinary punctuation gets escaped, and the whole thing gets anchored.
Two more, for the pattern of it.
- The glob
*.txtbecomes^.*\.txt$— a glob star becomes the regex.*, any character repeated any number of times, which is exactly what a glob star means. - The glob
lab[!0-9].cbecomes^lab[^0-9]\.c$— the negation moves from!to^, since inside a regex bracket set the leading^is the negation and!is just an exclamation mark.
Which one is running?
The practical question in front of any command line is: is the shell going to touch this, or is the tool? If the pattern is a bare argument naming files, the shell expands it and the command receives names. If the pattern is quoted and handed to a search tool, the shell leaves it alone and the tool interprets it. That is why find -name '*.mp4' is quoted — you want find to do the matching, in every directory it visits, not the shell to do it once in the directory you happen to be standing in.