Arrays Associativos (Hash) no Bash

Introdução

Versões mais novas do bash (>= 4.x) suportam um novo tipo de array: o array associativo. Esse tipo de array, também chamado de hash em outras linguagens, consegue indexar seus elementos usando strings ao invés dos usuais números inteiros. Os usos desse tipo de array são os mais variados possíveis, como veremos nesse post.

Hoje iremos aprender como criar e manipular arrays associativos no bash.

Meu bash é >= 4.x ?

Antes de tudo você deve se assegurar que está usando uma versão >= 4.x do bash. Para isso há dois modos:

$ echo $BASH_VERSION
4.2.10(1)-release

$ bash --version | head -n1
GNU bash, version 4.2.10(1)-release (i386-redhat-linux-gnu)

Declarando um array associativo

$ declare -A animais

O uso do declare é obrigatório para arrays associativos [1], portanto nunca esqueça de utilizá-lo. Após o parâmetro “-A” vem o nome do array associativo.

Bem, após declarar o array, você já pode associar valores a ele.

Usando o operador de igualdade:

animais=( [miau]=gato [auau]=cachorro [muu]=vaca )

Repare que dentro dos colchetes aparece a chave e após o ‘=’ vem o valor. Outra observação também é que se o valor contém espaços em branco, use aspas para englobá-lo como um só parâmetro.

É possível também assinalar um par chave/valor de cada vez:

animais[miau]=gato
animais[auau]=cachorro
animais[muu]=vaca

Obtendo chaves e valores

1) Obtendo as chaves

$ echo ${!animais[@]}
miau auau muu

2) Obtendo os valores das chaves

$ echo ${animais[@]}
gato cachorro vaca

OBS: Nesses casos as chaves e os valores vieram conforme a ordem que eles foram declarados, mas nem sempre é assim. Internamente as chaves e valores são ordenados seguindo um critério específico do algoritmo de hash e por causa disso a saída pode sair fora de ordem.

E por fim, para recuperar um valor de determinada chave, faça:

$ echo ${animais[auau]}
cachorro

Quantidade de elementos

Assim como arrays normais, você pode recuperar a quantidade de elementos de um hash, assim:

$ echo ${#animais[@]}
6

Percorrendo um array associativo

Podemos usar um loop para percorrer todos os elementos de um array associativo. Para isso expandimos a lista de chaves e de posse de cada chave recuperamos seu valor. Exemplo:

$ for chave in ${!animais[@]}; do echo "$chave = ${animais[$chave]}"; done
miau = gato
auau = cachorro
muu = vaca

Quando usamos uma variável como índice, obrigatoriamente devemos usar o cifrão [1].

Até que enfim os exemplos …

1) Contador de palavras

Esse é um exemplo clássico do uso de hashes. O que iremos fazer é pegar cada palavra de um texto e armazená-la como chave do hash, e no início seu valor será nulo (que é o mesmo que zero). Quando houver colisão, ou seja, se uma palavra (= chave) aparecer mais de uma vez, o seu valor será incrementado. No fim do script a quantidade de vezes em que cada palavra aparece no texto é mostrada.

$ cat doces.txt
O doce perguntou pro doce
Qual é o doce mais doce
Que o doce de batata-doce
O doce respondeu pro doce
Que o doce mais doce que
O doce de batata-doce
É o doce de doce de batata-doce

O script:

#!/bin/bash
#[contapalavra.sh]

# cria o hash
declare -A palavras

# arquivo que contem o texto
textofile='doce.txt'

# percorre cada linha
while read LINHA
do
    # percorre cada palavra
    for PALAVRA in $LINHA
    do
        # incrementa o valor
        let palavras[$PALAVRA]++
    done
done < $textofile

# Mostra as palavras e a
# quantidade de vezes em que elas
# apareceram
for chave in ${!palavras[@]}
do
    tput bold; echo -n "$chave"; tput sgr0
    echo -n " apareceu "
    tput bold; echo -n ${palavras[$chave]} ; tput sgr0
    echo " vez(es)."
done

Executando-o temos:

$ ./contapalavra.sh
mais apareceu 2 vez(es).
respondeu apareceu 1 vez(es).
que apareceu 1 vez(es).
É apareceu 1 vez(es).
de apareceu 4 vez(es).
perguntou apareceu 1 vez(es).
doce apareceu 12 vez(es).
O apareceu 3 vez(es).
Que apareceu 2 vez(es).
é apareceu 1 vez(es).
pro apareceu 2 vez(es).
o apareceu 4 vez(es).
batata-doce apareceu 3 vez(es).
Qual apareceu 1 vez(es).

2) Quantos usuários usam determinado shell?

Esse script vasculha o arquivo /etc/passwd e conta quantos usuários usam determinado shell.

#!/bin/bash
#[contashell.sh]

# cria o hash
declare -A shells

# Obtem uma lista de shells
for shell in $(cut -d: -f 7 /etc/passwd)
do
    let shells[$shell]++
done

# Mostra os valores do hash
for chave in ${!shells[@]}
do
    echo "$chave = ${shells[$chave]}"
done

Executando:

$ ./contashell.sh
/sbin/nologin = 40
/bin/sync = 1
/bin/bash = 3
/bin/sh = 1
/sbin/shutdown = 1
/sbin/halt = 1

3) Agrupar chaves pelo valor

Um array associativo não permite duplicação de chaves, mas nada impede que os valores sejam repetidos. Assim é possível agrupar chaves com valores semelhantes para resolver diversos tipos de problemas. Em [1] você encontrará algo semelhante aonde o autor agrupa animais “selvagens” e “domésticos” e guarda as respectivas chaves em vetores separados.

Conclusão

A possibilidade de usarmos strings como índices facilita muito a programação, principalmente nos casos em que queremos apagar ou contar as multiplicidades de palavras de determinada string, como vimos nos 3 exemplos apresentados. Infelizmente somente as versões mais novas do bash possibilitam o uso de arrays associativos, sobrando para aqueles que usam versões antigas o uso de “wrappers” [2] ou atualização para a versão mais nova do interpretador.

Referências

[1] Vetores associativos, by Júlio Cezar Neves (Acessado em: Março/2012)
http://www.dicas-l.com.br/cantinhodoshell/cantinhodoshell_20100120.php

[2] How to define hash tables in bash? from http://stackoverflow.com  (Acessado em: Março/2012)
http://stackoverflow.com/questions/1494178/how-to-define-hash-tables-in-bash

[3] Trava Língua  (Acessado em: Março/2012)
http://www.alzirazulmira.com/trava.htm

[4] Como utilizar arrays e arrays associativos em shell script by Gustavo Dutra (Acessado em: Março/2012)
http://gustavodutra.com/geek/como-utilizar-arrays-e-arrays-associativos-em-shell-script

Deixe um comentário