test e [[ ]] - Condicionais em Shell Scripts

test e [[ ]] - Condicionais em Shell Scripts

O Que Você Vai Aprender

  • A diferença e os trade-offs entre test, [ ] e [[ ]]
  • Como escolher os operadores corretos para strings, números e arquivos
  • Como evitar estruturalmente bugs de quoting como [: too many arguments

Resumo Rápido

  • Na dúvida, use [[ ]] (o padrão seguro no bash)
  • Use [ ] (ou seja, test) apenas quando precisar de portabilidade POSIX sh
  • Quase todo bug vem de (1) aspas faltando ou (2) confusão de operadores (-eq vs =)

Premissas

  • Shell: bash ([[ ]] é uma extensão do bash / ksh / zsh; não existe no POSIX sh)
  • Scripts começam com #!/bin/bash

Qual a diferença entre test, [ ] e [[]]?

Conclusão: [ ] é literalmente o comando test. [[ ]] é uma palavra-chave do shell com quoting mais seguro, mais operadores e correspondência de padrões.

Aqui está a relação em uma tabela.

Sintaxe O que realmente é Portabilidade Principal força
test EXPR Um builtin (também existe em disco) POSIX (alta) Mais fundamental
[ EXPR ] Um alias para test (] é um arg) POSIX (alta) Boa leitura com if
[[ EXPR ]] Uma palavra-chave do shell bash etc. (limitada) Seguro p/ aspas, =~, &&/||

