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.