Updated: 8 October 2026 · Applies to: bash 5.2 and 5.3 on Debian 13, Ubuntu 24.04 and 26.04 and AlmaLinux 10; bash 5.1 on Rocky Linux 9 and AlmaLinux 9
File name pattern matching (globbing) is done by the shell, not by the command. Before ls, cp or rm runs, bash replaces a pattern with the list of matching names. * matches any number of characters, ? exactly one character, and [...] one character from a set:
ls *.log # every name ending in .log
ls report?.txt # report1.txt, reportA.txt (one character)
ls report[0-9].txt # one digit only
ls report[!0-9].txt # one character that is not a digit
cp site.conf{,.bak} # brace expansion: site.conf site.conf.bak
The examples below were run in a directory that contains access.log, error.log, app.log.1, app.log.2.gz, report1.txt, report2.txt, report10.txt, reportA.txt, Report3.txt, site.conf, site.conf.bak, notes.md, two CSV files, two hidden files (.env, .htaccess) and an images folder. echo shows what a pattern expands to without running anything, so use it to test a pattern first.
The three wildcards: *, ? and [ ]
$ echo *.log access.log error.log $ echo *.log* access.log app.log.1 app.log.2.gz error.log $ echo report?.txt report1.txt report2.txt reportA.txt $ echo report[0-9].txt report1.txt report2.txt $ echo report[!0-9].txt reportA.txt
report10.txt is not matched by report?.txt, because ? is always exactly one character. Inside brackets you can use:
- a list or a range:
[12],[0-9],[a-z]. In bash 5 ranges follow plain ASCII order (theglobasciirangesoption is on by default), so[a-z]*.txtdoes not matchReport3.txt. - a class:
[[:digit:]],[[:upper:]],[[:lower:]],[[:alnum:]]. For exampleecho [[:upper:]]*printedReport3.txt. - negation:
[!0-9]or[^0-9], any character except those listed.
What * does not match
- Hidden files.
*skips names that start with a dot. Use.*for those, or runshopt -s dotglobto make*include them. - A slash. A wildcard works within one directory level:
echo */*.pngprinted onlyimages/logo.png, notimages/icons/menu.png. To match at any depth, turn onglobstarand use**:
$ shopt -s globstar $ echo **/*.png images/icons/menu.png images/logo.png
Watch out with .* on bash 5.1 (Rocky Linux 9 and AlmaLinux 9). There echo .* printed . .. .env .htaccess, so a command such as chmod -R g+w .* is also given .., the parent directory. Bash 5.2 and later (Debian 13, Ubuntu 24.04 and 26.04, AlmaLinux 10) skip . and .. (the globskipdots option) and printed only .env .htaccess.
When nothing matches
By default bash passes the pattern on unchanged, so the command receives the literal text:
$ ls *.xyz ls: cannot access '*.xyz': No such file or directory
Two shell options change this. With shopt -s failglob bash refuses to run the command and prints no match: *.xyz. With shopt -s nullglob the pattern disappears, which is useful in scripts (for f in *.log then does nothing) but surprising on the command line: ls *.xyz becomes plain ls and lists every file.
Brace expansion: {a,b} and {1..5}
Braces look like a wildcard but are not one. Bash creates every combination whether or not the files exist:
$ echo {missing,access}.log
missing.log access.log
$ echo report{1..3}.txt
report1.txt report2.txt report3.txt
$ echo {01..05}
01 02 03 04 05
$ echo site.conf{,.bak}
site.conf site.conf.bak
That makes braces ideal for creating things (mkdir -p site/{css,js,img}) and for a quick backup copy (cp site.conf{,.bak}), but ls {missing,access}.log reports an error for the name that does not exist.
Extended patterns (extglob)
With the extglob option, bash understands ?(p) zero or one, *(p) zero or more, +(p) one or more, @(p) exactly one and !(p) anything except p, where p can list alternatives separated by |:
$ echo *.@(log|conf) access.log error.log site.conf $ echo report+([0-9]).txt report1.txt report10.txt report2.txt $ echo !(*.log|*.txt) app.log.1 app.log.2.gz data-2026-10-01.csv data-2026-10-02.csv images notes.md site.conf site.conf.bak
In an interactive shell on Debian and Ubuntu, extglob is already on because the bash-completion package turns it on. In scripts it is off: add shopt -s extglob on its own line near the top, before the first line that uses such a pattern.
Quote a pattern that the command should see
Some programs do their own matching: find -name, grep --include, rsync and scp for remote paths. Put the pattern in quotes, or the shell expands it first. Unquoted, find received two file names and failed:
$ find . -name *.log find: paths must precede expression: `error.log' find: possible unquoted pattern after predicate `-name'? $ find . -name "*.log" ./error.log ./access.log
Single quotes, double quotes and a backslash (\*.log) all stop the expansion. The unquoted form is worse when the current directory contains exactly one match: in a test folder with access.log, logs/access.log and logs/error.log, find . -name *.log ran without an error but only searched for access.log, and silently missed logs/error.log. More on find in looking for files.
Globs are not regular expressions
Wildcards and regular expressions use some of the same symbols with different meanings:
- A glob
*is any text; the regex equivalent is.*. In a regex,*means "repeat the previous character". - A glob
?is one character; in a regex that is., and.in a glob is just a dot. - A glob must match the whole name; a regex matches anywhere in the line unless you anchor it with
^and$.
Globs are used by the shell, find -name, case and [[ $name == *.log ]]. Regular expressions are used by grep, sed, awk and find -regex. Giving grep a glob finds nothing:
$ ls | grep '*.log' $ ls | grep '\.log$' access.log error.log $ find . -regextype posix-extended -regex '.*/report[0-9]+\.txt' ./report1.txt ./report2.txt ./report10.txt
Regular expressions are covered in grep, sort, uniq, wc, cut and tr and grep character classes and bracket expressions.
Common mistakes
- A space inside the pattern.
rm * .tmpmeans "remove everything, then a file called .tmp". Test withechoorlsfirst, then press the Up arrow and change the command torm. - Too many matches. In a directory with 50,000 files,
rm *.logfailed with/usr/bin/rm: Argument list too long, because the expanded list was larger than the argument limit of about 2 MB (getconf ARG_MAXprinted 2097152). Letfinddo the matching instead:find . -maxdepth 1 -name '*.log' -delete. - Case. Patterns are case-sensitive:
report3*does not matchReport3.txtunless you runshopt -s nocaseglob. ls -l *shows the contents of subdirectories. See list files with ls.
Globbing belongs to bash, so the Rust coreutils on Ubuntu 26.04 make no difference: bash 5.3.9 there gave exactly the same results as bash 5.2 on Debian 13. For more worked examples, see practical examples using wildcards; to view the files you matched, see display file contents.
Ucartz services for this topic
- KVM VPS hosting: NVMe SSD and full root access, with Free Basic Managed Support.
- Unmanaged dedicated servers: full root access and free IPMI KVM, with Free Basic Managed Support.
- Linux server management: hardening, patching, monitoring and automation for Ubuntu, Debian, RHEL and AlmaLinux.
