Erro no teste de colchete quando a string é um parêntese esquerdo

27

Eu costumava estar confiante sobre o fato de que citar seqüências de caracteres é sempre uma boa prática para evitar que o shell a analise.

Então me deparei com isso:

$ x='('
$ [ "$x" = '1' -a "$y" = '1' ]
bash: [: `)' expected, found 1

Tentando isolar o problema, obtendo o mesmo erro:

$ [ '(' = '1' -a '1' = '1' ]
bash: [: `)' expected, found 1

Eu resolvi o problema assim:

[ "$x" = '1' ] && [ "$y" = '1' ]

Ainda preciso saber o que está acontecendo aqui.

Claudio
fonte
2
Como solução alternativa, no bash, você pode usar[[ "$x" = '1' && "$y" = '1' ]]
terdon
3
A especificação POSIX para teste descreve explicitamente -ae -ocomo obsoleta por esse motivo (que é o que o [OB]sobrescrito ao lado de sua especificação significa). Se você escrevesse [ "$x" = 1 ] && [ "$y" = 1 ], você ficaria bem e estaria bem dentro do domínio do comportamento bem definido / padronizado.
Charles Duffy
6
É por isso que as pessoas costumavam usar [ "x$x" = "x1" ]para impedir que os argumentos fossem mal interpretados como operadores.
Jonathan Leffler
@ JonathanLeffler: Ei, você condensou minha resposta a uma única frase, não é justo! :) Se alguém usa um shell POSIX como no dashlugar do Bash, ainda é uma prática útil.
Nominal Animal
Quero agradecer a todos que estão tendo tempo para responder à minha pergunta, muito apreciados rapazes :)! Também quero agradecer a edição vital feita à minha pergunta. Entrar neste fórum às vezes me dá a mesma emoção de escapar de Alcatraz; movimento errado significa sua vida. De qualquer forma eu realmente gostaria que meus agradecimentos vai chegar até você antes que este comentário é excluído
Claudio

Respostas:

25

