I couldn't figure out how to get this done using only curly braces. I don't see a way to achieve this either, so unless someone more clever than I can figure out a way I'd say it's not possible.
As an alternative
Sample Data
$ tree
.
|-- dir1
| |-- file1
| `-- file2
`-- dir2
|-- file1
`-- file2
Examples
$ seq 2 | xargs -i{} echo dir{}/file{}
dir1/file1
dir2/file2
This can be put into a command like this:
$ echo $(seq 2 | xargs -i{} echo dir{}/file{})
dir1/file1 dir2/file2
or this:
$ ls $(seq 2 | xargs -i{} echo dir{}/file{})
dir1/file1 dir2/file2
or this:
$ ls -l $(seq 2 | xargs -i{} echo dir{}/file{})
-rw-rw-r-- 1 saml saml 0 Sep 2 03:18 dir1/file1
-rw-rw-r-- 1 saml saml 0 Sep 2 03:31 dir2/file2
Why curly braces can't do this
If you look at your original example:
{foo,bar}Q{foo,bar}
The way this gets expanded is as follows:
fooQfoo fooQbar barQfoo barQbar
The mechanism that expanded this is called a Cartesian Product.
For example:
$ echo {A,B}{X,Y,Z}
AX AY AZ BX BY BZ
Or this:
$ echo {M,N}-{A,B}{X,Y,Z}
M-AX M-AY M-AZ M-BX M-BY M-BZ N-AX N-AY N-AZ N-BX N-BY N-BZ
There is no way to create a Cartesian Product that will result in:
fooQfoo barQbar
You're only option is to either resort to trickery such as this:
$ echo dir{1,2}/file{2,1}
dir1/file2 dir1/file1 dir2/file2 dir2/file1
And then put this into a Bash array:
$ a=(dir{1,2}/file{2,1})
$ echo ${a[@]:1:2}
dir1/file1 dir2/file2
The other option would be some "other method" such as the one I previously discussed above (using xargs
) for example.
References
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.
Best Answer
Yes,
is first expanded to:
And then the loop runs over the independent expansion of those two globs.
What you want here is one glob. Here,
*[bc]*
would do, but for anything more complex, inbash
, you'd need to enable ksh-like extended globs. You'll probably want the nullglob option as well:In
zsh
:The
(N)
being for a per-globnullglob
.In
ksh93
: