A wildcard in a Bash command usually becomes a list of pathnames before the target program starts. That makes *.txt convenient, but it also makes the command depend on the current directory and shell options. The program receives arguments, without information saying which ones came from a wildcard. Decide whether you intend to pass matching files or a pattern for the program to interpret.
Match first, then preserve the arguments
Bash performs word splitting before filename expansion. An unquoted *, ?, or bracket expression can therefore produce several arguments. A matching filename containing spaces remains one argument: Bash does not run another word-splitting pass over the filenames it just found. The common loss of spaces happens later when a loop expands its variable without quotes. Use "$file" when passing each matched pathname onward.
Quote a pattern when its recipient needs the pattern itself. For example, find . -name '*.txt' gives the matching expression to find; leaving it unquoted lets Bash expand it against the current directory first. A quoted variable containing an asterisk also stays literal. An unquoted variable containing an asterisk can trigger pathname expansion, even though the star was not written directly in the command.
A pattern operates on directory entries, not a promised collection of regular files. A directory named archive.txt also matches *.txt. Use a file-type check when that distinction matters. Ordinary stars do not cross slashes, and leading dots require explicit matching unless dotglob is enabled. Recursive traversal needs its own deliberate approach; plain * does not search the whole tree.
The environment can also change which names qualify. In Bash, setting GLOBIGNORE to a nonempty value filters matching names and enables dotglob as a side effect. A script inherited from an interactive session may therefore see hidden files unexpectedly. The fixture below clears that variable and selects its options explicitly so its assertions describe a reproducible configuration.
When it breaks
By default, an unmatched pattern stays as literal text. A loop over *.png can consequently run once with a nonexistent filename. With nullglob enabled, the pattern disappears instead. This avoids that literal but introduces another risk: a command receiving no file arguments may read standard input, or apply its own default target. Neither nounset nor pipefail checks that a glob found files.
For optional inputs, collect matches in a Bash array with nullglob and test its element count before invoking the consumer. For required matches, failglob rejects an unmatched expansion before the command runs. Keep option changes inside a subshell when callers should retain their original settings. Do not assume a script’s behavior matches an interactive shell whose startup files changed those options.
A filename beginning with a hyphen may be interpreted as an option by the receiving program. Expanding ./*.txt prefixes such names with a directory component; use the program’s documented -- separator where supported as well. Quoting preserves argument boundaries but does not change option parsing. Finally, enumeration and use are separate events: files can disappear between them, so a successful match is not proof that a later read will succeed.
Check it quickly
Run the following block with Bash and the usual temporary-directory tools. It creates an isolated directory, checks literal and expanded arguments, exercises both no-match policies, and removes only its own fixture. It does not require any preexisting text files.
bash --noprofile --norc <<'BASH'
set -e
export LC_ALL=C
set +f
unset GLOBIGNORE
shopt -u nullglob failglob dotglob
work=$(mktemp -d)
trap 'rm -rf -- "$work"' EXIT
cd "$work"
touch -- a.txt 'two words.txt' .hidden.txt -option.txt
files=(./*.txt)
[[ ${#files[@]} -eq 3 ]]
[[ ${files[2]} == './two words.txt' ]]
printf 'match <%s>\n' "${files[@]}"
literal=('*.txt')
[[ ${literal[0]} == '*.txt' ]]
missing=(./*.missing)
[[ ${missing[0]} == './*.missing' ]]
shopt -s nullglob
missing=(./*.missing)
[[ ${#missing[@]} -eq 0 ]]
if (shopt -s failglob; printf '%s\n' ./*.missing) 2>/dev/null; then
printf 'unexpected match\n' >&2
exit 1
fi
shopt -s dotglob
files=(./*.txt)
[[ ${#files[@]} -eq 4 ]]
printf 'PASS: spaces, literal, no-match policies, hidden file\n'
BASH
Expect three initial match lines, with the spaced filename on one line, followed by the PASS message. The hidden file appears only in the final array after dotglob is enabled. The failing subshell is intentional and must not print an expanded filename. These assertions verify Bash expansion and argument preservation; they do not establish how an arbitrary downstream tool handles missing operands, directories, or options. Check that tool’s contract before replacing the harmless printf with a real operation.
Sources
- GNU Bash: Filename Expansion, matching rules and no-match options, checked 2026-09-06.
- GNU Bash: Shell Expansions, ordering and changes to argument count, checked 2026-09-06.
- GNU Bash: Arrays, collecting matches and preserving elements, checked 2026-09-06.