The asterisk in Bash and shell globs
In Bash, Zsh and other shells, an unquoted * matches any run of characters in a file name, so ls *.txt lists every .txt file in the folder. The shell swaps in the matching names before the command runs. It skips hidden files, and quoting it, as in '*', turns it back into a plain asterisk.
What * matches
| Pattern | Matches |
|---|---|
* |
Every file and folder in the current folder, except hidden ones |
*.txt |
Names ending in .txt |
report-* |
Names starting with report- |
*2026* |
Names containing 2026 |
*/ |
Folders only |
*.{jpg,png} |
Names ending in .jpg or .png (brace expansion plus a glob) |
**/*.txt |
.txt files in this folder and every subfolder: on by default in Zsh, needs shopt -s globstar in Bash 4 and later |
A * never matches a /, so *.txt doesn't look inside subfolders. That's what ** is for.
The shell expands it, not the command
The command never sees the *. By the time ls, rm or cp starts, the shell has replaced the pattern with a list of names. You can watch it happen with echo:
mkdir demo && cd demo
touch a.txt b.txt notes.md .env
echo * # a.txt b.txt notes.md
echo *.txt # a.txt b.txt
This explains a classic bug with find, which wants the pattern itself:
find . -name *.txt # wrong: the shell turns it into find . -name a.txt b.txt
find . -name '*.txt' # right: find receives the pattern and searches every folder
With two matching files the first command fails with a confusing error. With exactly one, it quietly searches for that one name, which is worse.
When nothing matches
Shells disagree here, and it matters for scripts:
| Shell | ls *.log with no .log files |
|---|---|
| Bash (default) | Passes the text *.log to ls unchanged, so ls complains that *.log doesn't exist |
Bash with shopt -s nullglob |
The pattern disappears, and ls lists the whole folder |
Bash with shopt -s failglob |
The shell refuses to run the command |
| Zsh (default, and the macOS default shell) | zsh: no matches found: *.log, and the command doesn't run |
The Zsh behavior surprises people who paste commands like scp server:*.log . or pip install requests[security] from Bash-based tutorials. Quoting the argument fixes both.
Hidden files
A leading dot hides a file from *, so rm * leaves .env and .gitignore alone. To include them, use shopt -s dotglob in Bash, setopt globdots in Zsh, or the one-off Zsh qualifier *(D). Be careful with .*: Zsh and Bash 5.2 and later skip . and .., but older Bash, including the 3.2 that ships with macOS, matches both, so .* reaches into the parent folder.
Quoting a literal asterisk
Single quotes, double quotes and a backslash all stop expansion: '*', "*" and \* each pass one asterisk to the command. The Linux typing guide has examples with echo and grep.
The quoting rule that catches people is about variables. An unquoted variable is expanded and then globbed:
msg='*'
echo $msg # a.txt b.txt notes.md
echo "$msg" # *
Quoting "$var" every time avoids this, and it's what tools like ShellCheck will tell you to do.
$* versus $@ in scripts
Inside a script or function, $* and $@ both mean "all the arguments", and they differ only when quoted:
show() {
printf '[%s]\n' "$*"
printf '<%s>\n' "$@"
}
show "a b" c
# [a b c]
# <a b>
# <c>
"$*" joins everything into one string. "$@" keeps each argument separate, spaces and all, which is almost always what you want when passing arguments on.
rm * and file names that look like options
If a folder contains a file named -rf, then rm * expands to rm -rf a.txt b.txt ..., and rm reads the first name as options. Write rm -- * or rm ./* so every name is treated as a file. It's the asterisk's most consequential job, so it's worth the two extra characters.
Globs versus regex
A glob * is roughly the regex .*, and a glob ? is the regex .. They aren't interchangeable. In grep '*.txt' files.txt, the pattern is a regex, so the * has nothing to repeat and the dot matches any character. See the asterisk in regex.