0Pricing
DevOps Bootcamp · Lección

Paso de matrices y mapas asociativos entre funciones

Use namerefs y expansión indirecta para pasar estructuras de datos complejas a las funciones y recuperarlas de forma segura.

Paso de matrices y mapas asociativos entre funciones es una lección gratuita de DevOps Bootcamp en CoddyKit. Esta es la lección 4 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de DevOps Bootcamp, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de DevOps Bootcamp incluye 4 lecciones en total.

Por qué es complicado pasar arrays a funciones

En Bash, los arrays y los arrays asociativos no son valores de primera clase. Al intentar pasar un array a una función, no puede usar simplemente $my_array: eso solo expande el primer elemento.

Considere este intento incorrecto:

my_func "${my_array[@]}"

La función recibe una lista plana de palabras. No tiene forma de saber dónde termina un elemento y comienza el siguiente, y pierde por completo la estructura del array.

Bash proporciona dos mecanismos fiables para pasar estructuras de datos complejas:

  • Namerefs (declare -n): una referencia a otra variable mediante su nombre (Bash 4.3 o posterior)
  • Expansión indirecta (${!varname[@]}): acceso a una variable cuyo nombre está almacenado en otra variable (versiones anteriores de Bash)

En esta lección se explican ambas técnicas para que pueda escribir funciones robustas y reutilizables que trabajen con arrays y mapas.

Pasar un array por nombre mediante un nameref

El enfoque moderno más limpio es el nameref: se pasa el nombre del array como cadena y, dentro de la función, se declara una variable local que es una referencia a él.

Sintaxis principal:

  • declare -n ref="$1": ref se convierte en un alias del nombre de variable que se haya pasado como primer argumento
  • Puede leer, recorrer y modificar ref exactamente como si fuera el array original

Esto evita copiar el array completo y permite que la función trabaje con arrays de cualquier tamaño.

#!/usr/bin/env bash

print_array() {
    declare -n arr="$1"   # nameref: arr -> caller's variable
    echo "Array has ${#arr[@]} elements:"
    for elem in "${arr[@]}"; do
        echo "  - $elem"
    done
}

fruits=(apple banana cherry)
print_array fruits

Modificar dentro de una función el array de quien la llama

Como un nameref es un alias real, las escrituras realizadas mediante el nameref modifican la variable original de quien llama. No es necesario devolver valores con echo ni usar variables globales.

Esto hace que los namerefs sean ideales para funciones que transforman arrays en el propio lugar; por ejemplo, para normalizar cadenas, filtrar elementos u ordenar.

#!/usr/bin/env bash

to_uppercase() {
    declare -n _arr="$1"
    for i in "${!_arr[@]}"; do
        _arr[$i]="${_arr[$i]^^}"   # ^^ = uppercase in Bash 4+
    done
}

words=(hello world bash)
echo "Before: ${words[@]}"
to_uppercase words
echo "After:  ${words[@]}"

Devolver un array nuevo mediante un nameref

Una función también puede escribir en un array proporcionado por quien la llama aceptando su nombre como parámetro de salida. Quien llama crea un array vacío, pasa su nombre y la función lo rellena.

Este patrón es similar al modismo clásico de C de pasar un puntero a un búfer de resultados, solo que aquí se realiza completamente en el shell.

#!/usr/bin/env bash

get_even_numbers() {
    local -a source=("$@")
    # Last argument is the output array name — pop it off
    local out_name="${source[-1]}"
    unset 'source[-1]'

    declare -n _out="$out_name"
    _out=()   # clear
    for n in "${source[@]}"; do
        (( n % 2 == 0 )) && _out+=("$n")
    done
}

declare -a evens
get_even_numbers 1 2 3 4 5 6 7 8 evens
echo "Evens: ${evens[@]}"

La trampa de las colisiones de namerefs

Hay un problema crítico con los namerefs: si la variable nameref local tiene el mismo nombre que la variable de quien llama, Bash entra en un bucle de autorreferencia infinita y el script falla.

Por ejemplo, si quien llama da a su array el nombre arr y la función ejecuta declare -n arr="$1", el nameref arr apunta a sí mismo.

Convención para evitarlo: anteponga un guion bajo a las variables nameref internas (por ejemplo, _arr, _ref, _out). Casi nunca se usan esos nombres para las variables de quien llama, por lo que las colisiones son extremadamente poco frecuentes.

