você está aqui: Home  → Arquivo de Mensagens Saiba mais sobre o curso Programação Shell Linux

Diferenças entre o novo e o velho "test"

Colaboração: Julio Cezar Neves

Data de Publicação: 12 de abril de 2020

Um lembrete rápido, hoje, às 23h59m, se encerram as inscrições para o curso Programação Shell Linux, com o Prof. Julio Neves.

Ainda temos algumas vagas com desconto de 20%, mas estão quase acabando.

»»» Saiba mais e faça sua inscrição


Diferenças entre o novo e o velho "test"

Colaboração: Julio Cezar Neves

[ (O comando test) e [[ (normalmente chamado de comando novo test) são usados para avaliar condições. [[ funciona somente no Bash, Korn Shell e zsh; [ e test estão implementados em qualquer Shell compatível com o padrão POSIX. Apesar de todos os Shells modernos terem implementações internas (builtin) de [, geralmente este ainda é um executável externo, sendo normalmente /bin/[ ou /usr/bin/[. Veja isso:

$ which [
/usr/bin/[
$ which [[
$ whereis -b [
[: /usr/bin/[
$ whereis -b [[
[[:

A opção -b do comando whereis, serve para que ele mostre somente onde está o binário (código propriamente dito) do arquivo e, pelo resultado das linhas acima, podemos ver que não existe código externo do [[, isto é, ele é um intrínseco (builtin) e por isso podemos concluir que é mais veloz.

Embora [ e [[ tenham muito em comum, e compartilhem muitos operadores como -f, -s, -n, -z, há algumas diferenças notáveis, sendo as duas mais importantes é que o novo test ([[) permite:

  1. Comparações usando os metacaracteres de expansão de arquivos
  2. Comparações usando Expressões Regulares.

Testando com coringas

Esse novo tipo de construção é legal porque permite usar metacaracteres de expansão de arquivos para comparação. Esses coringas atendem às normas de Geração de Nome de Arquivos (File Name Generation).

Exemplo



$ echo $H
13
$ [[ $H == [0-9] || $H == 1[0-2] ]] || echo Hora inválida
Hora inválida

Nesse exemplo, testamos se o conteúdo da variável $H estava compreendido entre zero e nove ([0-9]) ou (||) se estava entre dez e doze (1[0-2]), dando uma mensagem de erro caso não estivesse.

Como você pode imaginar, esse uso de padrões para comparação aumenta muito o poderio do comando test.

Testando com Expressões Regulares

Outro grande trunfo deste comando é que ele suporta expressões regulares para especificar condições usando a seguinte sintaxe:

[[ CAD =~ REGEX ]]

Onde REGEX é uma expressão regular (que pode inclusive usar metacaracteres das Expressões Regulares avançadas). Assim sendo, poderíamos montar uma rotina para crítica de horários com a seguinte construção:

if  [[ $Hora =~ ([01][0-9]|2[0-3]):[0-5][0-9] ]]
then
    echo Horario OK
else
    echo O horario informado esta incorreto
fi

DICA: As subcadeias que casam com expressões entre parênteses são salvas no vetor BASH_REMATCH. O elemento de BASH_REMATCH com índice 0 é a porção da cadeia que casou com a expressão regular inteira. O elemento de BASH_REMATCH com índice n é a porção da cadeia que casou com a enésima expressão entre parênteses.

Vamos executar direto no prompt o comando anterior para entender melhor:

$ Hora=12:34
$ if  [[ $Hora =~ ([01][0-9]|2[0-3]):[0-5][0-9] ]]
> then
>     echo Horario OK
> else
>     echo O horario informado esta incorreto
> fi
Horario OK
$ echo ${BASH_REMATCH[@]}        # Lista todos elementos do vetor
12:34 12
$ echo ${BASH_REMATCH[0]}        # Índice zero tem o casamento total
12:34
$ echo ${BASH_REMATCH[1]}        # Índice 1 contem o retrovisor 1. Repare o grupo
12

No primeiro echo que fizemos, o caractere arroba (@) representa todos os elementos do vetor, em seguida, vimos que o elemento índice zero [0] está com a hora inteira e o elemento [1] está com a subcadeia que casou com [01][0-9]|2[0-3], isto é, a expressão que estava entre parênteses. Em outras palavras o índice N (com N maior que zero) atua como se fosse o retrovisor \N, com uma ressalva importante: só podemos criar até 9 retrovisores, mas nesse vetor, tal limite não existe.

Vamos ver se com outro exemplo pode ficar mais claro.

$ if [[ supermercado =~ (mini|(su|hi)per)?mercado ]]
> then
>   echo Todos os elementos - ${BASH_REMATCH[@]}        # O  mesmo  que echo ${BASH_REMATCH[*]}
>   echo Vetor completo     - ${BASH_REMATCH[0]}        # O mesmo que echo $BASH_REMATCH
>   echo Elemento indice 1 - ${BASH_REMATCH[1]}
>   echo Elemento indice 2 - ${BASH_REMATCH[2]}
> fi
Todos os elementos  - supermercado super su
Vetor completo      - supermercado
Elemento indice 1   - super
Elemento indice 2   - su

Agora que vocês entenderam o uso dessa variável, vou mostrar uma de suas grandes utilizações, já que aposto como não perceberam que enrolei vocês desde o primeiro exemplo desta seção. Para isso, vamos voltar ao exemplo da crítica de horas, porém mudando o valor da variável $Hora. Veja só como as Expressões Regulares podem nos enganar:

$ Hora=54321:012345
$ if [[ $Hora =~ ([01][0-9]|2[0-3]):[0-5][0-9] ]]
> then
> echo Horario OK
> else
> echo O horario informado esta incorreto
> fi
Horario OK

Epa! Isso era para dar errado! Vamos ver o que aconteceu:

$ echo ${BASH_REMATCH[0]} 
21:01

Ihhh, casou somente com os dois caracteres que estão antes e os dois que estão depois dos dois pontos (:). Viu como eu disse que tinha enrolado você?

Para que isso ficasse perfeito faltou colocar as âncoras das Expressões Regulares, isto é, um circunflexo (^) para marcar o início e um cifrão ($) para marcar o final. Veja:

$ Hora=54321:012345
$ if [[ $Hora =~ ^([01][0-9]|2[0-3]):[0-5][0-9]$ ]]
> then
> echo Horario OK
> else
> echo O horario informado esta incorreto
> fi
O horario informado esta incorreto

Uma tabela para referência

  1. Esta é uma extensão do padrão POSIX; alguns Shells podem tê-la e outros não.
  2. Os operadores -a e -o, e o agrupador (...), são definidos pelo padrão POSIX, mas apenas para casos estritamente limitados, pois são marcados como obsoletos. O uso desses operadores é desencorajado e é melhor você usar vários comandos [.

    Prefira fazer:

    if [ "$a" = a ] && [ "$b" = b ]; ...
    if [ "$a" = a ] || { [ "$b" = b ] && [ "$c" = c ];}; ...
    

no lugar de:

if [ "$a" = a -a "$b" = b ]; then ...
if [ "$a" = a ] -o \( [ "$b" = b ] -a [ "$c" = c ] \); ...

Algumas belas dicas!

  1. No novo test não será feita expansão de metacaracteres curingas (globbing - metacaracteres de expansão de arquivos) nem divisão de palavras (word spliting), e, portanto, muitos argumentos não precisam ser colocados entre aspas.

    $ ls arq* 
    
    arq  arq1  arq2  arqr 
    
    $ [ -f arq* ] || echo Não existe         # Quando expandir dá erro
    
    bash: [: número excessivo de argumentos 
    Não existe 
    
    $ [[ -f arq* ]] || echo Não existe 
    
    Não existe                             # Não expandiu e não achou arq*
    
    $ var="a b" 
    $ [ -z $var ] || echo Tem dado         # Após a expansão virará 2 argumentos
    
    bash: [: a: esperado operador binário 
    Tem dado 
    
    $ [[ -z $var ]] || echo Tem dado       # Perfeito!
    
    Tem dado
    
    $ > "Nome Ruim"                       # Criei arquivo Nome Ruim
    $ Arq="Nome Ruim" 
    $ [ -f $Arq ] && echo "$Arq é um arquivo" 
    
    bash: [: Arq: esperado operador binário
    
    $ [[ -f $Arq ]] && echo "$Arq é um arquivo" 
    
    Nome Ruim é um arquivo
    
    

Como você pode ver, o fato de você não precisar usar aspas nem apóstrofos torna o uso de [[ mais fácil e menos propenso a erros do que o [.

2. Usando [[, os parênteses não precisam ser "escapados":

$ [[ -f $Arq1 && ( -d $dir1 || -d $dir2) ]]
$ [ -f "$Arq1" -a \( -d "$dir1" -o -d "$dir2" \) ]

3. A partir do Bash 4.1, a comparação de cadeias usando maior que (>) e menor que (<) respeita as definições correntes do comando locale quando feita com [[. As versões anteriores do Bash não respeitavam o locale. O [ e o test também não o respeitam em nenhuma versão (embora as man pages digam que sim).

Como regra, aconselho sempre usar [[ para testar condições que envolvam cadeias e arquivos. Se você quiser comparar números, use uma expressão aritmética (( ... )).

Quando deve ser usado o novo comando test ([[) e quando se deve usar o antigo ([)? Se a portabilidade POSIX ou o Bourne Shell for uma preocupação, a sintaxe antiga deve ser usada. Se, por outro lado, o script requer bash, zsh ou korn shell, a nova sintaxe é muito mais flexível.



Veja a relação completa dos artigos de Julio Cezar Neves