5

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?

1
  • Are you sure you're getting a file called *.bk?  It should be *.djvu.bk.  If it's really *.bk, are you sure you're using bash? Apr 1, 2015 at 0:52

2 Answers 2

9

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.

3
  • Why doesn't glob-expansion perform on both *.djvu and *.djvu.bk?
    – Tim
    Mar 31, 2015 at 22:30
  • Oh, it does; it's just that *.djvu.bk matches no existing file. I will update my answer.
    – dhag
    Mar 31, 2015 at 22:32
  • shopt -s nullglob may help (at least in bash)
    – derobert
    Apr 1, 2015 at 1:43
4

Brace expansion happens before wildcard expansion, because brace expansion creates separate words, whereas wildcard expansion happens at the very end when the wildcard patterns are already in separate words. (Anyway, if it happened the other way round, then cp *.djvu{,.bk} would match nothing, because there is no file whose name ends with .djvu{,.bk}, and then brace expansion would produce the two words *.dvju and *.djvu.bk.) So first cp *.djvu{,.bk} is expanded to cp *.djvu *.djvu.bk, then each word containing wildcards is expanded if the wildcards match at least one file. Since you have files matching *.djvu but none matching *.djvu.bk, you end up with something like cp a.djvu b.djvu *.djvu.bk.

If there are files matching *.djvu.bk, then this wildcard pattern would be expanded as well, so you'd end up with something like cp a.djvu b.djvu a.djvu.bk c.djvu.bk. If the purpose was to run cp on both *.djvu and *.djvu.bk files, but allow either pattern to match nothing, then you could use ksh extended patterns:

shopt -s extglob
cp *.djvu?(.bk) somedir/

But that isn't what you're trying to do. You cannot get what you want by any form of expansion on the arguments of cp: you need to issue a separate cp command for each .djvu file, each with the right target. The only case where one cp command is enough is if there is a single .djvu file, so that *.djvu expands to a single file. In this case, the easiest way to type the command is to type the pattern (start typing cp *.djvu), then tell bash to expand it by pressing Ctrl+X *, press Backspace to delete the trailing space and type {,.bk}.

If there are multiple files to copy, you need to invoke cp in a loop, one way or another, or else use a different tool that can copy files according to name patterns.

Writing the loop in bash is pretty easy:

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

Another tool you can use is pax. It isn't much less typing here.

pax -rw -s'/$/.bk/' *.djvu .

Zsh has zcp for this kind of task. Load it in your .zshrc:

autoload -U zmv
alias zcp='zmv -C' zln='zmv -L'

then run

zcp '*.djvu' '$f.bk'

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.