Why this matters
Real directories contain names like Quarterly Report (final).xlsx, it's done.txt and downloaded files beginning with a dash. Every one of them breaks a careless command, and in a script the breakage can be destructive. On the other side, wildcards let you act on hundreds of files with one short pattern. Both topics come down to the same thing: understanding what bash does to your line before any program sees it.
Which characters are awkward, and why
A Unix file name may contain any byte except / (the separator) and the null byte. Spaces, quotes, newlines, stars, dashes: all legal. The kernel does not care. The trouble is the shell: many characters mean something to bash.
| Character(s) | Meaning to bash |
|---|---|
| space, tab, newline | separate words |
' " \ | quoting |
* ? [ ] | wildcards (glob patterns) |
$ ` | expansions (variables, commands) | |
; & | < > ( ) | command structure, redirection |
# (at the start of a word) | comment: rest of the line ignored |
~ (at the start of a word) | home directory |
- (at the start of an argument) | not special to bash, but programs read it as an option |
Open the shell for this lesson and look at the odd directory:
$ ls odd
-rf 'it'\''s here.txt' 'my report.txt' normal.txt 'photo [1].jpg'
ls itself quotes names that need it, showing you a form you could paste back into the shell.
Three ways to quote
Single quotes make everything inside literal. Nothing is special except the closing ':
$ cat 'my report.txt'
report
$ echo 'Price: $5 * 2; done'
Price: $5 * 2; done
Double quotes also keep spaces and wildcards literal, but still allow $ expansions (you will need that for variables). Use them for a name that contains a single quote:
$ cat "it's here.txt"
apostrophe
Backslash makes the next one character literal. Tab completion uses this style:
$ cat my\ report.txt
report
$ cat it\'s\ here.txt
apostrophe
You can mix styles within one word; bash glues adjacent pieces together: 'it'"'"'s here.txt' is the word it's here.txt (single-quoted it, double-quoted ', single-quoted s here.txt). Ugly, but sometimes needed. Module 7 treats quoting in depth; for now the rule is simple: if a name has anything other than letters, digits, ., _ and -, quote it.
Names that start with a dash
$ rm -rf
$ ls
-rf ...
Nothing happened: rm read -rf as options and, having no file operands, did nothing (-f also suppresses the complaint). Quoting does not help, because quotes are removed before rm runs and rm still sees -rf. Two fixes:
$ rm -- -rf # "--" means: end of options, everything after is a name
$ rm ./-rf # a path that does not start with "-"
The ./ trick works with every program, even ones that do not understand --.
Wildcards (globbing)
A word containing *, ? or [ is a pattern. Before running the command, bash replaces it with the list of matching file names, sorted. This is called globbing (after glob, a program early Unix used for it), and the patterns are globs.
| Pattern | Matches |
|---|---|
* | any string, including the empty string |
? | exactly one character |
[abc] | one character: a, b or c |
[a-z] | one character in the range a to z |
[!0-9] or [^0-9] | one character that is not a digit |
$ cd camera
$ echo img00?.jpg
img001.jpg img002.jpg img003.jpg img004.jpg img005.jpg img006.jpg img007.jpg img008.jpg img009.jpg
$ echo *.tar
backup-2025-11.tar backup-2025-12.tar backup-2026-01.tar backup-2026-02.tar
$ echo backup-2025-*
backup-2025-11.tar backup-2025-12.tar
$ echo [bn]*
backup-2025-11.tar backup-2025-12.tar backup-2026-01.tar backup-2026-02.tar notes.txt
echo is the perfect way to preview a pattern: it shows exactly the arguments the real command would get. Make it a reflex before rm or mv.
What happens underneath
you type: mv img*.jpg photos/
bash reads the current directory, matches each name against img*.jpg
bash runs: mv img001.jpg img002.jpg ... img014.jpg photos/
└─ execve("/usr/bin/mv", ["mv", "img001.jpg", ..., "photos/"], env)
The program receives a plain list of names. Consequences:
- Every program "supports wildcards" for free, and none of them sees the pattern.
- Names produced by globbing are never re-split, even if they contain spaces.
mv *.txt dir/handlesmy report.txtcorrectly. (This is the safe behavior; it will contrast sharply with unquoted variables in module 7.) - The pattern is matched against what exists now.
rm *.login the wrong directory deletes the wrong files. - Patterns are about names, not content, and do not cross
/:*does not matchsub/file.
Rules worth knowing now
- Hidden files are excluded.
*does not match names starting with.; write.*for those. - No match leaves the pattern as is (the second quiz).
ls *.xyzthen complains about a file literally named*.xyz. - Quoting turns globbing off.
ls '*.txt'asks for a file literally named*.txt. Use that when you mean the literal characters; for examplefindpatterns (module 14) must be quoted so that bash leaves them for find. - Sorting follows the locale. In this sandbox (
C.UTF-8) it is character by character, sodata10.csvsorts beforedata2.csv, and uppercase before lowercase. Desktop systems with other locales may sort differently. Pad numbers with zeros (img001) when order matters.
Brace expansion is not globbing
{a,b,c} looks similar but works differently: bash generates the words whether or not files exist.
$ echo report.{txt,pdf,md}
report.txt report.pdf report.md
$ mkdir -p project/{src,tests,docs}
$ echo img{1..3}.jpg
img1.jpg img2.jpg img3.jpg
Brace expansion is text generation; globbing is matching existing names. cp config{,.bak} is a popular shortcut for cp config config.bak.
Common mistakes
- Unquoted spaces.
rm my report.txttries to deletemyandreport.txt, and if a file namedreport.txtexists, it is gone. - Quoting a pattern you wanted expanded.
rm "*.tmp"looks for a file named*.tmp. - Expecting quotes to protect a leading dash. Use
--or./. - A pattern matching more than you think.
rm *in the wrong directory;rm * .bak(stray space) removes everything. Preview withecho. - Assuming
*includes hidden files. It does not.
Recap
- File names can contain anything except
/and NUL; trouble comes from characters bash interprets. - Single quotes: everything literal. Double quotes: literal except
$. Backslash: the next character. - A leading dash is handled by
--or a./prefix, not by quoting. *,?,[...]are expanded by bash into sorted matching names before the program runs; non-matching patterns stay as they are; hidden files are skipped.- Preview patterns with
echo. Braces{a,b}generate words regardless of what exists.
Going further: run man 7 glob for the full pattern rules. Then try touch $'line\nbreak.txt' (a name with a newline in it) and see how ls shows it. Names like that are why later modules teach null-separated file lists.