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.
Well, it is unravelled one layer at a time:
X{{a..c},{1..3}}Y
is documented as being expanded to X{a..c}Y
X{1..3}Y
(that's X{A,B}Y
expanded to XA
XB
with A
being {a..c}
and B
being {1..3}
), themselves documented as being expanded to XaY
XbY
XcY
X1Y
X2Y
X3Y
.
What may be worth documenting is that they can be nested (that the first }
does not close the first {
in there for instance).
I suppose shells could have chosen to resolve the inner braces first, like by acting upon each closing }
in turn:
X{{a..c},{1..3}}
X{a,{1..3}}Y
X{b,{1..3}}Y
X{c,{1..3}}Y
(that is A{a..c}B
expanded to AaB
AbB
AcB
, where A
is X{
and B
is ,{1..3}Y
)
X{a,1}Y
X{a,2}Y
X{a,3}Y
X{b,1}Y
X{b,2}Y
X{b,3}Y
X{c,1}Y
X{c,2}Y
X{c,3}Y
XaY
X1Y
XaY
Xa2
...
But I don't find that particularly more intuitive nor useful (see Kevin's example in comments for instance), there would still be some ambiguity as to the order in which the expansions would be done, and that's not how csh
(the shell that introduced brace expansion in the late 70s, while the {1..3}
form came later (1995) from zsh
and {a..c}
yet later (2004) from bash
) did it.
Note that csh
(from the start, see the 2BSD (1979) man page) did document the fact that brace expansions could be nested, though did not explicitly say how nested brace expansions would be expanded. But you can look at the csh
code from 1979 to see how it was done then. See how it does explicitly handle nesting indeed, and how it's resolved starting from the outer braces.
In any case, I don't really see how the expansion of {a..c},{1..3}
could have any bearing. In there, the ,
is not an operator of a brace expansion (as it's not inside braces), so is treated like any ordinary character.
Best Answer
The shell expands
*
only if un-quoted, any quoting stops expansion by the shell.Also, a brace expansion needs to be unquoted to be expanded by the shell.
This work (lets use echo to see what the shell does):
Even if there are files with some other names:
Why that works?
It is important that we understand why that works. It is because of the order of expansion. First the "Brace expansion" and later (the last one) "Pathname Expansion" (a.k.a glob-expansion).
We can turn off "Pathname expansion" for a moment:
The "Pathname Expansion" receives two arguments:
*.ext1
and*.ext2
.The problem is that we can not use a variable for the brace expansion.
It has been explained many times before for using a variable inside a "Brace Expansion"
To expand a "Brace Expansion" that is the result of a "Variable Expansion", you need to re-submit the command line to the shell with
eval
.Values of the file names bring no execution problem for eval:
But the value of
$list
could be unsafe. However, the value of$list
is set by the script writer. The script writer is in control ofeval
: Just not use externally set values for$list
. Try This:A better alternative.
An alternative (without eval) is to use Bash "Extended Patterns":
Note: Please be aware that both solutions (eval and patterns) (as written) are safe for filenames with spaces or new lines. But will fail for a
$list
with spaces, because$list
is unquoted or the eval removes the quotes.