为什么不对由$()中的命令打印的{1,2}进行插值?


8

我所在的目录中有两个文本文件:

$ touch test1.txt
$ touch test2.txt

当我尝试使用某种模式列出文件(使用Bash)时,它可以工作:

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

但是,当使用内含的命令生成模式时$(),只有一种模式有效:

$ 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

这里发生了什么?为什么该模式{1,2}不起作用?


4
大括号扩展不能用单引号或双引号引起来
Sergiy Kolodyazhnyy

3
@SergiyKolodyazhnyy这个问题的一点是,?援引,并得到后扩大$(...)的替代品,但支架膨胀不会。
Michael Homer

1
@muru不,那不是同一个问题。在这里,扩展的顺序无关紧要,重要的是在哪种情况下发生哪种扩展。如果这个问题重复出现,我不会感到惊讶,但是我找不到它。
吉尔斯(Gilles)'所以

1
@mosvy Ksh和bash以相同的顺序进行扩展,但是在bash根本不执行扩展的情况下,ksh进行大括号扩展。Zsh-with-globsubst与bash进行相同的扩展,但是顺序不同。
吉尔(Gilles)'所以

1
@吉尔斯,不,他们不。正如已记录并易于演示的那样,ksh(和zsh)将在遍历之前执行大括号扩展。zsh-with-globsubst不会对$-expansions:的结果执行任何大括号扩展zsh -o globsubst -c 'a=/e*; b={/b*,/v*}; echo $a; echo $b'
mosvy

Answers:


17

这是两件事的结合。首先,大括号扩展名不是与文件名匹配的模式:它是纯文本替换-请参阅`a [bc] d`(括号)和`a {b,c} d`(括号)有什么区别?。其次,当您使用双引号(ls $(…))之外的命令替换结果时,只会发生模式匹配(和单词拆分:“ split + glob”运算符),而不是完整的重新解析。

使用ls $(echo 'test?.txt'),命令echo 'test?.txt'输出字符串test?.txt(带有最后的换行符)。命令替换产生字符串test?.txt(没有最终的换行符,因为命令替换会删除尾随的换行符)。这种无引号的替换会进行单词拆分,从而产生一个由单个字符串组成的列表,test?.txt因为其中没有空格字符(更确切地说,是中没有字符$IFS)。然后,此单元素列表的每个元素都会进行条件通配符扩展,并且由于?字符串中存在通配符,因此通配符扩展确实会发生。由于该模式test?.txt至少匹配一个文件名,因此list元素test?.txt将替换为与该模式匹配的文件名列表,从而产生包含两个元素的列表,其中包含test1.txttest2.txt。最后ls用两个参数test1和调用test2

使用ls $(echo 'test{1,2}'),命令echo 'test{1,2}'输出字符串test{1,2}(带有最后的换行符)。命令替换将生成字符串test{1,2}。这种无引号的替换将进行单词拆分,从而产生由单个字符串组成的列表test{1,2}。然后,此单元素列表中的每个元素都会进行有条件的通配符扩展,由于该字符串中没有通配符,因此不执行任何操作(该元素保留原样)。因此ls用单个参数调用test{1,2}

为了进行比较,以下是与发生的情况ls $(echo test{1,2})。该命令echo test{1,2}输出字符串test1 test2(带有最后的换行符)。命令替换将导致字符串test1 test2(不带最终换行符)。这种无引号的替换经过单词拆分,产生两个字符串test1test2。然后,由于这两个字符串都不包含通配符,因此它们将被单独保留,因此ls使用两个参数test1和进行调用test2


3
请注意,pdksh和ksh93确实会在扩展时执行大括号扩展(在glob93之前;不是在noglob上,但是在ksh93的情况下,仍然在braceexpand关闭时!)
StéphaneChazelas

您似乎忘记.txt了第二种解释。
瓦尔说恢复莫妮卡

10

扩展顺序为:大括号扩展;波浪号扩展,参数和变量扩展,算术扩展和命令替换(以从左到右的方式完成);分词 和文件名扩展。

替换命令后不会进行括号扩展。您可以使用eval强制进行另一轮扩展:

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

结果是:

1lala 2lala

6

这个问题是特定于的bash,因为他们决定bash将大括号扩展名与文件名扩展名(globbing)分开,并先执行它,然后再执行其他所有扩展名。

bash联机帮助页:

扩展顺序为:大括号扩展;波浪号扩展,参数和变量扩展,算术扩展和命令替换(以从左到右的方式完成);分词 和路径名扩展。

在您的示例中,bash仅在执行命令替换($(echo ...))后才看到大括号,但为时已晚。

这不同于所有其他外壳,它们在路径名扩展(globbing)之前(甚至其中一部分)执行括号扩展。这包括但不限于csh最早在括号内发明的位置。

$ 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

后者的例子是在相同cshzshksh93mkshfish

另外,请注意,通过库功能(至少在Linux和所有BSD上)以及在其他独立的实现中(例如,在perl:中),也可以使用括号扩展作为全局的一部分glob(3)perl -le 'print join " ", <test{1,2}.txt>'

为什么这样做有所不同bash可能背后有一个故事,但是FWIW我找不到任何合乎逻辑的解释,而且我发现所有事后合理化都令人信服。


3
需要注意的是perl用于调用csh扩大水珠,所以这并不奇怪,它仍可以识别相同的通配符运营商csh
斯特凡Chazelas

1

请试试:::

ls $(回声测试{1,2} \。txt)

带反斜杠。现在可以使用了。还要删除像前面的海报所说的那样的引号。点不是用于匹配模式,而是在此处按字面意义使用。


(1)问题问:“这是怎么回事?为何模式{1,2}“表现出行为方式”?问题不问“如何使用命令{1,2}来表现命令的?工作方式?” 您在回答错误的问题。(2)反斜杠与此无关。您的命令的工作方式与之相同,因为您已删除了问题中命令中的引号。
G-Man说'Resstate Monica''Jun

0

如果您删除引号,则可以使用

$ ls $(echo test{1,2})
test1  test2

9
扩展现在发生在命令替换之前,我不认为这是问题的要求(比较发生了什么?)。
Michael Homer
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.