test e [[ ]] - Condicionais em Shell Scripts

test e [[ ]] - Condicionais em Shell Scripts

O Que Voce Vai Aprender

  • A diferenca e os trade-offs entre test, [ ] e [[ ]]
  • Como escolher os operadores corretos para strings, numeros e arquivos
  • Como evitar estruturalmente bugs de quoting como [: too many arguments

Resumo Rapido

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

Premissas

  • Shell: bash ([[ ]] e uma extensao do bash / ksh / zsh; nao existe no POSIX sh)
  • Scripts comecam com #!/bin/bash

Qual a diferenca entre test, [ ] e [[]]?

Conclusao: [ ] e literalmente o comando test. [[ ]] e uma palavra-chave do shell com quoting mais seguro, mais operadores e correspondencia de padroes.

Aqui esta a relacao em uma tabela.

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

Como [ ] e um comando, voce precisa de espacos ao redor de [, ] e cada operador. test e [ se comportam de forma identica.

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

[[ ]], por outro lado, e analisado como sintaxe do shell, o que o torna seguro para aspas e capaz de correspondencia de padroes.

Confirme o que [ realmente e

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

O builtin tem prioridade, mas test/[ tambem existem como comandos externos por razoes historicas.

Como comparar strings?

Conclusao: Use = para igualdade (== tambem funciona dentro de [[ ]]), -z para vazio e -n para nao vazio. Sempre coloque variaveis entre aspas duplas.

Operadores fundamentais

  • = ... igual (== e sinonimo dentro de [[ ]])
  • != ... diferente
  • -z "$s" ... string esta vazia (comprimento zero)
  • -n "$s" ... string nao esta vazia
name="penguin"

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

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

= vs ==

== e uma extensao 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 numeros?

Conclusao: Use -eq -ne -lt -le -gt -ge para numeros. = e > sao operadores de string e significam algo diferente.

Operador Significado Simbolo matematico
-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

Nao confunda = e -eq

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

Usar = quando voce quer uma comparacao numerica leva a surpresas com zero-padding ou espacos em branco.

Se voce quer aritmetica, (( )) permite escrever simbolos matematicos diretamente: (( count >= 10 )).

Como testar se um arquivo existe ou seu tipo?

Conclusao: Operadores de teste de arquivo verificam existencia, tipo e permissoes. Os mais comuns sao -e -f -d -r -w -x -s.

Operador Significado
-e file existe (qualquer tipo)
-f file e um arquivo regular
-d file e um diretorio
-r file e legivel
-w file e gravavel
-x file e executavel
-s file tamanho maior que zero
-L file e um link simbolico
a -nt b a e 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 "nao existe"

Negue com !. Um padrao comum de retorno antecipado.

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

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

Conclusao: Dentro de [[ ]], variaveis expandidas nao sofrem word splitting ou globbing, entao uma aspas faltando nao quebra a sintaxe.

Como [ ] e um comando, uma variavel vazia ou com espaco altera a contagem de argumentos e quebra a expressao.

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 variavel expandida nao e dividida nem globada, entao mesmo uma aspas faltando raramente quebra algo.

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

Mesmo assim, mantenha o habito de usar aspas. Escrever "$var" tanto em [ ] quanto em [[ ]] mantem o codigo seguro independente de para onde voce o porte.

O que sao correspondencia de padroes e regex do [[]]?

Conclusao: Dentro de [[ ]], o lado direito de == e um padrao glob e =~ e uma expressao regular. Nenhum dos dois existe em [ ].

Correspondencia de padrao glob (==)

Deixe o lado direito sem aspas para um padrao; 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

Correspondencia 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 multiplas condicoes?

Conclusao: 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 [ ] sao depreciados. Sua analise de argumentos e ambigua e uma fonte frequente de bugs. Para combinar condicoes, divida em [ ] separados e junte com && / ||.

Resumo: quando usar test vs [[]]

Conclusao: Em scripts bash, use [[ ]] por padrao 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 facil
  • [ ] / test ... quando precisar de #!/bin/sh ou compatibilidade POSIX
  • Strings usam = / -z / -n, numeros usam a familia -eq, arquivos usam -f / -d
  • Sempre coloque variaveis 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

Proximas Leituras