#!/usr/bin/env bash

# DANGER: caller uses variable named 'arr'
arr=(one two three)

bad_func() {
    declare -n arr="$1"   # collision! arr refers to itself
    echo "${arr[@]}"
}

good_func() {
    declare -n _arr="$1"  # underscore prefix avoids collision
    echo "${_arr[@]}"
}

# bad_func arr   # would hang / error
good_func arr

Pasar arrays asociativos (mapas) por nombre

Los arrays asociativos (declare -A) funcionan exactamente igual con los namerefs. Pase el nombre del mapa como cadena y declare un nameref dentro de la función: obtendrá acceso completo de lectura y escritura a todos los pares clave-valor.

Esto resulta muy útil para mapas de configuración, analizadores de opciones y tablas de búsqueda que deben compartirse entre funciones de una biblioteca.

#!/usr/bin/env bash

print_map() {
    declare -n _map="$1"
    echo "Map contents:"
    for key in "${!_map[@]}"; do
        printf '  %-15s => %s\n' "$key" "${_map[$key]}"
    done
}

declare -A config
config[host]="localhost"
config[port]="5432"
config[db]="myapp"

print_map config

Combinar dos mapas con un nameref

Como los namerefs proporcionan a la función un alias activo, puede implementar utilidades como la combinación de mapas de forma clara. Pase por nombre el mapa de destino y uno o más mapas de origen; la función recorre las claves de origen y las escribe en el destino.

Los namerefs permiten escribir una única función genérica merge_map que funcione con cualquier array asociativo del script.

#!/usr/bin/env bash

merge_map() {
    # merge_map dest src
    declare -n _dest="$1"
    declare -n _src="$2"
    for key in "${!_src[@]}"; do
        _dest["$key"]="${_src[$key]}"
    done
}

declare -A defaults=([timeout]=30 [retries]=3 [verbose]=false)
declare -A overrides=([retries]=5 [log_level]=debug)

merge_map defaults overrides

for k in "${!defaults[@]}"; do
    echo "$k = ${defaults[$k]}"
done

Expansión indirecta: compatibilidad con versiones antiguas de Bash

Si debe admitir Bash 4.0–4.2 (o los shells predeterminados antiguos de macOS), declare -n no está disponible. La alternativa es la expansión indirecta mediante ${!varname}.

La expansión indirecta interpreta el valor de varname como un nombre de variable y devuelve el valor de esa variable. Para los arrays, se combina con una sintaxis especial para obtener todos los elementos.

Limitaciones frente a los namerefs:

  • En la práctica, es de solo lectura (para escribir los cambios es necesario usar eval, que conlleva riesgos de inyección)
  • Las claves de los arrays asociativos no se pueden recorrer de forma segura sin eval
  • Use namerefs siempre que sea posible y recurra a la expansión indirecta solo cuando sea necesario
#!/usr/bin/env bash

sum_array() {
    local arr_name="$1"
    # Indirect: build a temporary copy by expanding arr_name[@]
    local -a items
    eval "items=( \"\${${arr_name}[@]}\" )"
    local total=0
    for n in "${items[@]}"; do
        (( total += n ))
    done
    echo "$total"
}

numbers=(10 20 30 40)
result=$(sum_array numbers)
echo "Sum: $result"

Crear una biblioteca modular de scripts

En los proyectos profesionales de Bash, las funciones de utilidad se almacenan en archivos de biblioteca (por ejemplo, lib/array_utils.sh) y los scripts que las necesitan los cargan mediante source.

El patrón es el siguiente:

  • Cada archivo de biblioteca define únicamente funciones, sin efectos secundarios en el nivel superior
  • Evite cargarlo dos veces mediante una variable indicadora: [[ -n $__LIB_LOADED ]] && return; __LIB_LOADED=1
  • Los scripts consumidores usan source o . para cargar la biblioteca
  • Las funciones intercambian datos complejos mediante namerefs, manteniendo toda la lógica encapsulada

El resultado son scripts que se pueden probar, reutilizar y mantener, no simples apaños de shell.

Ejemplo práctico: un analizador de opciones que devuelve un mapa

Un caso de uso real: un analizador de opciones de línea de comandos que rellena un array asociativo proporcionado por quien llama. Quien llama pasa el nombre del array como primer argumento, seguido de los argumentos reales "$@".

