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 (the globasciiranges option is on by default), so [a-z]*.txt does not match Report3.txt.
  • a class: [[:digit:]], [[:upper:]], [[:lower:]], [[:alnum:]]. For example echo [[:upper:]]* printed Report3.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 run shopt -s dotglob to make * include them.
  • A slash. A wildcard works within one directory level: echo */*.png printed only images/logo.png, not images/icons/menu.png. To match at any depth, turn on globstar and 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 * .tmp means "remove everything, then a file called .tmp". Test with echo or ls first, then press the Up arrow and change the command to rm.
  • Too many matches. In a directory with 50,000 files, rm *.log failed with /usr/bin/rm: Argument list too long, because the expanded list was larger than the argument limit of about 2 MB (getconf ARG_MAX printed 2097152). Let find do the matching instead: find . -maxdepth 1 -name '*.log' -delete.
  • Case. Patterns are case-sensitive: report3* does not match Report3.txt unless you run shopt -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

Was this answer helpful? 0 Users Found This Useful (0 Votes)