Por que {1,2} impresso por um comando em $ () não é interpolado?

8

Estou em um diretório no qual tenho dois arquivos de texto:

$ touch test1.txt
$ touch test2.txt

Quando tento listar os arquivos (com o Bash) usando algum padrão, ele funciona:

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

No entanto, quando um padrão é produzido por um comando incluído $(), apenas um dos padrões funciona:

$ 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

O que está acontecendo aqui? Por que o padrão {1,2}não funciona?

Herosław Miraszewski
fonte
4
Expansão Brace não é executada dentro de aspas simples ou duplas
Sergiy Kolodyazhnyy
3
@SergiyKolodyazhnyy O ponto da questão é que o arquivo também? é citado e é expandido depois que o substitui, mas a expansão da chave não. $(...)
Michael Homer
11
@uru Não, esse não é o mesmo problema. Aqui a ordem das expansões não importa, o que importa é qual expansão ocorre em que contexto. Eu não ficaria surpreso se essa pergunta fosse uma cópia, mas não consegui encontrá-la.
Gilles 'SO- stop be evil'
11
O @mosvy Ksh e bash fazem expansões na mesma ordem, mas o ksh prepara a expansão em um caso em que o bash não faz nada. Zsh-with-globsubst faz as mesmas expansões que o bash, mas em uma ordem diferente.
Gilles 'SO- stop be evil'
11
@ Gilles não, eles não. Conforme documentado e facilmente demonstrado, o ksh (e o zsh) executará a expansão da braçadeira imediatamente antes do globbing. zsh-com-globsubst não irá executar qualquer expansão cinta em tudo nos resultados de $-expansions: zsh -o globsubst -c 'a=/e*; b={/b*,/v*}; echo $a; echo $b'.
mosvy

Respostas:

17

É uma combinação de duas coisas. Primeiro, a expansão entre chaves não é um padrão que corresponde aos nomes dos arquivos: é uma substituição puramente textual - consulte Qual é a diferença entre `a [bc] d` (colchetes) e` a {b, c} d` (chaves)? . Segundo, quando você usa o resultado de uma substituição de comando fora de aspas duplas ( ls $(…)), o que acontece é apenas a correspondência de padrões (e a divisão de palavras: o operador “split + glob”), não uma nova análise completa.

Com ls $(echo 'test?.txt'), o comando echo 'test?.txt'gera a string test?.txt(com uma nova linha final). A substituição do comando resulta na sequência test?.txt(sem uma nova linha final, porque a substituição do comando retira as linhas novas à direita). Essa substituição não citada sofre divisão de palavras, produzindo uma lista que consiste na sequência única, test?.txtpois não há caracteres de espaço em branco (mais precisamente, não há caracteres $IFS) nela. Cada elemento dessa lista de um elemento passa por uma expansão curinga condicional e, como existe um caractere curinga ?na string, a expansão do curinga acontece. Como o padrão test?.txtcorresponde a pelo menos um nome de arquivo, o elemento list test?.txté substituído pela lista de nomes de arquivos que correspondem aos padrões, resultando na lista de dois elementos que contémtest1.txte test2.txt. Finalmente lsé chamado com dois argumentos test1e test2.

Com ls $(echo 'test{1,2}'), o comando echo 'test{1,2}'gera a string test{1,2}(com uma nova linha final). A substituição do comando resulta na cadeia test{1,2}. Essa substituição não citada sofre divisão de palavras, resultando em uma lista que consiste na sequência única test{1,2}. Cada elemento dessa lista de um elemento passa por uma expansão curinga condicional, o que não faz nada (o elemento é deixado como está), pois não há caractere curinga na sequência. Assim lsé chamado com o argumento único test{1,2}.

Para comparação, eis o que acontece com ls $(echo test{1,2}). O comando echo test{1,2}gera a string test1 test2(com uma nova linha final). A substituição do comando resulta na sequência test1 test2(sem uma nova linha final). Essa substituição sem aspas sofre divisão de palavras, produzindo duas cadeias test1e test2. Então, como nenhuma das seqüências contém um caractere curinga, elas são deixadas sozinhas, por isso lsé chamado com dois argumentos test1e test2.

Gilles 'SO- parar de ser mau'
fonte
3
Note-se que pdksh e ksh93 fazer executar expansão cinta em cima expansões (antes englobamento; não com noglob, mas no caso de ksh93, ainda quando braceexpand está desligado!)
Stéphane Chazelas
Você parece ter esquecido .txtna segunda explicação.
val diz Reintegrar Monica
10

A ordem das expansões é: expansão de chaves; expansão de til, expansão de parâmetro e variável, expansão aritmética e substituição de comando (feita da esquerda para a direita); divisão de palavras; e expansão de nome de arquivo.

A expansão da cinta não ocorrerá após a substituição do comando. Você pode usar eval para forçar outra rodada de expansão:

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

O resultado é:

1lala 2lala
dedowsdi
fonte
6

Esse problema é muito específico bashe é porque eles decidiram bashseparar a expansão da chave da expansão do nome do arquivo (globbing) e executá-la primeiro, antes de todas as outras expansões.

Na página de bashmanual:

A ordem das expansões é: expansão de chaves; expansão de til, expansão de parâmetro e variável, expansão aritmética e substituição de comando (feita da esquerda para a direita); divisão de palavras; e expansão do nome do caminho.

No seu exemplo, bashvocê só verá seus aparelhos depois de executar a substituição de comando (the $(echo ...)), quando for tarde demais.

Isso é diferente de todos os outros invólucros, que executam a expansão da cinta logo antes (e alguns até como parte da) expansão do nome do caminho (globbing). Isso inclui, mas não se limita a, cshonde as expansões de braquetes foram inventadas pela primeira vez.

$ 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

O último exemplo, é a mesma em csh, zsh, ksh93, mkshou fish.

Além disso, observe que a expansão do suporte como parte do globbing também está disponível através da glob(3)função de biblioteca (pelo menos no Linux e em todos os BSDs) e em outras implementações independentes (por exemplo, em perl:) perl -le 'print join " ", <test{1,2}.txt>'.

Por que isso foi feito de maneira diferente bashprovavelmente tem uma história por trás disso, mas FWIW eu não consegui encontrar nenhuma explicação lógica e acho todas as racionalizações post-hoc não convincentes.

mosvy
fonte
3
Note-se que perlusado para invocar csha expandir globs, por isso não é surpreendente que ele ainda reconhece os mesmos operadores englobamento comocsh
Stéphane Chazelas
1

Tente por favor:::

ls $ (teste de eco {1,2} \. txt)

Com uma barra invertida. Funciona agora. Remova também o que o pôster anterior disse, as aspas. O ponto não é para o padrão correspondente, mas para ser considerado literalmente como Período aqui.

mkzia
fonte
(1) A pergunta é “O que está acontecendo aqui? Por que [o] padrão {1,2}”se comporta dessa maneira? A pergunta não pergunta: "Como posso obter um comando {1,2}para se comportar da maneira que o comando ?funciona?" Você está respondendo à pergunta errada. (2) A barra invertida não tem nada a ver com isso. Seu comando funciona da maneira que funciona porque você removeu as aspas que estavam no comando na pergunta.
G-Man diz 'Restabelecer Monica
0

Funciona se você remover as aspas

$ ls $(echo test{1,2})
test1  test2
darxmurf
fonte
9
A expansão agora está acontecendo antes da substituição do comando, o que eu acho que não é o que a pergunta está pedindo (compare o que está acontecendo com ?).
Michael Homer