Este patrón permite que cualquier script reutilice el mismo analizador sin copiar código ni depender de variables globales.

#!/usr/bin/env bash

parse_opts() {
    declare -n _opts="$1"
    shift
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --host)   _opts[host]="$2";   shift 2 ;;
            --port)   _opts[port]="$2";   shift 2 ;;
            --user)   _opts[user]="$2";   shift 2 ;;
            *)        echo "Unknown option: $1" >&2; shift ;;
        esac
    done
}

declare -A opts=([host]=localhost [port]=5432 [user]=admin)
parse_opts opts --host db.example.com --port 3306

echo "Connecting to ${opts[host]}:${opts[port]} as ${opts[user]}"

Combinar namerefs de entrada y salida en una misma función

Las funciones pueden contener varios namerefs simultáneamente: uno para la entrada y otro para la salida. Esto permite crear funciones de transformación puras: leen una estructura de origen, calculan un resultado y lo escriben en una estructura de destino, todo ello sin modificar el estado global.

Usar nombres distintos con prefijo de guion bajo para cada nameref evita las colisiones, incluso cuando las variables de quien llama tienen nombres sencillos.

#!/usr/bin/env bash

filter_map_by_key_prefix() {
    # Usage: filter_map_by_key_prefix src_map dest_map prefix
    declare -n _src="$1"
    declare -n _dst="$2"
    local prefix="$3"

    _dst=()
    for key in "${!_src[@]}"; do
        if [[ $key == ${prefix}* ]]; then
            _dst["$key"]="${_src[$key]}"
        fi
    done
}

declare -A settings=(
    [db_host]=localhost [db_port]=5432
    [cache_ttl]=300     [cache_driver]=redis
    [app_debug]=true
)
declare -A db_settings

filter_map_by_key_prefix settings db_settings db_

for k in "${!db_settings[@]}"; do
    echo "$k = ${db_settings[$k]}"
done

Compruebe sus conocimientos: namerefs y paso de arrays

Compruebe que comprende cómo pasar arrays y mapas entre funciones en Bash.

Repaso: pasar estructuras de datos complejas en Bash

Esto es lo que ha aprendido en esta lección:

  • Los arrays no se pueden pasar por valor: pase el nombre de la variable como cadena.
  • declare -n ref="$varname" crea un nameref: un alias activo de la variable de quien llama. Tanto las lecturas como las escrituras se aplican a la variable original. Requiere Bash 4.3 o posterior.
  • La colisión de namerefs se produce cuando la variable interna y la externa tienen el mismo nombre. Evítela anteponiendo un prefijo a los namerefs internos (por ejemplo, _arr, _map).
  • Los parámetros de salida funcionan pasando el nombre de un array o mapa de resultados vacío; la función lo rellena mediante un nameref.
  • Varios namerefs en una misma función permiten realizar transformaciones puras de entrada a salida, sin efectos secundarios globales.
  • La expansión indirecta (${!varname[@]} + eval) es una alternativa antigua para Bash < 4.3, pero conlleva riesgos de inyección y, en la práctica, es de solo lectura.
  • Combine estas técnicas con archivos de biblioteca cargados mediante source para crear bases de código Bash modulares, reutilizables y fáciles de probar.

Preguntas frecuentes

¿La lección «Paso de matrices y mapas asociativos entre funciones» es gratis?

Sí — el texto completo de «Paso de matrices y mapas asociativos entre funciones» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de DevOps Bootcamp, actualiza a CoddyKit PRO. El curso de DevOps Bootcamp incluye 4 lecciones en total.

¿Qué aprenderé en «Paso de matrices y mapas asociativos entre funciones»?

Use namerefs y expansión indirecta para pasar estructuras de datos complejas a las funciones y recuperarlas de forma segura. Practicas DevOps Bootcamp con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar DevOps Bootcamp?

No se requiere experiencia previa. DevOps Bootcamp en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 4 de 4.

¿Cuánto tiempo toma la lección «Paso de matrices y mapas asociativos entre funciones»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de DevOps Bootcamp?

Sí. Cada lección de DevOps Bootcamp incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Diseño de funciones con ámbito local y códigos de retorno
  2. Creación y carga de bibliotecas Bash reutilizables
  3. Análisis de flags y argumentos con getopts
  4. Paso de matrices y mapas asociativos entre funciones
← Volver a DevOps Bootcamp