O teste ou [ou [[é mais portátil tanto entre as conchas do bash quanto entre outras conchas?

44

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 em todos os casos negativos e de ponta? Existem diferenças de portabilidade, por exemplo, entre o bash no Ubuntu e no OS X ou versões mais antigas / mais recentes do bash, por exemplo, antes / depois do 4.0 e ambos expandem as expressões da mesma maneira?

Michael Durrant
fonte
2
Para [ … ]vs [[ … ]]vs test …, há respostas mais completas nesta pergunta quase duplicada .
Gilles 'SO- stop be evil'
Para a questão específica do teste de gravabilidade de um arquivo, consulte também Como testar de forma não invasiva o acesso de gravação a um arquivo?
G-Man diz 'Reinstate Monica'
1
Há um ditado: "não há código portátil, apenas código que foi portado". Meu conselho com relação a isso: Use o formulário mais legível (provavelmente [...]) [...] e tente em todas as plataformas que você deseja oferecer suporte. Não há muita utilidade em obscurecer seus scripts, portanto eles são executados em plataformas antigas que nem você nem seu público-alvo usam. Isso dificultará a leitura do código, introduzirá bugs desnecessários e talvez até problemas de segurança (como aconteceu com o openssl).
stefan.schwetschke

Respostas:

31

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

[ -w "/home/durrantm" ] && echo "writable"
Costas
fonte
2
[o POSIX é incorporado ou o Bash?
Sandburg
O @Sandburg POSIX é um padrão, portanto não pode ter built-ins. Ele não define se algo deve ser incorporado ao intérprete ou não, exatamente o que ele deve fazer. Essas regras se aplicam a ambos os formulários teste [similares. Se um shell pode fazer a avaliação por si próprio, isso economiza um processo e o torna um pouco mais rápido, mas o resultado deve ser o mesmo.
Bachsau 20/06
@Bachsau que ainda não responde a pergunta de Sandburg quanto à possibilidade ou não o comportamento de [é definido pela POSIX e, portanto, maximamente portátil (que parece ser o ponto inteiro desta questão, se não me engano)
JamesTheAwesomeDude
@ Sandburg (e para qualquer um que tropeçar nisso): Sim, parece os dois [e test são POSIX . Parece não haver nenhum "recomendado", embora a única diferença (?) Que eu possa identificar entre eles é que test"não reconheça o argumento" - "[como um delimitador indicando o fim das opções]" (? - implicando que " - " é reconhecido como o fim dos argumentos [, pelo qual não posso apresentar um bom caso de teste de qualquer maneira. Mas discordo. Use o que você quiser. OMI [parece elegante, mas é testclaramente um comando)
JamesTheAwesomeDude
35

Sim, existem diferenças. Os mais portáteis são testou [ ]. Ambos fazem parte da especificação POSIXtest .

A if ... ficonstrução também é definida pelo POSIX e deve ser completamente portátil.

O [[ ]]é um kshrecurso que também está presente em algumas versões do bash( todos os modernos ), em zshe talvez em outros, mas não está presente em shou dashou dos outros conchas mais simples.

Então, para fazer seus scripts portátil, usar [ ], testou if ... fi.

terdon
fonte
10
Só para observar, bashe zshhá suporte [[há muito tempo (o bashadicionamos no final dos anos 90, o zshmais tardar em 2000, e eu ficaria surpreso se algum dia tivesse falta de suporte), então é improvável que você encontre uma versão deles [[. Encontrar um shell diferente compatível com POSIX (como dash) é muito mais provável.
chepner
1
Uma característica interessante [[é que as expansões de parâmetros não precisam ser citadas: o [[-f $file]]evento funciona se $filecontiver caracteres de espaço em branco.
helpermethod
2
@helpermethod também, expressões regulares [[ $a =~ ^reg.*exp.*$' ]]
integradas
20

Observe que isso [] && cmdnão é o mesmo que if .. ficonstrução.

Às vezes, seu comportamento é bastante semelhante e você pode usar em [] && cmdvez de if .. fi. Mas apenas algumas vezes. Se você tiver mais de um comando para executar se condição ou precisar de if .. else .. ficuidado e qual 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 lsfalhar, echoserá 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 essa construção atue da mesma maneira

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

observe que o ;eco é importante e deve estar lá.

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

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

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

Portanto, apenas para portabilidade [e [[uso [.

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

pressa
fonte
Provavelmente estou sendo denso, mas não estou vendo qual seria a diferença ... você poderia dar um exemplo em que essas duas construções dariam resultados diferentes?
evilsoup
1
@evilsoup, atualizado. Talvez não seja o melhor explicador, embora espero que fique claro agora.
apressar
1
Muito bons pontos, pressa. Suponho que uma forma segura de seu primeiro exemplo seria: [ -z "$VAR" ] && { ls file; true; } || echo wiiii. É um pouco mais detalhado, mas ainda é mais curto que a if...ficonstrução.
PM 2Ring
Fez a pergunta ser sobre [vs [[vs vs test e não também sobre se ... fi vs && para torná-la UMA pergunta. Infelizmente, fazer isso faz com que essa resposta pareça errada. Desculpas por não ter focado mais a questão inicialmente que levou a isso. Viva e (tente) aprender. :)
Michael Durrant
Eu diminuí a votação porque a) essa é principalmente uma boa resposta, mas é uma resposta para uma pergunta diferente. b) você apresenta [e ifcomo legendas, mas não são. Na verdade, é o &&que está substituindo o if. [executa algum código e retorna um status, muito parecido lse grepfaria. iframifica a execução dependendo do status de retorno do comando (instrução) fornecido após o if, poderia ser qualquer comando (instrução). &&executa a próxima instrução somente se a anterior retornou 0, como uma simples if..then..fi.
GnP
9

Na verdade, &&é isso que está substituindo o if, não o test: uma ifinstrução nos scripts de shell testa se um comando retornou um status de saída "bem-sucedido" (zero); no seu exemplo, o comando é [.

Portanto, existem duas coisas que você varia aqui: 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 strings e arquivos; no seu exemplo, você está executando o comandotest -w /home/durrantm
  • [é um pseudônimo desse comando, igualmente padronizado, que possui um último argumento obrigatório ]para parecer uma expressão entre colchetes; não se deixe enganar, ainda é apenas um comando (você pode até achar que seu sistema possui um arquivo chamado /bin/[)
  • [[é uma versão estendida do comando test incorporada 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 ) executa uma operação AND lógica, avaliando dois comandos e retornando 0 (que representa true) se ambos retornarem 0; ele avaliará apenas o segundo comando se o primeiro retornar zero, para que possa ser usado como uma condição condicional simples
  • O if ... then ... ficonstruto ( padronizado aqui ) usa o mesmo método para julgar a "verdade", mas permite uma lista composta de instruções na thencláusula, em vez do comando único fornecido por um &&curto-circuito, além de fornecer elife elsecláusulas difíceis de escrever usando apenas &&e ||. Observe que não há colchetes ao redor da condição em uma ifinstrução .

Portanto, todos os itens a seguir são igualmente portáteis e totalmente equivalentes para o 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átil devido à natureza não padrão de [[:

  • [[ -w /home/durrantm ]] && echo "writable"
  • if [[ -w /home/durrantm ]]; then echo "writable"; fi
IMSoP
fonte
Sim, removi a parte if .... fi para fazer desta uma pergunta.
Michael Durrant
7

Para portabilidade, use test/ [. Mas se você não precisar da portabilidade, para a sanidade de si mesmo e de outras pessoas que estão lendo seu script, use [[. :)

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

PM 2Ring
fonte
7

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

test -w /home/durrantm && echo writable

é o mais portátil. Trabalha nas conchas dos Bourne cshe nas rcfamílias.

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

saída seria "writable"em vez de writableem conchas da rcfamília ( rc, es, akanga, onde "não é especial).

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

não funcionaria em shells da família cshou rcem sistemas que não possuem um [comando $PATH(alguns são conhecidos por possuir, testmas não o [apelido).

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(de onde se originou) zshe bash(todos os 3 da família Bourne).

Nenhum funcionaria no fishshell onde você precisa:

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

ou:

if [ -w /home/durrantm ]; echo writable; end
Stéphane Chazelas
fonte
0

Minha razão mais importante para escolher if foo; then bar; fiou foo && baré se o status de saída de todo o comando é importante.

comparar:

#!/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 foofalhar (ou for falso, dependendo do seu ponto de vista), no primeiro exemplo o do_baz não será executado, pois o script será encerrado ... O set -ecomando instrui o shell a sair imediatamente se algum comando retornar um status falso. Muito útil se você estiver fazendo coisas como:

cd /some/directory
rm -rf *

Você não deseja que o script continue em execução se cdfalhar por qualquer motivo.

Wurtel
fonte
1
Nenhuma falha foonão abortará o script nos dois casos. Isso é um caso especial para set -e(quando o comando é avaliado como uma condição (para a esquerda, de algum && / || ou se / quando / até / ELSIF ... condições) A falhando. barSairia do shell em ambos os casos.
Stéphane Chazelas
2
Você deseja cd /some/directory && rm -rf -- *ou cd /some/directory || exit; rm -rf -- *(ainda não remove arquivos ocultos). Pessoalmente, não gosto da ideia de usar set -ecomo desculpa para não fazer esforço para escrever o código correto.
Stéphane Chazelas