Como [ ] é um comando, você precisa de espaços ao redor de [, ] e cada operador. test e [ se comportam de forma idêntica.

$ test 1 -lt 2; echo $?
0
$ [ 1 -lt 2 ]; echo $?
0

[[ ]], por outro lado, é analisado como sintaxe do shell, o que o torna seguro para aspas e capaz de correspondência de padrões.

Confirme o que [ realmente é

$ type [
[ is a shell builtin
$ ls -l /usr/bin/[
-rwxr-xr-x 1 root root ... /usr/bin/[

O builtin tem prioridade, mas test/[ também existem como comandos externos por razões históricas.

Como comparar strings?

Conclusão: Use = para igualdade (== também funciona dentro de [[ ]]), -z para vazio e -n para não vazio. Sempre coloque variáveis entre aspas duplas.

Operadores fundamentais

  • = ... igual (== é sinônimo dentro de [[ ]])
  • != ... diferente
  • -z "$s" ... string está vazia (comprimento zero)
  • -n "$s" ... string não está vazia
name="penguin"

if [[ "$name" = "penguin" ]]; then
    echo "match"
fi

if [[ -z "$name" ]]; then
    echo "empty"
else
    echo "not empty"
fi

= vs ==

== é uma extensão do bash. Para portabilidade POSIX sh, use = dentro de [ ]. Dentro de [[ ]] ambos funcionam.

[ "$a" = "$b" ]     # Seguro para POSIX
[[ "$a" == "$b" ]]  # bash (tanto = quanto == funcionam dentro de [[ ]])

Como comparar números?

Conclusão: Use -eq -ne -lt -le -gt -ge para números. = e > são operadores de string e significam algo diferente.

Operador Significado Símbolo matemático
-eq igual ==
-ne diferente !=
-lt menor que <
-le menor ou igual <=
-gt maior que >
-ge maior ou igual >=
count=42

if [[ "$count" -ge 10 ]]; then
    echo "10 or more"
fi

Não confunda = e -eq

[[ "08" = "8" ]]    # false (diferentes como strings)
[[ "08" -eq "8" ]]  # true (iguais como numeros)

Usar = quando você quer uma comparação numérica leva a surpresas com zero-padding ou espaços em branco.

Se você quer aritmética, (( )) permite escrever símbolos matemáticos diretamente: (( count >= 10 )).

Como testar se um arquivo existe ou seu tipo?

Conclusão: Operadores de teste de arquivo verificam existência, tipo e permissões. Os mais comuns são -e -f -d -r -w -x -s.

Operador Significado
-e file existe (qualquer tipo)
-f file é um arquivo regular
-d file é um diretório
-r file é legível
-w file é gravável
-x file é executável
-s file tamanho maior que zero
-L file é um link simbólico
a -nt b a é mais novo que b
config="/etc/myapp.conf"

if [[ -f "$config" && -r "$config" ]]; then
    echo "readable config file exists"
else
    echo "config missing or unreadable"
fi

Testar "não existe"

Negue com !. Um padrão comum de retorno antecipado.

if [[ ! -d "$dir" ]]; then
    echo "No such directory: $dir" >&2
    exit 1
fi

Por que [[]] é menos propenso a erros?

Conclusão: Dentro de [[ ]], variáveis expandidas não sofrem word splitting ou globbing, então uma aspas faltando não quebra a sintaxe.

Como [ ] é um comando, uma variável vazia ou com espaço altera a contagem de argumentos e quebra a expressão.

v=""
[ $v = "x" ]      # vira [ = "x" ]: too many arguments / erro de sintaxe
[ "$v" = "x" ]    # vira [ "" = "x" ] (corretamente false) - aspas salvam

v="a b"
[ $v = "x" ]      # vira [ a b = "x" ] (4 args, erro)

Dentro de [[ ]], uma variável expandida não é dividida nem globada, então mesmo uma aspas faltando raramente quebra algo.

v="a b"
[[ $v = "x" ]]    # nao quebra (false)

Mesmo assim, mantenha o hábito de usar aspas. Escrever "$var" tanto em [ ] quanto em [[ ]] mantém o código seguro independente de para onde você o porte.

O que são correspondência de padrões e regex do [[]]?

Conclusão: Dentro de [[ ]], o lado direito de == é um padrão glob e =~ é uma expressão regular. Nenhum dos dois existe em [ ].

Correspondência de padrão glob (==)

Deixe o lado direito sem aspas para um padrão; coloque aspas para corresponder como string literal.

file="report.txt"

if [[ "$file" == *.txt ]]; then
    echo "text file"
fi

if [[ "$file" == "*.txt" ]]; then
    echo "only matches the literal string *.txt"
fi

Correspondência regex (=~)

Deixe o lado direito sem aspas. Grupos capturados ficam no array BASH_REMATCH.

input="port=8080"

if [[ "$input" =~ ^port=([0-9]+)$ ]]; then
    echo "port number: ${BASH_REMATCH[1]}"
fi

Como combinar múltiplas condições?

Conclusão: Dentro de [[ ]] use && e ||. Com [ ], junte comandos separados: [ ... ] && [ ... ].

# Combinar dentro de [[ ]] (legivel)
if [[ "$age" -ge 18 && "$age" -lt 65 ]]; then
    echo "working age"
fi

# Juntar comandos [ ] com &&
if [ "$age" -ge 18 ] && [ "$age" -lt 65 ]; then
    echo "working age"
fi

Os operadores -a (AND) e -o (OR) dentro de [ ] são depreciados. Sua análise de argumentos é ambígua e uma fonte frequente de bugs. Para combinar condições, divida em [ ] separados e junte com && / ||.

Resumo: quando usar test vs [[]]

Conclusão: Em scripts bash, use [[ ]] por padrão e reserve [ ] para locais que precisam de POSIX sh. Acerte quoting e escolha de operador e os bugs praticamente desaparecem.

  • [[ ]] ... apenas bash. Seguro para aspas, suporta =~, &&/||, e leitura fácil
  • [ ] / test ... quando precisar de #!/bin/sh ou compatibilidade POSIX
  • Strings usam = / -z / -n, números usam a família -eq, arquivos usam -f / -d
  • Sempre coloque variáveis entre aspas como "$var"

Template para copiar e colar

# Verificacao de argumento
if [[ $# -lt 1 ]]; then
    echo "usage: $0 <file>" >&2
    exit 1
fi

target="$1"

# Existencia e tipo de arquivo
if [[ ! -f "$target" ]]; then
    echo "not a regular file: $target" >&2
    exit 1
fi

# Ramificar por extensao
if [[ "$target" == *.log ]]; then
    echo "processing a log file"
fi

Próximas Leituras