É preferível colchetes duplos [[]] em vez de colchetes simples [] no Bash?

574

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

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

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

Leonard
fonte
19
Seja flexível, às vezes permita-se ouvir um conselho sem exigir uma explicação detalhada :) Quanto a [[isso, o código é bom e claro, mas lembre-se daquele dia em que você portará seu scriptworks no sistema com o shell padrão que não é bashou ksh, etc. [é mais feio, pesado, mas funciona como AK-47em qualquer situação.
rook
178
@rook Você pode ouvir um conselho sem uma explicação profunda, com certeza. Mas quando você solicita uma explicação e não a obtém, geralmente é uma bandeira vermelha. "Confie, mas verifique" e tudo isso.
Josip Rodin
6
Veja também: Qual é a diferença entre os operadores Bash [[vs [vs (vs ((? Em Unix e Linux SE.
codeforester
1
@rook em outras palavras "faça o que você mandou e não faça perguntas"
glifo
@rook Isso não fazia sentido.
Rakib

Respostas:

608

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

[[ -e $b ]]

para testar se existe um arquivo. Mas com [, você tem que citar $b, porque divide o argumento e expande coisas como "a*"(onde o [[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 um componente interno, mas ainda não tenha esse tratamento especial).

[[também possui outros recursos interessantes, como correspondência de expressões regulares =~e operadores, como eles são conhecidos em idiomas do tipo C. Aqui está uma boa página: Qual é a diferença entre test [e [[? e testes de bash

Johannes Schaub - litb
fonte
21
Considerando que o bash está em toda parte nos dias de hoje, acho que é bastante portátil. A única exceção comum para mim é nas plataformas de busybox - mas você provavelmente desejaria fazer um esforço especial de qualquer maneira, dado o hardware em que é executado.
armas
14
@ armas: De fato. Eu sugiro que sua segunda frase refute a sua primeira; se você considerar o desenvolvimento de software como um todo, "o bash está em toda parte" e "a exceção são as plataformas busybox" são completamente incompatíveis. O busybox é generalizado para o desenvolvimento incorporado.
Corridas de leveza em órbita
11
@ armas: Mesmo que o bash esteja instalado na minha caixa, ele pode não estar executando o script (eu posso ter / bin / sh associado a alguma variante de traço, como é padrão no FreeBSD e Ubuntu). Também há estranheza adicional no Busybox: atualmente, com opções de compilação padrão, ele analisa, [[ ]]mas interpreta como o mesmo significado [ ].
dubiousjim
59
@dubiousjim: Se você usa construções somente bash (como esta), deve ter #! / bin / bash, não #! / bin / sh.
Nick Matteo
16
É por isso que faço meus scripts usarem, #!/bin/shmas depois os alterno para uso #!/bin/bashassim que confio em algum recurso específico do BASH, para indicar que ele não é mais portátil do shell Bourne.
Anthony
151

Diferenças de comportamento

Algumas diferenças no Bash 4.3.11:

  • Extensão POSIX vs Bash:

  • comando regular vs magia

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

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

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

      Nada é alterado na maneira como Bash analisa o comando.

      Em particular, <é o redirecionamento &&e ||concatena vários comandos, ( )gera sub-conchas, a menos que seja escapado \, e a expansão de palavras acontece normalmente.

    • [[ X ]]é uma construção única que faz com que Xseja analisado magicamente. <, &&, ||E ()são tratados de maneira especial, e regras de decomposição palavra são diferentes.

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

      No Bashese: [é um comando interno e [[é uma palavra-chave: /ubuntu/445749/whats-the-difference-between-shell-builtin-and-shell-keyword

  • <

  • && e ||

    • [[ a = a && b = b ]]: verdadeiro, lógico e
    • [ a = a && b = b ]: erro de sintaxe, &&analisado como um separador de comandos ANDcmd1 && cmd2
    • [ a = a -a b = b ]: equivalente, mas reprovado pelo POSIX³
    • [ a = a ] && [ b = b ]: POSIX e equivalente confiável
  • (

    • [[ (a = a || a = b) && a = b ]]: false
    • [ ( a = a ) ]: erro de sintaxe, ()é interpretado como um subshell
    • [ \( a = a -o a = b \) -a a = b ]: equivalente, mas ()foi descontinuado pelo POSIX
    • { [ a = a ] || [ a = b ]; } && [ a = b ] Equivalente ao POSIX⁵
  • divisão de palavras e geração de nome de arquivo após expansões (divisão + glob)

    • x='a b'; [[ $x = 'a b' ]]: true, aspas não necessárias
    • x='a b'; [ $x = 'a b' ]: erro de sintaxe, expande para [ a b = 'a b' ]
    • x='*'; [ $x = 'a b' ]: erro de sintaxe se houver mais de um arquivo no diretório atual.
    • x='a b'; [ "$x" = 'a b' ]: Equivalente POSIX
  • =

    • [[ ab = a? ]]: true, porque faz a correspondência de padrões ( * ? [são mágicos). Não é expandido para arquivos no diretório atual.
    • [ ab = a? ]: a?glob se expande. Portanto, pode ser verdadeiro ou falso, dependendo dos arquivos no diretório atual.
    • [ ab = a\? ]: expansão falsa, não global
    • =e ==são iguais em ambos [e [[, mas ==é uma extensão do Bash.
    • case ab in (a?) echo match; esac: Equivalente POSIX
    • [[ ab =~ 'ab?' ]]: false⁴, perde mágica com ''
    • [[ ab? =~ 'ab?' ]]: verdade
  • =~

    • [[ ab =~ ab? ]]: true, correspondência de expressão regular estendida POSIX , ?não expande glob
    • [ a =~ a ]: erro de sintaxe. Nenhum equivalente do bash.
    • printf 'ab\n' | grep -Eq 'ab?': Equivalente ao POSIX (apenas dados de linha única)
    • awk 'BEGIN{exit !(ARGV[1] ~ ARGV[2])}' ab 'ab?': Equivalente POSIX.

Recomendação : use sempre [].

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

Se você usa [[ ]]você:

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

¹ Inspirado na [[...]]construção equivalente no shell Korn

² mas falha em alguns valores de aou b(como +ou index) e faz comparação numérica se ae se bparecem com números inteiros decimais. expr "x$a" '<' "x$b"trabalha em torno de ambos.

³ e também falha para alguns valores de aou bcomo !ou (.

Bas no bash 3.2 e superior e a compatibilidade fornecida com o bash 3.1 não está ativada (como com BASH_COMPAT=3.1)

⁵ embora o agrupamento (aqui com o {...;}grupo de comandos em vez de (...)executar um subshell desnecessário) não seja necessário, pois os operadores ||e &&shell (em oposição aos operadores ||e && [[...]]ou -o/ -a [operadores) têm igual precedência. Então [ a = a ] || [ a = b ] && [ a = b ]seria equivalente.

Ciro Santilli adicionou uma nova foto
fonte
4
Boas explicações. Eu uso [pelos mesmos motivos.
Gordon
2
Em Bashese :) ha. Bom esclarecimento das distinções e razões para se manter com o POSIX.
Jonathan Komar
56

[[ ]]possui mais recursos - 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 .

Aliás, como o guia observa, [[ ]]foi introduzido no ksh88 (a versão de 1988 do shell Korn).

Nils von Barth
fonte
5
Isso está longe de ser um basismo, foi introduzido pela primeira vez no shell Korn.
Henk Langeveld
6
@ Thomas, o ABS é realmente considerado uma referência muito ruim em muitos círculos; embora tenha muitas informações precisas, tende a tomar muito pouco cuidado para não mostrar más práticas em seus exemplos e passou grande parte de sua vida sem manutenção.
Charles Duffy
@CharlesDuffy obrigado pelo seu comentário, você pode citar uma boa alternativa. Não sou especialista em scripts de shell, estou procurando um guia que possa consultar para escrever um script a cada semestre.
Thomas
9
@ Thomas, o Wooledge BashFAQ e o wiki associado são o que eu uso; eles são ativamente mantidos pelos habitantes do canal #bash Freenode (que, embora às vezes espinhosos, tendem a se importar profundamente com a correção). BashFAQ # 31, mywiki.wooledge.org/BashFAQ/031 , é a página diretamente relevante para esta pergunta.
Charles Duffy
13

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

O colchete duplo é um “comando composto” onde, como teste e o colchete único, estão embutidos no shell (e na realidade são o mesmo comando). Assim, o colchete simples e duplo executam códigos diferentes.

O teste e o suporte ú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 será suportado.

f3lix
fonte
7
O que há com a obsessão dos scripts shell mais rápidos ? Eu o quero mais portátil e não me importo com a melhoria que isso [[possa trazer. Mas então, eu sou um peido velho da velha escola :-)
Jens
1
Eu acho que muitos shells provam versões internas [e test, embora existam versões externas.
dubiousjim
4
@ Jens em geral, eu concordo: todo o objetivo dos scripts é (era?) Portabilidade (caso contrário, codificaríamos e compilaríamos, não scripts) ... as duas exceções em que consigo pensar são: (1) conclusão da guia (onde scripts de conclusão podem ficar muito longos com muita lógica condicional); e (2) super-prompts ( PS1=...crazy stuff...e / ou $PROMPT_COMMAND); para estes, não quero nenhum atraso perceptível na execução do script.
Michael
1
Parte da obsessão com o mais rápido é simplesmente estilo. Tudo o mais é igual, por que não incorporar a construção de código mais eficiente ao seu estilo padrão, especialmente se essa construção também oferece mais legibilidade? Quanto à portabilidade, muitas tarefas para as quais o bash é adequado são inerentemente não portáveis. Por exemplo, eu preciso executar apt-get updatese já faz mais de X horas desde a última execução. É um grande alívio quando se pode deixar a portabilidade fora da lista já muito longa de restrições de código.
Ron Burk
5

Se você gosta de seguir o guia de estilo do Google :

Teste [e[[

[[ ... ]]reduz erros, pois não ocorre expansão de nome de caminho ou divisão de palavras entre [[e ]], e [[ ... ]]permite a correspondência de expressões regulares onde [ ... ]não.

# This ensures the string on the left is made up of characters in the
# alnum character class followed by the string name.
# Note that the RHS should not be quoted here.
# For the gory details, see
# E14 at https://tiswww.case.edu/php/chet/bash/FAQ
if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
  echo "Match"
fi

# This matches the exact pattern "f*" (Does not match in this case)
if [[ "filename" == "f*" ]]; then
  echo "Match"
fi

# This gives a "too many arguments" error as f* is expanded to the
# contents of the current directory
if [ "filename" == f* ]; then
  echo "Match"
fi
crizCraig
fonte
1
O Google escreveu " nenhuma expansão de nome de caminho ... ocorre ", mas [[ -d ~ ]]retorna verdadeiro (o que implica que ~foi expandido para /home/user). Eu acho que o Google deveria ter sido mais preciso na sua escrita.
JamesThomasMoon1979
2

Uma situação típica em que você não pode usar [[está no script autotools configure.ac; os colchetes têm um significado especial e diferente; portanto, você precisará usar em testvez de [ou [[- Observe esse teste e [é o mesmo programa.

Vicente Bolea
fonte
2
Dado que as ferramentas automáticas não são um shell POSIX, por que você esperaria [ser definido como uma função de shell POSIX?
Gordon
Como o script autoconf se parece com um script shell e produz um script shell, a maioria dos comandos shell opera dentro dele.
vy32
-1

[[]] colchetes duplos não são suportados em determinadas versões do SunOS e declarações de funções internas totalmente sem suporte por: GNU bash, versão 2.02.0 (1) -release (sparc-sun-solaris2.6)

Carniceiro
fonte
1
muito verdadeiro e nada inconseqüente. a portabilidade do bash nas versões mais antigas deve ser considerada. As pessoas dizem que "o bash é onipresente e portátil, exceto talvez (insira o SO esotérico aqui) " - mas, na minha experiência, o solaris é uma daquelas plataformas em que deve ser dada atenção especial à portabilidade: não apenas para considerar versões mais antigas do bash em um sistema operacional mais recente, problemas / erros com matrizes, funções, etc; mas mesmo utilitários (usados ​​nos scripts) como tr, sed, awk, tar têm peculiaridades e peculiaridades no solaris que você precisa contornar.
Michael
2
você está tão certo ... muitos utilitários que não são POSIX no Solaris, basta olhar para a saída "df" e argumentos ... Que vergonha! Espero que esteja desaparecendo aos poucos (exceto no Canadá).
quer
7
Solaris 2.6 a sério? Foi lançado em 1997 e encerrou o suporte em 2006. Acho que se você ainda está usando isso, tem outros problemas !. Aliás, ele usava o Bash v2.02, que introduzia colchetes duplos, por isso deveria funcionar mesmo em algo tão antigo quanto isso. O Solaris 10 de 2005 usou o Bash 3.2.51 e o Solaris 11 de 2011 usa o Bash 4.1.11.
Peterh 25/10
1
Portabilidade do script re. Nos sistemas Linux, geralmente há apenas uma edição de cada ferramenta e essa é a edição GNU. No Solaris, você normalmente pode escolher entre uma edição nativa do Solaris ou a edição GNU (por exemplo, tar Solaris vs tar GNU). Se você depende de extensões específicas do GNU, deve declarar isso em seu script para que seja portátil. No Solaris, você faz isso prefixando com "g", por exemplo, `ggrep` se desejar o GNU grep.
Peterh 25/10
-2

Em poucas palavras, [[é melhor porque não bifurca outro processo. Nenhum suporte ou um suporte único é mais lento que um suporte duplo, porque bifurca outro processo.

unix4linux
fonte
8
Test e [são nomes para o mesmo comando interno no bash. Tente usar type [para ver isso.
AB
1
@ alberge, isso é verdade, mas [[, diferentemente de [, é sintaxe interpretada pelo interpretador de linha de comando bash. No bash, tente digitar type [[. O unix4linux está correto que, embora os [testes clássicos do Bourne-shell iniciem um novo processo para determinar o valor verdadeiro, a [[sintaxe (emprestada do ksh pelo bash, zsh, etc.) não.
Tim Gilbert
2
@ Tim, não tenho certeza de qual shell Bourne você está falando, mas [está embutido no Bash e no Dash ( /bin/shem todas as distribuições Linux derivadas do Debian).
AB
1
Oh, entendo o que você quer dizer, é verdade. Eu estava pensando em algo como, digamos, / bin / sh em sistemas Solaris ou HP / UX mais antigos, mas é claro que se você precisasse ser compatível com aqueles que não usaria [[também.
Tim Gilbert
1
alberge Bourne shell não é Bash (também conhecido como Bourne Again SHell ).
kiamlaluno