8

I am in a directory in which I have two text files:

$ touch test1.txt
$ touch test2.txt

When I try to list the files (with Bash) using some pattern it works:

$ ls test?.txt
test1.txt  test2.txt
$ ls test{1,2}.txt
test1.txt  test2.txt

However, when a pattern is produced by a command enclosed in $(), only one of patterns work:

$ ls $(echo 'test?.txt')
test1.txt  test2.txt
$ ls $(echo 'test{1,2}.txt')
ls: cannot access test{1,2}.txt: No such file or directory

What's going on here? Why the pattern {1,2} does not work?

  • 4
    Brace expansion isn't performed within single or double quotes – Sergiy Kolodyazhnyy Jun 06 '19 at 06:47
  • brace-expansion does _not_ work inside quotes (single or double) – Inian Jun 06 '19 at 06:47
  • 3
    @SergiyKolodyazhnyy The point of the question is that the `?` is *also* quoted, and gets expanded after `$(...)` substitutes it, but the brace expansion doesn't. – Michael Homer Jun 06 '19 at 06:48
  • 1
    @muru No, that's not the same issue. Here the _order_ of expansions doesn't matter, what matters is which expansion takes place in which context. I wouldn't be surprised if this question was a duplicate, but I couldn't find it. – Gilles 'SO- stop being evil' Jun 06 '19 at 07:00
  • @Gilles doesn't the order matter though? If brace expansion took place after command substitution, `ls $(echo 'test{1,2}.txt')` would work like OP expects it to. – muru Jun 06 '19 at 07:02
  • @muru No. For example, compare `bash -c 'echo {a,b}$(echo foo >/dev/tty)'` and `zsh -c 'setopt glob_subst; echo {a,b}$(echo foo >/dev/tty)'`. Bash and zsh differ in the order of brace expansion vs command substitution, which causes this command to produce different output. Zsh doesn't normally do globbing on the output of a command substitution, but `setopt glob_subst` turns this feature on (without changing anything about the _order_ of expansion), and so zsh-with-globsubst would behave just like bash for the sake of this particular question. – Gilles 'SO- stop being evil' Jun 06 '19 at 07:08
  • I think what Gilles means is that order doesn't matter in this particular case. The outter shell sees `{a,b}$(echo foo >/dev/tty)` on the same level, but with `ls $(echo 'test{1,2}.txt')` the brace expansion is seen only by subshell. Is that correct, @Gilles ? – Sergiy Kolodyazhnyy Jun 06 '19 at 07:15
  • @SergiyKolodyazhnyy I didn't mean that the order doesn't matter — I gave an example where it does! I meant that the order of expansion is not what affects the behavior in the case given by the question. With `{a,b}$(echo foo >/dev/tty)`, in a shell where brace expansion happens before command substitution, `foo` is printed twice, but if brace expansion happens after command substitution, `foo` is only printed once. – Gilles 'SO- stop being evil' Jun 06 '19 at 07:18
  • @Gilles yet the OP's example will work as expected in shells where the order of expansions is different, like `ksh`. The `zsh` case is different because `zsh` will do no further expansions on the result of `$(...)`, and there seems to be only flags to force simple globbing (`~`) or splitting (`=`), but not brace expansion. –  Jun 06 '19 at 19:00
  • 1
    @mosvy Ksh and bash do expansions in the same order, but ksh does brace expansion in a case where bash doesn't do it at all. Zsh-with-globsubst does the same expansions as bash, but in a different order. – Gilles 'SO- stop being evil' Jun 06 '19 at 20:59
  • 1
    @Gilles no they don't. As documented and easily demonstrated, ksh (and zsh) will perform the brace expansion just before globbing. zsh-with-globsubst won't perform any brace expansion at all on the results of `$`-expansions: `zsh -o globsubst -c 'a=/e*; b={/b*,/v*}; echo $a; echo $b'`. –  Jun 06 '19 at 22:14

5 Answers5

17

It's a combination of two things. First, brace expansion is not a pattern that matches file names: it's a purely textual substitution — see What is the difference between `a[bc]d` (brackets) and `a{b,c}d` (braces)? . Second, when you use the result of a command substitution outside double quotes (ls $(…)), what happens is only pattern matching (and word splitting: the “split+glob” operator), not a complete re-parsing.

