Qual é a diferença entre os operadores do Bash [[vs [vs (vs ((?

246

Estou um pouco confuso sobre o que esses operadores fazem de maneira diferente quando usados ​​no bash (colchetes, colchetes duplos, parênteses e parênteses duplos).

[[ , [ , ( , ((

Já vi pessoas usá-las em declarações if como esta:

if [[condition]]

if [condition]

if ((condition))

if (condition)
RetroCode
fonte
4
Você pode querer ver unix.stackexchange.com/questions/tagged/test primeiro
cuonglm
3
@cuonglm Ironic porque esse link gera essa pergunta como o primeiro resultado. Paradoxo!
Insano
5
Suponho que a leitura da documentação não seja uma opção?
Corridas da Luz em Órbita
34
Parênteses e colchetes não são tão fáceis de procurar na documentação, e isso é tudo o que você tem se não souber o nome desses recursos.
Ilkkachu

Respostas:

258

Uma ifdeclaração normalmente se parece com

if commands1
then
   commands2
else
   commands3
fi

A thencláusula é executada se o código de saída commands1for zero. Se o código de saída for diferente de zero, a elsecláusula será executada. commands1pode ser simples ou complexo. Ele pode, por exemplo, ser uma sequência de uma ou mais condutas separadas por um dos operadores ;, &, &&, ou ||. As ifcondições mostradas abaixo são apenas casos especiais de commands1:

  1. if [ condition ]

    Este é o testcomando tradicional do shell . Está disponível em todos os shells POSIX. O comando test define um código de saída e a ifinstrução age de acordo. Testes típicos são se existe um arquivo ou se um número é igual a outro.

  2. if [[ condition ]]

    Esta é uma nova variação atualizada testdo ksh que o bash e o zsh também suportam. Este testcomando também define um código de saída e a ifinstrução age de acordo. Entre seus recursos estendidos, ele pode testar se uma string corresponde a uma expressão regular.

  3. if ((condition))

    Outra extensão ksh que o bash e o zsh também suportam. Isso executa aritmética. Como resultado da aritmética, um código de saída é definido e a ifinstrução age de acordo. Retorna um código de saída zero (verdadeiro) se o resultado do cálculo aritmético for diferente de zero. Assim [[...]], este formulário não é POSIX e, portanto, não é portátil.

  4. if (command)

    Isso executa o comando em um subshell. Quando o comando é concluído, ele define um código de saída e a ifinstrução age de acordo.

    Um motivo comum para usar um subshell como este é para limitar efeitos colaterais de commandse commandnecessário atribuições de variáveis ou outras alterações no ambiente do shell. Essas alterações não permanecem após a conclusão do subshell.

  5. if command

    O comando é executado e a ifinstrução age de acordo com seu código de saída.

John1024
fonte
24
Obrigado por incluir a quinta opção. Isso é essencial para entender como isso realmente funciona e é surpreendentemente subutilizado.
pintainhos
4
Observe que, [na verdade, é um comando ou símbolo binário, não interno. Geralmente vive em /bin.
Julien R.
8
@JulienR. na verdade, [é construído como está test. Existem versões binárias disponíveis por motivos de compatibilidade. Confira help [e help test.
OldTimer 20/09/16
4
Vale notar que, enquanto ((is not POSIX, $((ou seja, a expansão aritmética é e é fácil confundi-los Muitas vezes, uma solução é usar algo como. [ $((2+2)) -eq 4 ]A fazer uso de aritmética em declarações conditinal
Sergiy Kolodyazhnyy
1
Eu gostaria de poder votar novamente esta resposta mais de uma vez. Explicação perfeita.
Anthony Gatlin
77
  • (…)parênteses indicam um subshell . O que há dentro deles não é uma expressão como em muitos outros idiomas. É uma lista de comandos (assim como parênteses externos). Esses comandos são executados em um subprocesso separado, portanto, qualquer redirecionamento, atribuição, etc. realizado dentro dos parênteses não tem efeito fora dos parênteses.
    • Com um cifrão à esquerda, $(…)é uma substituição de comando : há um comando entre parênteses e a saída do comando é usada como parte da linha de comando (após expansões extras, a menos que a substituição seja entre aspas duplas, mas isso é outra história ) .
  • { … }chaves são como parênteses, pois agrupam comandos, mas apenas influenciam a análise, não o agrupamento. O programa x=2; { x=4; }; echo $ximprime 4, enquanto que x=2; (x=4); echo $ximprime 2. (Também chaves entre palavras-chave precisam ser delimitadas e encontradas na posição de comando (daí o espaço depois {e o ;anterior }), enquanto os parênteses não. Isso é apenas uma peculiaridade de sintaxe.)
    • Com um sinal de dólar à esquerda, ${VAR}é uma expansão de parâmetro , expandindo para o valor de uma variável, com possíveis transformações extras. O ksh93shell também suporta ${ cmd;}como forma de substituição de comando que não gera um subshell.
  • ((…))parênteses duplos cercam uma instrução aritmética , isto é, uma computação em números inteiros, com uma sintaxe semelhante a outras linguagens de programação. Essa sintaxe é usada principalmente para atribuições e em condicionais. Isso existe apenas no ksh / bash / zsh, não no sh simples.
    • A mesma sintaxe é usada em expressões aritméticas $((…)), que se expandem para o valor inteiro da expressão.
  • [ … ]colchetes simples cercam expressões condicionais . Expressões condicionais são construídas principalmente em operadores , como -n "$variable"para testar se uma variável está vazia e -e "$file"para testar se existe um arquivo. Observe que você precisa de um espaço ao redor de cada operador (por exemplo [ "$x" = "$y" ], não [ "$x"="$y" ]), e um espaço ou um caractere como ;dentro e fora dos colchetes (por exemplo [ -n "$foo" ], não [-n "$foo"]).
  • [[ … ]]colchetes duplos são uma forma alternativa de expressões condicionais no ksh / bash / zsh com alguns recursos adicionais, por exemplo, você pode escrever [[ -L $file && -f $file ]]para testar se um arquivo é um link simbólico para um arquivo regular, ao passo que colchetes simples exigem [ -L "$file" ] && [ -f "$file" ]. Consulte Por que a expansão de parâmetros com espaços sem aspas funciona entre colchetes duplos [[mas não entre colchetes únicos]? para saber mais sobre esse assunto.

No shell, todo comando é um comando condicional: todo comando tem um status de retorno que é 0, indicando sucesso ou um número inteiro entre 1 e 255 (e potencialmente mais em alguns shells), indicando falha. O [ … ]comando (ou [[ … ]]formulário de sintaxe) é um comando específico que também pode ser escrito test …e é bem-sucedido quando um arquivo existe, ou quando uma string está vazia, ou quando um número é menor que outro, etc. O ((…))formulário de sintaxe é bem-sucedido quando um número é diferente de zero. Aqui estão alguns exemplos de condicionais em um script de shell:

  • Teste se myfilecontém a sequência hello:

    if grep -q hello myfile; then 
  • Se mydirfor um diretório, mude para ele e faça o seguinte:

    if cd mydir; then
      echo "Creating mydir/myfile"
      echo 'some content' >myfile
    else
      echo >&2 "Fatal error. This script requires mydir to exist."
    fi
    
  • Teste se existe um arquivo chamado myfileno diretório atual:

    if [ -e myfile ]; then 
  • O mesmo, mas também incluindo links simbólicos pendentes:

    if [ -e myfile ] || [ -L myfile ]; then 
  • Teste se o valor de x(que é assumido como numérico) é pelo menos 2, portably:

    if [ "$x" -ge 2 ]; then 
  • Teste se o valor de x(que é assumido como numérico) é pelo menos 2, em bash / ksh / zsh:

    if ((x >= 2)); then 
Gilles
fonte
Note que suporte único suporta a -avez de &&, por isso, pode-se escrever: [ -L $file -a -f $file ], que é o mesmo número de caracteres dentro dos colchetes, sem extra [e ]...
Alexis Wilke
6
@AlexisWilke Os operadores -ae -osão problemáticos porque podem levar a análises incorretas se alguns dos operandos envolvidos parecerem operadores. É por isso que não os mencionei: eles têm vantagem zero e nem sempre funcionam. E nunca escreva expansões de variáveis ​​não citadas sem uma boa razão: tudo [[ -L $file -a -f $file ]]bem, mas com colchetes simples você precisa [ -L "$file" -a -f "$file" ](o que é bom, por exemplo, se $filesempre começa com /ou ./).
Gilles
Observe que é [[ -L $file && -f $file ]](não -acom a [[...]]variante).
Stéphane Chazelas
18

Na documentação do bash :

(list)A lista é executada em um ambiente subshell (consulte AMBIENTE DE EXECUÇÃO DE COMANDO abaixo). As atribuições variáveis ​​e os comandos internos que afetam o ambiente do shell não permanecem em vigor após a conclusão do comando. O status de retorno é o status de saída da lista.

Em outras palavras, verifique se o que acontece na 'lista' (como a cd) não tem efeito fora do (e ). A única coisa que vai vazar é o código de saída do último comando ou com set -eo primeiro comando que gera um erro (excepto alguns, como if, while, etc.)

((expression))A expressão é avaliada de acordo com as regras descritas abaixo em AVALIAÇÃO ARITMÉTICA. Se o valor da expressão for diferente de zero, o status de retorno será 0; caso contrário, o status de retorno é 1. Isso é exatamente equivalente a deixar "expressão".

Esta é uma extensão do bash, permitindo que você faça contas. É um pouco semelhante ao uso exprsem todas as limitações de expr(como ter espaços em todos os lugares, escapar *etc.)

[[ expression ]]Retorne um status de 0 ou 1, dependendo da avaliação da expressão da expressão condicional. As expressões são compostas das primárias descritas abaixo em EXPRESSÕES CONDICIONAIS. A divisão de palavras e a expansão do nome do caminho não são executadas nas palavras entre os [[e]]; expansão de til, expansão de parâmetro e variável, expansão aritmética, substituição de comando, substituição de processo e remoção de cotação. Operadores condicionais como -f devem estar sem aspas para serem reconhecidos como primários.

Quando usados ​​com [[, os operadores <e> classificam lexicograficamente usando o código do idioma atual.

Isso oferece um teste avançado para comparar seqüências de caracteres, números e arquivos, um pouco como testofertas, mas mais poderoso.

[ expr ]Retorne um status de 0 (verdadeiro) ou 1 (falso), dependendo da avaliação da expressão condicional expr. Cada operador e oper e deve ser um argumento separado. As expressões são compostas das primárias descritas acima em EXPRESSÕES CONDICIONAIS. test não aceita nenhuma opção, nem aceita e ignora um argumento de - como significando o fim das opções.

[...]

Este chama test. Na verdade, antigamente, [havia um link simbólico para test. Funciona da mesma maneira e você tem as mesmas limitações. Como um binário sabe o nome com o qual foi iniciado, o programa de teste pode analisar parâmetros até encontrar um parâmetro ]. Truques divertidos do Unix.

Observe que, no caso de bash, [e testsão funções internas (como mencionado em um comentário), ainda assim as mesmas limitações se aplicam.

Alexis Wilke
fonte
1
Embora teste, [é claro, sejam comandos integrados no Bash, mas é provável que também exista um binário externo.
Ilkkachu
1
O binário externo para [não é um link simbólico para testa maioria dos sistemas modernos.
precisa saber é o seguinte
1
De alguma forma, acho divertido que eles se preocupem em criar dois binários separados, os quais têm exatamente o que precisam, em vez de apenas combiná-los e adicionar alguns condicionais. Embora na verdade strings /usr/bin/testmostre que também tem o texto de ajuda, não sei o que dizer.
Ilkkachu
2
@ Random832 Entendo o argumento da GNU para evitar um comportamento inesperado do arg0, mas sobre os requisitos do POSIX, eu não seria tão afirmativo. Embora o testcomando deva obviamente existir como um comando independente baseado em arquivo pelo padrão, nada nele declara que sua [variante precisa ser implementada dessa maneira também. Por exemplo, Solaris 11 não fornece qualquer [executável, mas é, no entanto, totalmente compatível com os padrões POSIX
jlliagre
2
(saída 1) tem um efeito fora dos parênteses.
Alexander Alexander
14

[ vs [[

Esta resposta cobrirá o subconjunto [vs [[da pergunta.

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, possui 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: https://askubuntu.com/questions/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 ]POSIX equivalente 5
  • 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?' ]]: falso 4 , perde mágica com''
    • [[ ab? =~ 'ab?' ]]: verdadeiro
  • =~

    • [[ 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 para 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 (.

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

5 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
@ StéphaneChazelas obrigado pela informação! Eu adicionei exprà resposta. O termo "extensão Bash" não significa que o Bash foi o primeiro shell a adicionar alguma sintaxe, já que aprender POSIX sh vs Bash já é suficiente para me deixar louco.
Ciro Santilli adicionou
Veja man testse você tentou man [e se perdeu. Isso explicará a variante POSIX.
Jonathan Komar
13

Alguns exemplos:

Teste tradicional:

foo="some thing"
# check if value of foo is not empty
if [ -n "$foo" ] ; then... 
if test -n "$foo" ; then... 

teste [são comandos como qualquer outro, portanto, a variável é dividida em palavras, a menos que esteja entre aspas.

Teste de novo estilo

[[ ... ]] é uma construção de shell especial (mais recente), que funciona de maneira um pouco diferente, a coisa mais óbvia é que ela não divide variáveis ​​de palavras:

if [[ -n $foo ]] ; then... 

Alguma documentação aqui [e [[aqui .

Teste aritmético:

foo=12 bar=3
if (( $foo + $bar == 15 )) ; then ...  

Comandos "normais":

Todas as opções acima agem como comandos normais e ifpodem executar qualquer comando:

# grep returns true if it finds something
if grep pattern file ; then ...

Múltiplos comandos:

Ou podemos usar vários comandos. O agrupamento de um conjunto de comandos os ( ... )executa no subshell, criando uma cópia temporária do estado do shell (diretório de trabalho, variáveis). Se precisarmos executar algum programa temporariamente em outro diretório:

# this will move to $somedir only for the duration of the subshell 
if ( cd $somedir ; some_test ) ; then ...

# while here, the rest of the script will see the new working
# directory, even after the test
if cd $somedir ; some_test ; then ...
ilkkachu
fonte
1

Comandos de agrupamento

O Bash fornece duas maneiras de agrupar uma lista de comandos a serem executados como uma unidade.

( list )Colocar uma lista de comandos entre parênteses causa a criação de um ambiente de subshell, e cada um dos comandos na lista é executado nesse subshell. Como a lista é executada em um subshell, as atribuições de variáveis ​​não permanecem em vigor após a conclusão do subshell.

$ a=1; (a=2; echo "inside: a=$a"); echo "outside: a=$a"
inside: a=2
outside: a=1

{ list; }Colocar uma lista de comandos entre chaves faz com que a lista seja executada no contexto atual do shell . Nenhum subshell é criado. É necessário o ponto e vírgula (ou nova linha) a seguir. Fonte

${} Parameter expansion Ex:  ANIMAL=duck; echo One $ANIMAL, two ${ANIMAL}s
$() Command substitution Ex: result=$(COMMAND) 
$(()) Arithmetic expansion Ex: var=$(( 20 + 5 )) 

Construções condicionais

Suporte único ou seja, []
Para efeitos de comparação ==, !=, <,e >e deve ser usada e, para comparação numérico eq, ne,lte gtdeve ser usado.

Suportes avançados ie[[]]

Em todos os exemplos acima, usamos apenas colchetes únicos para incluir a expressão condicional, mas o bash permite colchetes duplos, que serve como uma versão aprimorada da sintaxe de colchetes únicos.

Para comparação ==, !=, <,e >pode usar literalmente.

  • [é sinônimo de comando de teste. Mesmo que esteja embutido no shell, ele cria um novo processo.
  • [[ é uma nova versão aprimorada, que é uma palavra-chave, não um programa.
  • [[é entendido por Korne Bash.

Fonte

Premraj
fonte