Arrays associativos no Bash 4+

1. Introdução aos Arrays Associativos

Arrays associativos são estruturas de dados que armazenam pares chave-valor, onde cada chave é única e pode ser uma string arbitrária. Diferentemente dos arrays indexados tradicionais (que usam números inteiros como índices), os arrays associativos funcionam como mapas ou dicionários encontrados em outras linguagens de programação.

A principal diferença entre arrays indexados e associativos está na natureza das chaves:
- Arrays indexados: chaves são números inteiros sequenciais (0, 1, 2, ...)
- Arrays associativos: chaves são strings arbitrárias definidas pelo usuário

Requisito fundamental: Arrays associativos estão disponíveis apenas no Bash versão 4.0 ou superior. Para verificar sua versão do Bash, use:

echo $BASH_VERSION

Se sua versão for anterior à 4.0, será necessário atualizar o Bash para utilizar este recurso.

2. Declaração e Inicialização

Para declarar um array associativo, utilizamos a opção -A (maiúscula) com o comando declare:

declare -A meu_array

A atribuição de valores pode ser feita de duas formas principais:

Atribuição direta:

declare -A usuario
usuario[nome]="João Silva"
usuario[email]="joao@exemplo.com"
usuario[idade]=30

Inicialização com literais:

declare -A config=(
    [host]="localhost"
    [porta]=8080
    [debug]=true
    [caminho_log]="/var/log/app"
)

Cuidados importantes:
- Chaves com espaços devem ser colocadas entre aspas: meu_array["chave com espaco"]=valor
- Caracteres especiais como [ e ] precisam ser escapados ou colocados entre aspas
- Valores com espaços também devem ser citados adequadamente

declare -A exemplo
exemplo["nome completo"]="Maria das Dores"
exemplo["valor (R\$)"]=150.50

3. Operações Básicas

Acessando valores:

echo "${usuario[nome]}"    # Saída: João Silva
echo "${config[porta]}"    # Saída: 8080

Verificando existência de chave:

if [[ -v usuario[email] ]]; then
    echo "Chave 'email' existe"
fi

# Alternativa com expansão condicional
if [[ ${config[debug]+existe} ]]; then
    echo "Chave 'debug' existe"
fi

Removendo elementos:

unset usuario[idade]

Tamanho do array:

echo "${#usuario[@]}"    # Número de pares chave-valor

4. Iteração e Listagem

Percorrendo chaves:

for chave in "${!usuario[@]}"; do
    echo "Chave: $chave"
done

Percorrendo valores:

for valor in "${usuario[@]}"; do
    echo "Valor: $valor"
done

Percorrendo pares chave-valor simultaneamente:

for chave in "${!usuario[@]}"; do
    echo "$chave => ${usuario[$chave]}"
done

Uso com printf para formatação:

printf "%-20s => %s\n" "chave" "valor"
for chave in "${!config[@]}"; do
    printf "%-20s => %s\n" "$chave" "${config[$chave]}"
done

5. Manipulação Avançada

Copiando arrays associativos:

declare -A copia
for chave in "${!original[@]}"; do
    copia["$chave"]="${original[$chave]}"
done

Mesclando dois arrays associativos:

declare -A mesclado
for chave in "${!arr1[@]}" "${!arr2[@]}"; do
    if [[ -v arr2[$chave] ]]; then
        mesclado["$chave"]="${arr2[$chave]}"
    else
        mesclado["$chave"]="${arr1[$chave]}"
    fi
done

Ordenação de chaves ou valores:

# Ordenar chaves
for chave in $(printf '%s\n' "${!config[@]}" | sort); do
    echo "$chave => ${config[$chave]}"
done

# Ordenar por valores
for valor in $(printf '%s\n' "${config[@]}" | sort); do
    for chave in "${!config[@]}"; do
        [[ "${config[$chave]}" == "$valor" ]] && echo "$chave => $valor"
    done
done

Uso de variáveis como chaves dinâmicas:

declare -A dados
chave_dinamica="usuario_$(date +%s)"
dados["$chave_dinamica"]="valor dinâmico"

6. Aplicações Práticas

Contagem de frequência de palavras:

declare -A frequencia
texto="gato cachorro gato passaro cachorro gato"
for palavra in $texto; do
    ((frequencia[$palavra]++))
done

for palavra in "${!frequencia[@]}"; do
    echo "$palavra: ${frequencia[$palavra]}"
done

Mapeamento de configurações (arquivo .conf):

declare -A config_app
while IFS='=' read -r chave valor; do
    [[ -z "$chave" || "$chave" =~ ^# ]] && continue
    config_app["$chave"]="$valor"
done < "app.conf"

Cache de resultados de comandos:

declare -A cache_comandos
executar_com_cache() {
    local cmd="$1"
    if [[ -v cache_comandos[$cmd] ]]; then
        echo "Usando cache"
        echo "${cache_comandos[$cmd]}"
    else
        echo "Executando comando"
        cache_comandos["$cmd"]=$(eval "$cmd" 2>/dev/null)
        echo "${cache_comandos[$cmd]}"
    fi
}

Tabelas de lookup para substituição de texto:

declare -A substituicoes=(
    [":user:"]="joaosilva"
    [":date:"]="$(date +%Y-%m-%d)"
    [":version:"]="2.1.0"
)

template="Usuário: :user:, Data: :date:, Versão: :version:"
for chave in "${!substituicoes[@]}"; do
    template="${template//$chave/${substituicoes[$chave]}}"
done
echo "$template"

7. Limitações e Boas Práticas

Limitações importantes:
- Chaves devem ser strings (não é possível usar números como chaves sem convertê-los para string)
- Não há ordenação garantida dos elementos (a ordem de iteração é imprevisível)
- Arrays associativos não podem ser exportados para subshells diretamente

Exportação e passagem para funções:

# Passagem para função (por referência)
processa_array() {
    local -n arr_ref=$1
    for chave in "${!arr_ref[@]}"; do
        echo "$chave => ${arr_ref[$chave]}"
    done
}

declare -A meu_arr=([a]=1 [b]=2)
processa_array meu_arr

Diferenças entre ${!array[@]} e ${!array[*]}:
- ${!array[@]}: Expande para uma lista de chaves, preservando palavras com espaços
- ${!array[*]}: Expande para uma única string com todas as chaves

Dicas de desempenho e legibilidade:
- Use nomes descritivos para seus arrays associativos
- Prefira declare -A explícito em vez de declaração implícita
- Utilize [[ -v array[chave] ]] para verificar existência (mais rápido que ${array[chave]+existe})
- Para arrays muito grandes, considere usar arquivos temporários ou bancos de dados

# Boa prática: sempre declarar explicitamente
declare -A config_servidor

# Evitar: uso implícito (pode causar erros)
config_servidor[host]="localhost"  # Funciona, mas menos seguro

Referências