In bash, what does brace expansion such as `{a,b}`, `{1..5}` or `cp config.yml{,.bak}` produce, and how does it differ from a filename glob like `*.txt`?
answer
- text generation, not file matching
- preamble, alternatives, postscript
- empty alternative gives the backup idiom
- never reads the directory
- runs before every other expansion
basics
~20 sBrace expansion generates literal text: bash copies the text around the braces once per alternative, so {a,b} yields two words and {1..5} yields five. It never looks at the filesystem, unlike a glob, so it produces words even for files that do not exist.
solid answer
~40 sBrace expansion is pure text generation. Bash takes the preamble before the brace and the postscript after it and emits one word per alternative inside: `{a,b}.txt` becomes `a.txt b.txt`, `{1..5}` becomes `1 2 3 4 5`, and the empty alternative in `cp config.yml{,.bak}` becomes `cp config.yml config.yml.bak`. Braces also combine, so `{a,b}{1,2}` gives four words. The key difference from a glob such as `*.txt` is that brace expansion never consults the filesystem — it happens before pathname expansion and produces its words whether or not the resulting files exist, which is exactly why it is useful for `mkdir -p src/{main,test}` and for renaming a file to a backup name that does not exist yet. A brace with no comma and no valid range, like `{a}`, is left alone as literal text.
code
bash · 13 linesecho {a,b}.txt
# a.txt b.txt (printed even in an empty directory)
echo {a,b}{1,2}
# a1 a2 b1 b2
mkdir -p project/{src,test}/{main,util}
cp config.yml{,.bak}
# runs: cp config.yml config.yml.bak
echo {a}
# {a} -- no comma and no range, so left literalgo deeper
Be able to read cp file{,.bak} out loud as cp file file.bak, and to say that braces generate text while * matches files that already exist.
Explain the preamble/alternatives/postscript structure, the cartesian product of adjacent braces, and the silent pass-through when a brace list has no comma or valid range.
Show judgment about portability: the increment and zero-padding forms are bash 4.0+, so a script that must run on macOS bash 3.2 cannot rely on them, and the failure is silent rather than loud.
Frame it as a readability and reviewability call for the team: braces are excellent for creating things that do not exist yet, but a brace list standing in for a directory listing hides intent, and silent pass-through means a typo ships as data.
## What brace expansion is Brace expansion is the first thing bash does to a command line, and it is the only expansion that is purely about generating text. It takes a *preamble*, a braced list of alternatives, and a *postscript*, and emits one word for each alternative with the preamble and postscript pasted on: ```bash echo pre{a,b,c}post # preapost prebpost precpost ``` The alternatives are separated by unquoted commas. A word is produced for each one, in order, left to right. ## The two forms **Comma lists** — `{a,b,c}` — are arbitrary strings, including the empty string. The empty alternative is what makes the classic backup idiom work: ```bash cp config.yml{,.bak} # cp config.yml config.yml.bak mv report.log{,.1} # mv report.log report.log.1 ``` Read it by expanding it in your head: the first alternative is empty, so the first word is the bare `config.yml`; the second alternative appends `.bak`. **Sequence expressions** — `{1..5}`, `{a..e}` — expand to an inclusive range of integers or single characters. `{1..5}` gives `1 2 3 4 5`; `{5..1}` counts down. Bash 4.0 added an optional increment, `{0..20..5}`, and zero-padding, `{01..10}` giving `01 02 … 10`. Bash 3.2, still the version shipped with macOS, supports the plain integer and character ranges but not the increment or the padding. Braces nest and concatenate. Adjacent braces produce a cartesian product, and a brace inside a brace expands as part of its enclosing alternative: ```bash echo {a,b}{1,2} # a1 a2 b1 b2 mkdir -p src/{main,test}/{java,resources} ``` ## Why it is not a glob A glob such as `*.txt` is *pathname expansion*: bash reads the directory and replaces the pattern with the names that actually match. Brace expansion never touches the filesystem. `echo {a,b}.txt` prints `a.txt b.txt` in an empty directory just as happily as in a full one. That is not a limitation, it is the point — you use braces precisely when the target does not exist yet: ```bash mkdir -p /srv/app/{bin,etc,log} # none of these exist yet cp app.conf{,.orig} # app.conf.orig does not exist yet ``` The two also differ in *when* they run. Brace expansion is the very first expansion bash performs; pathname expansion is nearly the last. So braces can generate patterns that are then globbed — `ls {src,test}/*.py` first becomes `ls src/*.py test/*.py`, and each of those is matched against the filesystem afterwards. ## When braces do nothing Bash leaves the braces alone, as literal characters, when the construct is not a valid expansion: ```bash echo {a} # {a} — no comma, no range echo {1..n} # {1..n} — n is not an integer literal echo '{a,b}' # {a,b} — quoted, so not expanded ``` This silent pass-through is the source of most surprises. Nothing errors; you simply get the brace text as data, and a downstream command receives a filename with braces in it. A related trap is that brace expansion cannot see variable values, because it runs before variables are expanded — that is a consequence of expansion order and worth understanding separately. Also note that braces need no spaces: `{a, b}` contains a space, so the second alternative is ` b` with a leading space, which after the word splitting stage becomes its own word in a way you probably did not intend. Write comma lists tightly. ## Practical uses an interviewer likes to hear - Creating a directory skeleton in one command: `mkdir -p project/{src,test,docs}`. - Making a backup before editing in place: `cp /etc/nginx/nginx.conf{,.$(date +%F)}` — note the command substitution here runs *after* the brace expansion, which is fine because it is in the postscript, not inside the braces. - Generating a fixed set of test inputs: `touch data/file{01..20}.csv` on bash 4+. - Fetching several sibling paths: `curl -O http://host/pkg-{1,2,3}.tar.gz`. And the anti-use: do not reach for braces when you actually want to match existing files. `rm {*.log,*.tmp}` works, but only because each generated word is then globbed; if you meant "whatever logs exist", a glob alone is clearer.
- Why does `echo {a}` print the braces instead of expanding to `a`?Because it is not a valid brace expansion. Bash requires either at least one unquoted comma or a valid sequence expression such as `1..5` between the braces. When neither is present it leaves the whole construct as literal text, which is why a typo in a brace list fails silently rather than erroring — you get a word with braces in it.
- How would you generate the numbers 0, 5, 10, 15, 20 with brace expansion, and what should you check before shipping that script?`echo {0..20..5}`. The increment form and the zero-padded form `{01..10}` were added in bash 4.0, so check the interpreter on your targets — macOS still ships bash 3.2, where those silently pass through as literal text rather than erroring. If you need portability, use a counting loop or `seq` instead.
- In `ls {src,test}/*.py`, which happens first, the brace expansion or the glob?The braces. Bash expands `{src,test}/*.py` into the two words `src/*.py` and `test/*.py` first, and only later does pathname expansion match each of those patterns against the filesystem. That ordering is why braces can be used to build glob patterns, but a glob can never be used to build a brace list.
Brace expansion is a photocopier for text: it stamps out one copy of the surrounding characters per alternative. A glob is a search of the filing cabinet — it can only hand you documents that are already there.
saying these in an interview costs you the question
- Says brace expansion only matches files that exist
- Calls {a,b} a glob or a regex alternation
- Thinks {1..5} needs the files 1 through 5 to exist
- Expects an error from {a} instead of literal text
- Believes braces are expanded after variables