bash: [: missing ']' quando o teste é armazenado na variável [duplicate]

1

Por que isso funciona:

[ -r /tmp ] && echo "tt" >/tmp/taa
cat taa
tt

Mas o seguinte não funciona e como corrigir isso, preservando a variável para reusabilidade?

COMD='[ -r /tmp ] && echo "tt"'
$COMD >/tmp/taa
bash: [: missing ']

Obrigado

Editar: eval funciona de fato. No entanto, não entendo por que, e na pergunta, que a minha foi marcada como uma duplicata de , não há explicação também. Além disso, se a parte que começa com && for removida, ela funciona e até mesmo retorna resultados corretos sem eval :

COMD='[ -r /tmp ]'
$COMD
echo $?
0

COMD='[ -r /tmpdddd ]'
$COMD
echo $?
1
    
por igorepst 07.06.2018 / 21:05

2 respostas

3

Você não esperaria:

var='echo test && reboot'
echo $var

Para produzir test e depois reiniciar, você faria?

Em vez disso, gera echo test && reboot

Isso é o mesmo para

var='echo test && reboot'
$var

Agora, onde se torna confuso é que na maioria dos shells parecidos com o Bourne, ele não falha com um erro que diz que o comando echo test && reboot não foi encontrado. Em vez disso, e a menos que você tenha modificado $IFS , ele gerará test && reboot .

Isso porque, nesses shells, deixar uma variável sem aspas aplica algum tipo de operador split + glob.

Como $var não é citado, ele é dividido em palavras (de acordo com o parâmetro $IFS special): echo , test , && , reboot e o comando a ser executado é derivado do primeiro ( echo ) e todas as palavras passadas como argumentos para esse comando. && é um argumento para echo lá. Apenas um literal && sem aspas será entendido como o operador && da linguagem sh . Como em C, var="foo, bar"; printf(var) não significa que dois argumentos são passados para printf apenas porque var contém , , que é um operador na sintaxe da linguagem C.

Na verdade, se voltarmos na história para as primeiras versões do Unix no início dos anos 70, o sh (hoje chamado de Thompson shell ) teria rodado reboot lá . Bem, sh naquela época não suportava variáveis, mas tinha parâmetros posicionais.

Aqui usando o Unix V1 em um emulador PDP11:

$ cat myscript
echo $1
$ sh myscript 'echo test; reboot'
echo test
No command
$ ls -l /bin/sh
total   12
 88 lxrwr-  1 sys    5312 Jan  1 00:00:00 sh

(felizmente, não havia nenhum comando reboot , portanto, o erro No command ).

Isso pode parecer loucura pelo padrão atual, mas lembre-se de que o Unix V1 era executado em máquinas com algumas centenas de kilobytes de memória e megabytes de armazenamento, então as coisas tinham que ser mantidas simples na época. Veja como sh é significativamente menor que /bin/true (o comando que não faz nada) em um sistema moderno (27KB no meu sistema (sem contar o vinculador dinâmico ou as bibliotecas dinâmicas com as quais ele está vinculado) contra 5KB para esse sh).

O Unix V7 no final dos anos 70 veio com o shell Bourne, o novo sh . Muito mais avançado, mas ainda não quebrou completamente a compatibilidade com o seu antecessor. O mesmo para o csh que ocorreu mais ou menos na mesma hora no BSD. É provavelmente a principal razão para esse comportamento desajeitado da shell Bourne: a expansão dos parâmetros ainda sofre algumas formas de expansão, como a divisão em palavras e também globbing (mas não tanto quanto a reinterpretação da linguagem sh shish como no início do Unix shells como isso seria loucura), por compatibilidade retroativa com as conchas Thomson.

Alguns shells como rc , fish ou zsh (aquele que é um shell parecido com o Bourne) acabaram sendo corrigidos posteriormente, mas muitos incluindo bash mantiveram compatibilidade retroativa com o shell Bourne.

Agora, se você quiser interpretar uma string como código shell, é para isso que serve o comando eval :

eval "$var"

(observe as aspas para evitar o split + globbing em shells parecidos com Bourne).

Mas se você quiser armazenar código em uma variável, faria muito mais sentido usar uma função como em outros idiomas:

f() { echo test && reboot; }
    
por 07.06.2018 / 23:30
3

Porque o operador && não é reconhecido após a expansão da variável. As cotações também não são: se isso funcionasse, imprimiria "tt" . (Tente: cmd='echo "tt"'; $cmd )

Crie uma função:

checktmp() {
    [ -r /tmp ] && echo "tt"
}
checktmp

As funções são para código, variáveis para dados.

    
por 07.06.2018 / 21:43