Eu encontrei comparações de variáveis com literais de string várias vezes ao longo dos anos que tinham um caractere prefixando a variável e o literal, por exemplo
if [ "x$A" = "xtrue" ]; then
para verificar se $A
é "true"
.
Presumo que isso seja feito para obter compatibilidade com o shell ou para contornar um bug de longo prazo, um comportamento não intuitivo, etc. Nada óbvio vem à mente.
Hoje imaginei que queria saber o motivo, mas minha pesquisa não deu em nada. Ou talvez seja apenas eu fazendo algo de uma exposição bastante frequente a ocorrências raras.
Essa prática ainda é útil, talvez até melhor?
O importante a entender aqui é que na maioria dos shells¹,
[
é apenas um comando comum analisado pelo shell como qualquer outro comando comum.Em seguida, o shell invoca esse comando
[
(também conhecido comotest
) com uma lista de argumentos e, em seguida, cabe[
a interpretá-los como uma expressão condicional.Nesse ponto, essas são apenas uma lista de strings e as informações sobre quais resultaram de alguma forma de expansão são perdidas, mesmo nos shells em que
[
está embutido (todos do tipo Bourne atualmente).O
[
utilitário costumava ter dificuldade em dizer quais de seus argumentos eram operadores e quais eram operandos (a coisa em que os operadores trabalham). Não ajudou que a sintaxe fosse intrinsecamente ambígua. Por exemplo:[ -t ]
costumava ser (e ainda é em alguns shells/[
s) para testar se stdout é um terminal.[ x ]
é a abreviação de[ -n x ]
: test sex
é uma string não vazia (para que você possa ver que há um conflito com o acima).[
s,-a
e-o
pode ser tanto unário ([ -a file ]
para arquivo acessível (agora substituído por[ -e file ]
),[ -o option ]
for a opção está habilitada? ) e operadores binários ( e e ou ). Novamente,! -a x
pode serand(nonempty("!"), nonempty("x"))
ounot(isaccessible("x"))
.(
e adicione mais problemas)
.!
Em linguagens de programação normais como C ou
perl
, em:Não há como o conteúdo de
$a
ou$b
ser considerado como operadores porque a expressão condicional é analisada antes deles$a
e$b
é expandida. Mas em conchas, em:O shell expande as variáveis primeiro ². Por exemplo, se
$a
contém(
e$b
contém)
, tudo o que o[
comando vê são[
,(
,=
e argumentos)
.]
Então isso significa"(" = ")"
(são(
e)
lexicalmente iguais) ou( -n = )
(é=
uma string não vazia).As implementações históricas (
test
apareceram no Unix V7 no final dos anos 70) costumavam falhar mesmo nos casos em que não eram ambíguas apenas por causa da ordem em que estavam processando seus argumentos.Aqui com a versão 7 Unix em um emulador PDP11:
A maioria dos shells e
[
implementações têm ou tiveram problemas com essas ou suas variantes . Combash
4.4 hoje:POSIX.2 (publicado no início dos anos 90) desenvolveu um algoritmo que tornaria
[
o comportamento de 's inequívoco e determinístico quando passado no máximo 4 argumentos (ao lado de[
e]
) nos padrões de uso mais comuns ([ -f "$a" -o "$b" ]
ainda não especificados, por exemplo). Ele descontinuou(
,)
,-a
e-o
, e foi descartado-t
sem operando.bash
implementou esse algoritmo (ou pelo menos tentou) embash
2.0.[
Assim, em implementações compatíveis com POSIX ,[ "$a" = "$b" ]
é garantido comparar o conteúdo de$a
e$b
para igualdade, sejam eles quais forem. Sem-o
, escreveríamos:Ou seja, chame
[
duas vezes, cada vez com menos de 5 argumentos.Mas demorou um pouco para que todas as
[
implementações se tornassem compatíveis.bash
's não era compatível até 4.4 (embora o último problema fosse para o[ '(' ! "$var" ')' ]
qual ninguém realmente usaria na vida real)O
/bin/sh
do Solaris 10 e anterior, que não é um shell POSIX, mas um shell Bourne ainda tem problemas com[ "$a" = "$b" ]
:O uso
[ "x$a" = "x$b" ]
contorna o problema, pois não há[
operador que comece comx
. Outra opção é usarcase
em vez disso:(citar é necessário em torno
$b
de , não em torno de$a
).De qualquer forma, não é e nunca foi sobre valores vazios. As pessoas têm problemas com valores vazios
[
quando se esquecem de citar suas variáveis, mas isso não é um problema[
.com o valor padrão de
$IFS
se torna:Que é um teste para saber se é
=
oux
não uma string não vazia, mas nenhuma quantidade de prefixação ajudará³ como[ x$a = x$b ]
ainda será:[ x = x-o x ]
o que causaria um erro e poderia ficar muito pior incluindo DoS e injeção de comandos arbitrários com outros valores como embash
:A solução correta é sempre citar :
Observe que
expr
tem problemas semelhantes (e ainda piores).expr
também tem um=
operador, embora seja para testar se os dois operandos são inteiros iguais quando se parecem com números inteiros decimais, ou ordenam o mesmo quando não.Em muitas implementações,
expr + = +
, ouexpr '(' = ')'
ouexpr index = index
não fazem comparação de igualdade.expr "x$a" = "x$b"
funcionaria em torno disso para comparação de strings, mas prefixar com umx
poderia afetar a classificação (em localidades que têm elementos de agrupamento começando comx
, por exemplo) e obviamente não pode ser usado para comparação de númerosexpr "0$a" = "0$b"
não funciona para comparar inteiros negativos.expr " $a" = " $b"
funciona para comparação de inteiros em algumas implementações, mas não em outras (paraa=01 b=1
, alguns retornariam true, alguns false).¹
ksh93
é uma exceção. Inksh93
,[
pode ser visto como uma palavra reservada em que na[ -t ]
verdade é diferente devar=-t; [ "$var" ]
, ou de""[ -t ]
oucmd='['; "$cmd" -t ]
. Isso é para preservar a compatibilidade com versões anteriores e ainda ser compatível com POSIX nos casos em que isso importa. O-t
só é considerado um operador aqui se for literal eksh93
detecta que você está chamando o[
comando.² ksh adicionou um
[[...]]
operador de expressão condicional com suas próprias regras de análise de sintaxe (e alguns problemas próprios) para resolver isso (também encontrado em alguns outros shells, com algumas diferenças).³ exceto
zsh
onde split+glob não é invocado na expansão do parâmetro, mas a remoção vazia ainda é, ou em outros shells ao desabilitar split+glob globalmente comset -o noglob; IFS=
As pessoas geralmente atribuem o prefixo a problemas com strings vazias, mas essa não é a razão para isso. O problema é muito simples: a expansão da variável pode ser um dos
test
operadores de ', transformando subitamente um teste de igualdade binária em uma expressão diferente.Implementações recentes do comando na maioria das plataformas evitam a armadilha com look-ahead no analisador de expressão, evitando que o analisador reconheça o primeiro operando de um operador binário como algo diferente de um operando, desde que haja tokens suficientes para ser um binário operador claro: