Os colchetes duplos ] são preferíveis a colchetes no Bash?

Um colega alegou recentemente em uma revisão de código que a [[ ]] construção deve ser preferida em relação a [ ] em construções como

 if [ "`id -nu`" = "$someuser" ] ; then echo "I love you madly, $someuser" fi 

Ele não podia fornecer uma justificativa. Existe um?

[[ tem menos surpresas e geralmente é mais seguro de usar. Mas ele não é portátil – o POSIX não especifica o que ele faz e apenas alguns shells o suportam (ao lado do bash, eu ouvi que o ksh também suporta). Por exemplo, você pode fazer

 [[ -e $b ]] 

para testar se existe um arquivo. Mas com [ , você tem que citar $b , porque ele divide o argumento e expande coisas como "a*" (onde [[ leva literalmente)]. Isso também tem a ver com como [ pode ser um programa externo e recebe seu argumento normalmente como qualquer outro programa (embora também possa ser construído, mas ainda não tem esse tratamento especial).

[[ também tem alguns outros resources interessantes, como a expressão regular combinando com =~ junto com os operadores como eles são conhecidos em linguagens semelhantes ao C. Aqui está uma boa página sobre isso: qual é a diferença entre teste, [ e [[ ? e testes de bash

[[ ]] tem mais resources – sugiro que você dê uma olhada no Advanced Bash Scripting Guide para obter mais informações, especificamente a seção de comando de teste estendido no Capítulo 7. Testes .

Incidentalmente, como aponta o guia, [[ ]] foi introduzido em ksh88 (a versão de 1988 do shell Korn).

Diferenças de comportamento

Primeiro, vamos analisar as diferenças de comportamento entre os dois. Testado no Bash 4.3.11.

  • Extensão POSIX vs Bash:

    • [ é POSIX
    • [[ é uma extensão do Bash
  • comando regular vs magia

    • [ é apenas um comando normal com um nome estranho.

      ] é apenas um argumento de [ que impede que outros argumentos sejam usados.

      O Ubuntu 16.04 na verdade tem um executável para ele em /usr/bin/[ fornecido pelo coreutils, mas a versão integrada do bash tem precedência.

      Nada é alterado na maneira que Bash analisa o comando.

      Em particular, < é redirecionamento, && e || concatenar vários comandos, ( ) gera subshells a menos que escape por \ , e a expansão de palavras acontece como de costume.

    • [[ X ]] é uma construção única que faz X ser analisado magicamente. < , && , || e () são tratados especialmente e as regras de divisão de palavras são diferentes.

      Existem também outras diferenças como = e =~ .

    Em Bashese: [ é um comando interno, e [[ é uma palavra-chave: https://askubuntu.com/questions/445749/whats-the-difference-between-shell-builtin-and-shell-keyword

  • <

    • [[ a < b ]] : comparação lexicográfica
    • [ a \< b ] : O mesmo que acima. \ required ou então faz redirecionamento como para qualquer outro comando. Extensão Bash.
    • Eu não consegui encontrar uma alternativa POSIX para isso, consulte: Como testar strings para menor ou igual?
  • && e ||

    • [[ a = a && b = b ]] : verdadeiro, lógico e
    • [ a = a && b = b ] : erro de syntax, && analisado como um separador de comando AND cmd1 && cmd2
    • [ a = a -ab = b ] : equivalente, mas preterido por POSIX
    • [ a = a ] && [ b = b ] : recomendação POSIX
  • (

    • [[ (a = a || a = b) && a = b ]] : falso
    • [ ( a = a ) ] : erro de syntax, () é interpretado como um subshell
    • [ \( a = a -oa = b \) -aa = b ] : equivalente, mas () é preterido por POSIX
    • ([ a = a ] || [ a = b ]) && [ a = b ] recomendação POSIX
  • divisão de palavras

    • x='a b'; [[ $x = 'ab' ]] x='a b'; [[ $x = 'ab' ]] : true, aspas não são necessárias
    • x='a b'; [ $x = 'ab' ] x='a b'; [ $x = 'ab' ] : erro de syntax, expande para [ ab = 'ab' ]
    • x='a b'; [ "$x" = 'ab' ] x='a b'; [ "$x" = 'ab' ] : equivalente
  • =

    • [[ ab = a? ]] [[ ab = a? ]] : true, porque faz correspondência de padrões ( * ? [ são mágicas). Não glob expandir para arquivos no diretório atual.
    • [ ab = a? ] [ ab = a? ] : a? glob expande. Então, pode ser verdadeiro ou falso, dependendo dos arquivos no diretório atual.
    • [ ab = a\? ] [ ab = a\? ] : falso, não expansão glob
    • = e == são os mesmos em ambos [ e [[ , mas == é uma extensão Bash.
    • printf 'ab' | grep -Eq 'a.' : Equivalente POSIX ERE
    • [[ ab =~ 'ab?' ]] [[ ab =~ 'ab?' ]] : false, perde magia com ''
    • [[ ab? =~ 'ab?' ]] [[ ab? =~ 'ab?' ]] : true
  • =~

    • [[ ab =~ ab? ]] [[ ab =~ ab? ]] : true, correspondência de expressão regular estendida POSIX ? não glob expandir
    • [ a =~ a ] : erro de syntax
    • printf 'ab' | grep -Eq 'ab?' : Equivalente a POSIX

Recomendação

Eu prefiro sempre usar [] .

Existem equivalentes POSIX para cada construção [[ ]] que vi.

Se você usa [[ ]] você:

  • perder a portabilidade
  • forçar o leitor a aprender as complexidades de outra extensão bash. [ é apenas um comando regular com um nome estranho, sem uma semântica especial envolvida.

De qual comparador, teste, suporte ou suporte duplo é o mais rápido? ( http://bashcurescancer.com )

O colchete duplo é um “comando composto”, no qual o teste e o colchete único são internos do shell (e, na verdade, são o mesmo comando). Assim, o suporte único e o suporte duplo executam código diferente.

O suporte de teste e único são os mais portáteis, pois existem como comandos separados e externos. No entanto, se você estiver usando qualquer versão remotamente moderna do BASH, o suporte duplo é suportado.

Uma situação típica em que você não pode usar [[está em um script autotools configure.ac, há colchetes com um significado especial e diferente, então você terá que usar o test invés de [ou [[- Note que test e [são os mesmos programa.

[[]] colchetes duplos não são portados sob determinada versão do SunOS e totalmente não-portados dentro de declarações de function por: GNU bash, versão 2.02.0 (1) -release (sparc-sun-solaris2.6)

Em suma, é melhor porque não bifurca outro processo. Nenhum parênteses ou um único parêntese é mais lento do que um parêntese duplo, pois ele bifurca outro processo.