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

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

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

$ touch test1.txt
$ touch test2.txt

Quando tento listar os arquivos (com Bash) usando algum padrão 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 entre $(), 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?

Responder1

É uma combinação de duas coisas. Primeiro, a expansão de chaves não é um padrão que corresponde a nomes de arquivos: é uma substituição puramente textual - vejaQual é a diferença entre `a[bc]d` (colchetes) e `a{b,c}d` (colchetes)?. Segundo, quando você usa o resultado de uma substituição de comando fora das aspas duplas ( ls $(…)), o que acontece é apenas a correspondência de padrões (e a divisão de palavras: o operador “split+glob”), e 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 string test?.txt(sem uma nova linha final, porque a substituição do comando remove as novas linhas finais). Essa substituição sem aspas sofre divisão de palavras, produzindo uma lista que consiste em uma única string, test?.txtuma vez que não há caracteres de espaço em branco (mais precisamente, nenhum caractere em $IFS). Cada elemento desta lista de um elemento passa então por expansão condicional de curinga e, como há um caractere curinga ?na string, a expansão de 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, produzindo a lista de dois elementos contendo test1.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 string test{1,2}. Essa substituição sem aspas sofre divisão de palavras, produzindo uma lista que consiste em uma única string test{1,2}. Cada elemento desta lista de um elemento passa então por uma expansão condicional de curinga, que não faz nada (o elemento é deixado como está), pois não há nenhum caractere curinga na string. Assim lsé chamado com o único argumento test{1,2}.

Para efeito de comparação, aqui está 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 string test1 test2(sem uma nova linha final). Essa substituição sem aspas sofre divisão de palavras, produzindo duas strings test1e test2. Então, como nenhuma das strings contém um caractere curinga, elas são deixadas sozinhas, então lssão chamadas com dois argumentos test1e test2.

Responder2

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

A expansão de chaves não acontecerá 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')

Seu resultado é:

1lala 2lala

Responder3

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

Na bashpágina de manual:

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

No seu exemplo, bashsó verá o seu aparelho depois de ter realizado a substituição do comando (o $(echo ...)), quando já for tarde demais.

Isso é diferente de todos os outros shells, que executam a expansão de chaves logo antes (e alguns até como parte da) expansão do nome do caminho (globbing). Isso inclui, mas não está limitado a, cshonde as expansões de chaves 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 é o mesmo em csh, zsh, ou ksh93.mkshfish

Além disso, observe que a expansão da chavecomo parte do globbingtambé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>').

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

Responder4

Tente por favor:::

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

Com uma barra invertida. Agora está funcionando. Remova também o que o autor da postagem anterior disse, as aspas. O Ponto não serve para combinar padrões, mas deve ser interpretado literalmente como Ponto aqui.

informação relacionada