With ls $(echo 'test?.txt'), the command echo 'test?.txt' outputs the string test?.txt (with a final newline). The command substitution results in the string test?.txt (without a final newline, because command substitution strips trailing newlines). This unquoted substitution undergoes word splitting, yielding a list consisting of the single string test?.txt since there are no whitespace characters (more precisely, no characters in $IFS) in it. Each element of this one-element list then undergoes conditional wildcard expansion, and since there is a wildcard character ? in the string the wildcard expansion does happen. Since the pattern test?.txt matches at least one file name, the list element test?.txt is replace by the list of file names that match the patterns, yielding the two-element list containing test1.txt and test2.txt. Finally ls is called with two arguments test1 and test2.

With ls $(echo 'test{1,2}'), the command echo 'test{1,2}' outputs the string test{1,2} (with a final newline). The command substitution results in the string test{1,2}. This unquoted substitution undergoes word splitting, yielding a list consisting of the single string test{1,2}. Each element of this one-element list then undergoes conditional wildcard expansion, which does nothing (the element is left as is) since there is no wildcard character in the string. Thus ls is called with the single argument test{1,2}.

For comparison, here's what happens with ls $(echo test{1,2}). The command echo test{1,2} outputs the string test1 test2 (with a final newline). The command substitution results in the string test1 test2 (without a final newline). This unquoted substitution undergoes word splitting, yielding two strings test1 and test2. Then, since neither of the strings contains a wildcard character, they're left alone, so ls is called with two arguments test1 and test2.

fra-san
  • 9,931
  • 2
  • 21
  • 42
Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
10

The order of expansions is: brace expansion; tilde expansion, parameter and variable expansion, arithmetic expansion, and command substitution (done in a left-to-right fashion); word splitting; and filename expansion.

Brace expansion won't happen after command substitution. You can use eval to force another round of expansion:

eval echo $(echo '{1,2}lala')

It's result is:

1lala 2lala
dedowsdi
  • 1,008
  • 6
  • 12
6

That problem is very specific to bash, and it's because they decided in bash to separate the brace expansion from filename expansion (globbing), and to perform it first, before all the other expansions.

From the bash manpage:

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

In your example, bash will only see your braces after it had performed the command substitution (the $(echo ...)), when it's just too late.

This is different from all the other shells, which perform the brace expansion just before (and some even as a part of) pathname expansion (globbing). That includes but is not limited to csh where brace-expansions were first invented.

$ csh -c 'ls `echo "test{1,2}.txt"`'
test1.txt test2.txt
$ ksh -c 'ls $(echo "test{1,2}.txt")'
test1.txt  test2.txt

$ var=nope var1=one var2=two bash -c 'echo $var{1,2}'
one two
$ var=nope var1=one var2=two csh -c 'echo $var{1,2}'
nope1 nope2

The latter example is the same in csh, zsh, ksh93, mksh or fish.

Also, notice that brace expansion as part of globbing is also available via the glob(3) library function (at least on Linux and all the BSDs), and in other independent implementations (eg. in perl: perl -le 'print join " ", <test{1,2}.txt>').

Why that was done differently in bash has probably a story behind it, but FWIW I wasn't able to find any logical explanation, and I find all the post-hoc rationalizations unconvincing.

Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
  • 3
    Note that `perl` used to invoke `csh` to expand globs, so it's not surprising that it still recognises the same globbing operators as `csh` – Stéphane Chazelas Jun 06 '19 at 20:29
0

It works if you remove the quotes

$ ls $(echo test{1,2})
test1  test2
darxmurf
  • 1,097
  • 6
  • 19
  • 9
    The expansion is now happening before the command substitution, which I don't think is what the question is asking for (compare what's happening with `?`). – Michael Homer Jun 06 '19 at 06:48
0

Please try:::

ls $(echo test{1,2}\.txt)

With a BackSlash. It works now. Also remove the like the earlier poster said, the quotes. The Dot is not for matching pattern, but to be taken literally as Period here.

mkzia
  • 39
  • 2
  • (1) The question asks “What’s going on here?  Why [does] the pattern `{1,2}`” behave the way it does?  The question does not ask “How can I get a command using `{1,2}` to behave the way the command with `?` works?”  You are answering the wrong question.   (2) The backslash has nothing to do with it.  Your command works the way it does because you have removed the quotes that were in the command in the question. – G-Man Says 'Reinstate Monica' Jun 08 '19 at 20:34