Globbing is the shell’s pattern matching for file names. When you type rm *.log, it isn’t rm that understands *: the shell expands the pattern into a list of matching files, then runs the command. Understanding this makes you faster, and helps avoid dangerous mistakes.

The basic wildcards

Pattern Matches Example
* Any number of characters (including none) *.log → app.log, error.log
? Exactly one character file?.txt → file1.txt, fileA.txt
[abc] One character from the set log[123].txt
[a-z], [0-9] One character in a range backup-[0-9][0-9].tgz
[!abc] or [^abc] One character not in the set [!.]* → names not starting with a dot
ls *.conf
ls access.log.?             # access.log.1 … access.log.9
ls backup-2026-0[1-6]-*.tgz # first half of 2026

Globs don’t match hidden files (starting with .) unless the pattern starts with a dot. See Linux hidden files.

Braces generate strings, whether or not files exist:

mkdir -p project/{src,tests,docs}
cp nginx.conf{,.bak}               # cp nginx.conf nginx.conf.bak
echo file{1..5}.txt                # file1.txt … file5.txt
ls /var/log/{syslog,auth.log}

Character classes

ls *[[:digit:]]*        # names containing a digit
ls [[:upper:]]*         # names starting with an uppercase letter

Other classes: [:alpha:], [:alnum:], [:space:], [:lower:].

Recursive globs and options (Bash)

shopt -s globstar     # enable **
ls **/*.php           # all .php files in all subdirectories
shopt -s dotglob      # * also matches hidden files
shopt -s nullglob     # a pattern with no matches expands to nothing (instead of itself)
shopt -s extglob      # extended patterns
ls !(*.gz)            # everything except .gz files (extglob)

For large trees, find is often clearer and more powerful. See Linux find command.

Quoting: when you don’t want expansion

echo *.txt       # expanded by the shell
echo "*.txt"     # literal *.txt
find /var/log -name "*.gz"     # quote it, so find (not the shell) does the matching
grep "error.*timeout" app.log  # regex for grep, quoted so the shell leaves it alone

Unquoted patterns in find -name are a classic bug: if a matching file exists in the current directory, the shell expands it first.

Wildcards (globs) are not regular expressions: in a glob * means “anything”, while in a regex .* does. See regex basics.

Safety with rm, mv and cp

  • Preview first: replace the command with echo or ls:
echo rm /var/www/site/cache/*
ls -d /tmp/build-*
  • Watch for stray spaces: rm -rf /var/www/site/ * deletes the current directory’s contents too.
  • Use -- before patterns that might start with a dash: rm -- *.tmp.
  • With variables, quote and guard them: rm -rf "${DIR:?}/"* fails if DIR is empty, instead of deleting /*.
  • “Argument list too long” means the glob matched too many files. Use find … -delete or xargs.
find /var/www/site/cache -type f -name '*.tmp' -delete

Globs in scripts

In scripts, an unmatched glob stays as the literal text (*.log), so a loop over for f in /var/log/app/*.log runs once with a non-existent file name when the directory is empty. Either enable nullglob, or check that each file exists inside the loop ([ -e "$f" ] || continue). Always quote "$f" when using the variable, so file names with spaces work.

Quick reference

Pattern Meaning
* anything
? one character
[0-9] one digit
[!x] not x
{a,b} a or b (brace expansion)
** recursive (shopt -s globstar)
"*.log" literal (quoted)

Related: Bash scripting tutorial and pipes and redirection.

  • Linux
  • Bash
  • Command Line