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?.txt
uma 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?.txt
corresponde 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.txt
e test2.txt
. Finalmente ls
é chamado com dois argumentos test1
e 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 test1
e test2
. Então, como nenhuma das strings contém um caractere curinga, elas são deixadas sozinhas, então ls
são chamadas com dois argumentos test1
e 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 bash
separar 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 bash
pá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, bash
só 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, csh
onde 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
.mksh
fish
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 bash
provavelmente 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.