Bash – brace expansion and * expansion

bashbrace-expansionwildcards

Assume in the working directory, there is only one djvu file. I would like to backup the file into a file with file name having extra .bk at the end. cp *.djvu{,.bk} copies the djvu file into a file named *.djvu.bk. Why is * in the backup file name not expanded? How can it be?

Best Answer

What happens is that bash first expands *.djvu{,.bk} into *.djvu *.djvu.bk, and then does glob-expansion on those. This would explain what you observe: in your case, *.djvu, matches an existing file, say foo.djvu and expands into that, but *.djvu.bk matches no file, and thus expands as itself, *.djvu.bk.

The order of expansion is specified in the bash documentation:

The order of expansions is: brace expansion, tilde  expansion,  parameā€
ter,  variable  and arithmetic expansion and command substitution (done
in a left-to-right fashion), word splitting, and pathname expansion.

I would suggest rewriting your copy command as:

for f in *.djvu; do cp -- "$f" "$f".bk; done

Or perhaps, to avoid the syntactic overhead of an explicit for loop:

parallel -j1 cp -- {} {}.bk ::: *.djvu

(On second thoughts... that's not really much shorter.)

To answer your sub-question "how could it be expanded", one could use a sub-command (example in a directory containing just foo.djvu and bar.djvu):

$ echo $(echo *.djvu){,.bk}
bar.djvu foo.djvu bar.djvu foo.djvu.bk

But that isn't as safe a solution as the for loop or parallel call above; it will break down on file names containing white space.