Comparação de Condicionais Bash: Quando usar test, [ e [[

Compare test, colchetes simples e colchetes duplos para manter seus condicionais Bash portáteis, seguros e legíveis.

Comparação de Condicionais Bash: Quando usar test, [ e [[

Quando um condicional Bash se comporta de forma estranha, o problema geralmente é a construção que você escolheu. test, [ ] e [[ ]] parecem semelhantes, mas lidam com aspas, padrões, regex e portabilidade de forma diferente.

Este guia compara as três formas para que você possa escrever condicionais seguros, legíveis e adequados ao shell em que seu script realmente é executado.

O Comando test: A Fundação

O comando test é uma das formas mais antigas e fundamentais de avaliar condições em scripts de shell. É um comando embutido na maioria dos shells modernos e faz parte do padrão POSIX, tornando-o altamente portátil. test avalia uma expressão e retorna um status de saída de 0 (verdadeiro) ou 1 (falso).

Uso Básico

O comando test recebe um ou mais argumentos, que formam a expressão a ser avaliada. Ele verifica atributos de arquivo, comparações de strings e comparações de inteiros.

# Verifica se um arquivo existe
if test -f "meuarquivo.txt"; then
    echo "meuarquivo.txt existe e é um arquivo regular."
fi

# Verifica se duas strings são iguais
NOME="Alice"
if test "$NOME" = "Alice"; then
    echo "O nome é Alice."
fi

# Verifica se um número é maior que outro
CONTAGEM=10
if test "$CONTAGEM" -gt 5; then
    echo "A contagem é maior que 5."
fi

Operadores Comuns do test

  • Operadores de Arquivo: -f (arquivo regular), -d (diretório), -e (existe), -s (não vazio), -r (legível), -w (gravável), -x (executável).
  • Operadores de String: = (igual), != (diferente), -z (string vazia), -n (string não vazia).
  • Operadores de Inteiro: -eq (igual), -ne (diferente), -gt (maior que), -ge (maior ou igual), -lt (menor que), -le (menor ou igual).

Dica: Sempre coloque variáveis usadas com test entre aspas (ex.: "$NOME") para evitar problemas com divisão de palavras e expansão de nomes de caminho se o valor da variável contiver espaços ou caracteres glob.

Colchetes Simples [ ]: A Forma test

A construção de colchetes simples [ ] é uma sintaxe alternativa para o comando test. Em muitos shells, [ é um comando embutido, e os sistemas geralmente também fornecem um /usr/bin/[ externo. A principal diferença é que [ requer um ] de fechamento como seu último argumento. Assim como test, é compatível com POSIX.

Sintaxe e Semântica

# Equivalente a test -f "meuarquivo.txt"
if [ -f "meuarquivo.txt" ]; then
    echo "meuarquivo.txt existe e é um arquivo regular usando [ ]."
fi

# Equivalente a test "$NOME" = "Alice"
NOME="Bob"
if [ "$NOME" != "Alice" ]; then
    echo "O nome não é Alice."
fi

Observe o espaço obrigatório após [ e antes de ]. Eles são tratados como argumentos separados para o comando [.

Colocando Variáveis entre Aspas: Um Detalhe Crítico

Como [ ] é fundamentalmente o comando test, ele herda os mesmos comportamentos em relação à divisão de palavras e expansão de nomes de caminho. Isso significa que variáveis sem aspas podem levar a comportamentos inesperados ou vulnerabilidades de segurança.

Considere este exemplo:

#!/bin/bash

ENTRADA="arquivo com espaços.txt"

# PERIGOSO: Variável sem aspas causará problemas se ENTRADA contiver espaços
# O shell realizará a divisão de palavras, tratando "arquivo" e "com espaços.txt" como argumentos separados
# levando a um erro de sintaxe ou avaliação incorreta.
# if [ -f $ENTRADA ]; then echo "Encontrado"; else echo "Não encontrado"; fi 

# CORRETO: Coloque a variável entre aspas para tratá-la como um único argumento
if [ -f "$ENTRADA" ]; then
    echo "'arquivo com espaços.txt' existe."
else
    echo "'arquivo com espaços.txt' não existe ou não é um arquivo regular."
fi

Sem aspas, $ENTRADA se expandiria para arquivo com espaços.txt, e [ -f arquivo com espaços.txt ] seria interpretado como um erro de sintaxe pelo comando [ porque -f espera apenas um operando. Colocar entre aspas garante que $ENTRADA seja passado como um único argumento, "arquivo com espaços.txt".

Perigos da Divisão de Palavras e Expansão de Nomes de Caminho

Tanto test quanto [ estão sujeitos aos comportamentos padrão do shell de divisão de palavras e expansão de nomes de caminho (globbing). Se uma variável contiver espaços ou caracteres glob (*, ?, [ ]) e não estiver entre aspas, o shell a expandirá antes que test ou [ vejam os argumentos. Isso pode levar a erros de sintaxe ou comparações incorretas quando caracteres glob corresponderem a arquivos existentes.

Colchetes Duplos [[ ]]: A Palavra-chave Moderna do Bash

A construção de colchetes duplos [[ ]] é uma palavra-chave do Bash (também suportada por Ksh e Zsh), não um comando externo ou um alias. Essa distinção é crucial, pois permite que [[ ]] se comporte de forma diferente e ofereça funcionalidades aprimoradas e segurança melhorada em comparação com test ou [ ].

Funcionalidades Aprimoradas

[[ ]] introduz vários recursos poderosos não disponíveis com test ou [:

  1. Sem Divisão de Palavras ou Expansão de Nomes de Caminho: Variáveis dentro de [[ ]] geralmente não precisam ser colocadas entre aspas (embora seja uma boa prática fazê-lo para maior clareza). O shell lida com o conteúdo de [[ ]] como uma única unidade, evitando a divisão de palavras e a expansão de nomes de caminho. Isso reduz significativamente erros comuns de script e riscos de segurança.

    # Não é necessário colocar variáveis entre aspas (embora ainda seja seguro fazê-lo)
    ENTRADA="arquivo com espaços.txt"
    if [[ -f $ENTRADA ]]; then # $ENTRADA é tratado como uma única string aqui
        echo "'$ENTRADA' existe."
    fi
    
  2. Globbing para Comparação de Strings: Os operadores == e != realizam correspondência de padrões (globbing) em vez de igualdade estrita de strings quando usados dentro de [[ ]]. Isso significa que você pode usar *, ? e [] como curingas.

    NOME_ARQUIVO="meu_documento.txt"
    if [[ "$NOME_ARQUIVO" == *".txt" ]]; then # Verifica se NOME_ARQUIVO termina com .txt
        echo "É um arquivo de texto!"
    fi
    
    # Nota: Para igualdade estrita de strings sem globbing, use `test` ou `[ ]` com `=`
    # ou certifique-se de que nenhum caractere glob esteja presente no lado direito de `==` em [[ ]] 
    # (ou coloque o lado direito entre aspas se ele contiver caracteres glob literais que você deseja corresponder literalmente).
    
  3. Correspondência de Expressões Regulares: O operador =~ permite realizar correspondência de expressões regulares.

    ENDERECO_IP="192.168.1.100"
    if [[ "$ENDERECO_IP" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
        echo "Formato de IP válido."
    fi

    # Importante: O padrão regex no lado direito de =~ geralmente NÃO deve ser colocado entre aspas
    # se contiver caracteres que seriam tratados como padrões glob.
    # Se o regex estiver em uma variável, ele também deve estar sem aspas.
    # Exemplo de padrão: ^[A-Za-z]+$
    ```

4.  **Operadores Lógicos `&&` e `||`**: `[[ ]]` suporta os operadores lógicos mais intuitivos no estilo C `&&` (E) e `||` (OU) para combinar múltiplas condições, juntamente com `!` para negação. Esses operadores têm avaliação de curto-circuito e precedência adequadas, ao contrário de `-a` e `-o` do `test`.

    ```bash
    IDADE=25
    if [[ "$NOME" == "Alice" && "$IDADE" -ge 18 ]]; then
        echo "Alice é adulta."
    fi

    if [[ "$USUARIO" == "root" || -w /etc/fstab ]]; then
        echo "Ou é root ou pode escrever em fstab."
    fi
    ```

### Natureza Específica do Bash

Embora `[[ ]]` ofereça vantagens significativas, sua principal desvantagem é que é uma extensão do Bash/Ksh/Zsh e não faz parte do padrão POSIX. Isso significa que scripts que dependem de `[[ ]]` podem não ser portáveis para `sh`, `dash` ou sistemas Unix-like mais antigos/mínimos.

## Comparação Lado a Lado: `test` vs. `[` vs. `[[`

Aqui está uma tabela resumindo as principais diferenças:

| Característica               | `test`                           | `[ ]`                               | `[[ ]]`                                   |
| :------------------------- | :------------------------------- | :---------------------------------- | :---------------------------------------- |
| **Tipo**                   | Comando embutido (ou externo)   | Comando embutido (alias para `test`)  | Palavra-chave do shell (Bash, Ksh, Zsh)            |
| **Compatível com POSIX**        | Sim                              | Sim                                 | Não                                        |
| **`]` de Fechamento Obrigatório**   | Não                               | Sim (como último argumento)              | Sim (como parte da palavra-chave)                  |
| **Divisão de Palavras**         | Sim, em variáveis sem aspas       | Sim, em variáveis sem aspas         | Não, variáveis são tratadas como strings únicas |
| **Expansão de Nomes de Caminho**     | Sim, em variáveis sem aspas       | Sim, em variáveis sem aspas         | Não |
| **Correspondência de Padrões Glob** | Não para igualdade de strings           | Não para igualdade de strings             | Sim com lado direito sem aspas de `==` ou `!=` |
| **Expressões Regulares**    | Não                               | Não                                  | Sim com `=~` |
| **E/OU Lógicos**         | `-a`, `-o` existem, mas são fáceis de interpretar erroneamente | `-a`, `-o` existem, mas são fáceis de interpretar erroneamente | `&&`, `||` com comportamento normal de curto-circuito |
| **Comandos Compostos**      | Requer chamadas `test` separadas   | Requer chamadas `[` separadas         | Pode combinar expressões diretamente (`&&`/`||`)|
| **Colocação de Variáveis entre Aspas**       | **Obrigatória** para segurança         | **Obrigatória** para segurança            | Geralmente não é necessária, mas é uma boa prática |

## Quando Usar Cada Um

Escolher a construção condicional correta depende principalmente dos seus requisitos de portabilidade e da complexidade da sua lógica condicional.

### Conformidade POSIX vs. Recursos Modernos do Bash

-   **Use `test` ou `[ ]` quando...**
    -   **A portabilidade é fundamental**: Se o seu script precisa ser executado em qualquer shell compatível com POSIX (`sh`, `dash`, sistemas mais antigos, etc.), `test` ou `[ ]` são suas únicas opções confiáveis.
    -   Suas condições são simples (verificações de arquivo, comparações básicas de strings/inteiros).
    -   Você se sente confortável com a colocação cuidadosa de todas as variáveis entre aspas e usando `&&`/`||` no nível do shell fora dos colchetes quando precisar de lógica composta.

-   **Use `[[ ]]` quando...**
    -   **Você está escrevendo exclusivamente para Bash** (ou Ksh/Zsh) e não precisa de portabilidade POSIX.
    -   Você precisa de recursos avançados como correspondência de padrões glob, correspondência de expressões regulares ou operadores lógicos `&&`/`||` no estilo C.
    -   Você deseja os recursos de segurança aprimorados que evitam a divisão de palavras e a expansão de nomes de caminho, levando a um código mais robusto e menos propenso a erros.
    -   Suas condições envolvem lógica complexa que seria complicada com `test -a`/`-o`.

### Melhores Práticas e Recomendações

1.  **Priorize `[[ ]]` para Scripts Bash**: Se o seu script é destinado ao Bash, `[[ ]]` é geralmente a escolha preferida devido à sua segurança aumentada, funcionalidade estendida e sintaxe mais intuitiva para condições complexas. Ele reduz drasticamente erros comuns de script relacionados a aspas e caracteres especiais.

2.  **Sempre Coloque Aspas em `test` e `[ ]`**: Se você *precisar* usar `test` ou `[ ]` para conformidade POSIX, crie o hábito de **sempre colocar suas variáveis entre aspas** para evitar comportamentos inesperados devido à divisão de palavras e expansão de nomes de caminho.

    ```bash
    # Boa prática para [ ] e test
    VAR="uma string com espaços"
    if [ -n "$VAR" ]; then echo "Não vazio"; fi
    ```

3.  **Esteja Atento à Correspondência de Padrões**: Em `test` e `[ ]`, `=` é usado para igualdade de strings. Em `[[ ]]`, `=` e `==` podem realizar correspondência de padrões quando o lado direito não está entre aspas. Coloque o lado direito entre aspas quando quiser uma comparação literal de strings.

4.  **Expressões Regulares com `=~`**: Ao usar `=~` em `[[ ]]`, o lado direito normalmente deve estar sem aspas para permitir que o shell o interprete como um padrão de expressão regular, não como uma string literal para corresponder.

    ```bash
    # Padrão regex sem aspas está correto para =~ em [[ ]]
    if [[ "$LINHA" =~ ^Erro: ]]; then echo "Erro encontrado"; fi
    ```

## Conclusão

Use `[ ]` ou `test` quando seu script precisar ser executado sob POSIX `sh`. Use `[[ ]]` quando seu shebang for Bash e você quiser manipulação de variáveis mais segura, correspondência glob, correspondência regex e condições compostas mais limpas. O hábito principal é simples: combine a sintaxe condicional com o shell e coloque aspas deliberadamente.