Este é um caso de canto muito obscuro, que pode ser considerado um bug na [definição do teste interno; no entanto, ele corresponde ao comportamento do [binário real disponível em muitos sistemas. Tanto quanto eu posso dizer, só afecta certos casos e uma variável com um valor que corresponde a um [operador como (, !, =, -e, e assim por diante.

Deixe-me explicar o porquê e como contornar isso nos shells Bash e POSIX.


Explicação:

Considere o seguinte:

x="("
[ "$x" = "(" ] && echo yes || echo no

Sem problemas; o acima não gera erro e gera resultados yes. É assim que esperamos que as coisas funcionem. Você pode alterar a sequência de comparação para '1'se desejar e o valor de x, e funcionará conforme o esperado.

Observe que o /usr/bin/[binário real se comporta da mesma maneira. Se você executar, por exemplo, '/usr/bin/[' '(' = '(' ']'não há erro, porque o programa pode detectar que os argumentos consistem em uma operação de comparação de string única.

O bug ocorre quando nós e com uma segunda expressão. Não importa qual é a segunda expressão, desde que válida. Por exemplo,

[ '1' = '1' ] && echo yes || echo no

saídas yese é obviamente uma expressão válida; mas, se combinarmos os dois,

[ "$x" = "(" -a '1' = '1' ] && echo yes || echo no

O Bash rejeita a expressão se e somente se xé (ou !.

Se executarmos o procedimento acima usando o [programa atual , ou seja,

'/usr/bin/[' "$x" = "(" -a '1' = '1' ] && echo yes || echo no

o erro seria compreensível: como o shell faz as substituições de variáveis, o /usr/bin/[binário recebe apenas parâmetros ( = ( -a 1 = 1e a finalização ], é compreensível que ele não analise se os parênteses abertos iniciam ou não uma subexpressão, havendo uma operação e . Certamente, é possível analisá-lo como duas comparações de strings, mas fazê-lo com avidez pode causar problemas quando aplicado a expressões apropriadas com subexpressões entre parênteses.

O problema, na verdade, é que o shell [interno se comporta da mesma maneira, como se expandisse o valor de xantes de examinar a expressão.

(Essas ambiguidades, e outras relacionadas à expansão de variáveis, foram um grande motivo pelo qual o Bash foi implementado e agora recomenda o uso das [[ ... ]]expressões de teste.)


A solução alternativa é trivial e geralmente é vista em scripts usando shshells mais antigos . Você adiciona um caractere "seguro", geralmente x, na frente das strings (ambos os valores sendo comparados), para garantir que a expressão seja reconhecida como uma comparação de strings:

[ "x$x" = "x(" -a "x$y" = "x1" ]
Animal Nominal
fonte
11
Eu não chamaria o comportamento do [embutido de bug. Se alguma coisa, é uma falha de design inerente. [[é uma palavra-chave do shell , não apenas um comando interno; portanto, ele analisa as coisas antes da remoção de cotações e substitui a divisão de palavras usual. por exemplo [[ $x == 1 ]], não precisa usar "$x", porque um [[contexto é diferente do normal. Enfim, é assim que [[é possível evitar as armadilhas do [. O POSIX precisa [se comportar dessa maneira e o bash é principalmente compatível com POSIX, mesmo sem ela --posix, portanto, mudar [para uma palavra-chave não é atraente.
27568 Peter Panes
Sua solução alternativa não é recomendada pelo POSIX. Basta usar duas chamadas para [.
Curinga
@ PeterCordes: Muito bem; bom ponto. (Talvez eu devesse ter usado o design, em vez de o definido no meu primeiro parágrafo, mas, como [o comportamento está sobrecarregado pelo histórico anterior ao POSIX, escolhi a última palavra.) Evito pessoalmente usar [[meus scripts de exemplo, mas apenas porque é um exceção à recomendação de citação que eu sempre uso (porque omitir aspas é o motivo mais comum para erros de script que vejo) e não pensei em um parágrafo simples para explicar por que [[é uma exceção às regras, sem fazer minha recomendação de citação suspeito.
Nominal Animal
@Wildcard: Não. O POSIX não recomenda essa prática , e é isso que importa. Só porque uma autoridade não recomenda essa prática, não torna isso ruim. De fato, como aponto, essa é uma prática histórica usada em shscripts, bem antes da padronização do POSIX. Usando subexpress~oes parêntesis e -ae -ooperadores lógicos em testes / [é mais eficiente que depender de encadeamento expressão (via &&e ||); é que nas máquinas atuais a diferença é irrelevante.
Nominal Animal
@Wildcard: No entanto, eu pessoalmente prefiro a cadeia de expressões de teste usando &&e ||, mas a razão é, torna-se mais fácil para nós seres humanos para manter (ler, compreender e modificar, se / quando necessário)-los sem a introdução de bugs. Portanto, não estou criticando sua sugestão, mas apenas o raciocínio por trás da sugestão. (Por razões muito semelhantes, o .LT., .GE., etc operadores de comparação em Fortran 77 tem versões muito mais amigáveis <, >=etc. em versões posteriores.)
Nominal animal
11

[aka testvê:

 argc: 1 2 3 4  5 6 7 8
 argv: ( = 1 -a 1 = 1 ]

testaceita subexpressões entre parênteses; então pensa que o parêntese esquerdo abre uma subexpressão e está tentando analisá-la; o analisador vê =como a primeira coisa na subexpressão e pensa que é um teste implícito de comprimento de cadeia, portanto é feliz; a subexpressão deve ser seguida por um parêntese direito e, em vez disso, o analisador encontra em 1vez de ). E reclama.

Quando testpossui exatamente três argumentos, e o argumento do meio é um dos operadores reconhecidos, ele aplica esse operador aos 1º e 3º argumentos sem procurar subexpressões entre parênteses.

Para obter os detalhes completos man bash, procure test expr.

Conclusão: O algoritmo de análise usado por testé complicado. Use apenas expressões simples e usar os shell operadores !, &&e ||combiná-los.

AlexP
fonte