O teste é [ou [[mais portátil entre os shells do bash e entre os outros shells?

42

Eu vejo que posso fazer

$ [ -w /home/durrantm ] && echo "writable"
writable

ou

$ test -w /home/durrantm && echo "writable"
writable

ou

$ [[ -w /home/durrantm ]] && echo "writable"
writable

Eu gosto de usar a terceira sintaxe. Eles são equivalentes em todos os aspectos e para todos os casos negativos e de borda? Existem diferenças na portabilidade, por ex. entre o bash no Ubuntu e no OS X ou versões mais antigas / bash, por ex. antes / depois de 4.0 e ambos expandem as expressões da mesma maneira?

    
por Michael Durrant 16.11.2014 / 13:40

7 respostas

28

[ é sinônimo do comando test e é simultaneamente um comando bash builtin e separado. Mas [[ é uma palavra-chave bash e funciona apenas em algumas versões. Portanto, por razões de portabilidade, é melhor usar apenas [] ou test

[ -w "/home/durrantm" ] && echo "writable"
    
por 16.11.2014 / 14:02
34

Sim, existem diferenças. Os mais portáteis são test ou [ ] . Estes são ambos parte da especificação POSIX test .

A construção if ... fi também é definida pelo POSIX e deve ser totalmente portátil.

O recurso [[ ]] é um recurso ksh que também está presente em algumas versões do bash ( todos os modernos ), em zsh e talvez em outros mas não está presente em sh ou dash ou os vários outros shells mais simples.

Portanto, para tornar seus scripts portáteis, use [ ] , test de if ... fi .

    
por 16.11.2014 / 14:04
19

Por favor, note que [] && cmd não é o mesmo que if .. fi construção.

Às vezes, seu comportamento é bem parecido e você pode usar [] && cmd em vez de if .. fi . Mas só às vezes. Se você tiver mais de um comando para executar, se condição, ou você precisar de if .. else .. fi , seja cuidadoso e mostre a lógica.

Alguns exemplos:

[ -z "$VAR" ] && ls file || echo wiiii

Não é o mesmo que

if [ -z $VAR ] ; then
  ls file
else
  echo wiii
fi

porque se ls falhar, echo será executado, o que não acontecerá com if .

Outro exemplo:

[ -z "$VAR" ] && ls file && echo wiii

não é o mesmo que

if [ -z "$VAR" ] ; then
   ls file
   echo $wiii
fi

embora esta construção funcione da mesma forma

[ -z "$VAR" ] && { ls file ; echo wiii ; }

observe ; após o eco ser importante e estar presente.

Então, resumindo a declaração acima, podemos dizer

[] && cmd == se o primeiro comando for bem sucedido, então execute o próximo

if .. fi == se condição (que pode ser o comando de teste também) então execute o comando (s)

Portanto, para portabilidade entre [ e [[ use [ apenas.

if é compatível com POSIX. Então, se você tiver que escolher entre [ e if , escolha sua tarefa e comportamento esperado.

    
por 16.11.2014 / 19:24
9

Na verdade, o && que está substituindo a instrução if , não a test : an if no script de shell testa se um comando retornou um status de saída "com êxito" (zero); no seu exemplo, o comando é [ .

Portanto, há duas coisas que você está variando: o comando usado para executar o teste e a sintaxe usada para executar o código com base no resultado desse teste.

Comandos de teste:

  • test é um comando padronizado para avaliar propriedades de cadeias de caracteres e arquivos; no seu exemplo, você está executando o comando test -w /home/durrantm
  • [ é um alias desse comando, igualmente padronizado, que possui um último argumento obrigatório de ] para parecer uma expressão entre colchetes; não se deixe enganar, ainda é apenas um comando (você pode até achar que o seu sistema tem um arquivo chamado /bin/[ )
  • [[ é uma versão estendida do comando de teste embutido em alguns shells, mas não faz parte do mesmo padrão POSIX; inclui opções extras que você não está usando aqui

Expressões condicionais:

  • O operador && ( padronizado aqui ) realiza uma operação lógica AND, avaliando dois comandos e retornando 0 (que representa true) se ambos retornarem 0; ele só avaliará o segundo comando se o primeiro retornar zero, então ele pode ser usado como um simples condicional
  • A if ... then ... fi construct ( padronizada aqui ) usa o mesmo método de avaliação "verdade", mas permite uma lista composta de instruções na cláusula then , em vez do único comando fornecido por um curto-circuito && , e fornece cláusulas elif e else , que são difíceis de gravar usando apenas && e || . Observe que não há colchetes ao redor da condição em uma declaração if .

Portanto, as seguintes são todas renderizações igualmente portáteis e totalmente equivalentes do seu exemplo:

  • test -w /home/durrantm && echo "writable"
  • [ -w /home/durrantm ] && echo "writable"
  • if test -w /home/durrantm; then echo "writable"; fi
  • if [ -w /home/durrantm ]; then echo "writable"; fi

Embora o seguinte também seja equivalente, mas menos portável devido à natureza não padrão de [[ :

  • [[ -w /home/durrantm ]] && echo "writable"
  • if [[ -w /home/durrantm ]]; then echo "writable"; fi
por 16.11.2014 / 20:14
7

Para portabilidade, use test / [ . Mas se você não precisar da portabilidade, por causa da sanidade de si mesmo e dos outros que estiverem lendo seu script, use [[ . :)

Veja também What is the difference between test, [ and [[ ? no BashFAQ .

    
por 16.11.2014 / 14:21
7

Se você quiser portabilidade fora do mundo tipo Bourne, então:

test -w /home/durrantm && echo writable

é o mais portátil. Ele funciona em shells das famílias Bourne, csh e rc .

test -w /home/durrantm && echo "writable"

produziria "writable" em vez de writable em cascas da família rc ( rc , es , akanga , onde " não é especial).

[ -w /home/durrantm ] && echo writable

não funcionaria em shells das famílias csh ou rc em sistemas que não têm um comando [ em $PATH (alguns são conhecidos por terem test , mas não seus [ alias).

if [ -w /home/durrantm ]; then echo writabe; fi

só funciona em conchas da família Bourne.

[[ -w /home/durrantm ]] && echo writable

só funciona em ksh (onde se originou), zsh e bash (todos os 3 da família Bourne).

Nenhum funcionaria no shell fish em que você precisa:

[ -w /home/durrantm ]; and echo writable

ou:

if [ -w /home/durrantm ]; echo writable; end
    
por 17.11.2014 / 14:00
0

Meu motivo mais importante para escolher if foo; then bar; fi ou foo && bar é se o status de saída de todo o comando é importante.

compare:

#!/bin/sh
set -e
foo && bar
do_baz

com:

#!/bin/sh
set -e
if foo; then bar; fi
do_baz

Você pode pensar que eles fazem o mesmo; no entanto, se foo falhar (ou for falso, dependendo do seu ponto de vista), então no primeiro exemplo do_baz não será executado, pois o script terá saído ... O set -e instrui o shell a sair imediatamente se houver comando retorna um status falso. Muito útil se você estiver fazendo coisas como:

cd /some/directory
rm -rf *

Você não deseja que o script continue sendo executado se o cd falhar por qualquer motivo.

    
por 17.11.2014 / 13:35