Passaggio di array e mappe associative tra funzioni
Usi nameref ed espansione indiretta per passare in sicurezza strutture dati complesse dentro e fuori dalle funzioni.
Passaggio di array e mappe associative tra funzioni è una lezione DevOps Bootcamp gratuita su CoddyKit. Questa è la lezione 4 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento DevOps Bootcamp, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso DevOps Bootcamp include 4 lezioni in totale.
Perché passare gli array alle funzioni è complesso
In Bash, gli array e gli array associativi non sono valori di prima classe. Quando si prova a passare un array a una funzione, non si può semplicemente usare $my_array: in questo modo viene espanso solo il primo elemento.
Consideri questo tentativo non funzionante:
my_func "${my_array[@]}"La funzione riceve un elenco piatto di parole. Non ha modo di sapere dove termina un elemento e dove inizia quello successivo, e perde completamente la struttura dell'array.
Bash mette a disposizione due meccanismi affidabili per passare strutture dati complesse:
- Nameref (
declare -n): un riferimento a un'altra variabile tramite il suo nome (Bash 4.3+) - Espansione indiretta (
${!varname[@]}): consente di accedere a una variabile il cui nome è memorizzato in un'altra variabile (versioni precedenti di Bash)
Questa lezione illustra entrambe le tecniche, così potrà scrivere funzioni robuste e riutilizzabili che lavorano con array e mappe.
Passare un array tramite il nome usando un nameref
L'approccio moderno più semplice è il nameref: si passa il nome dell'array come stringa e, all'interno della funzione, si dichiara una variabile locale che vi fa riferimento.
Sintassi fondamentale:
declare -n ref="$1":refdiventa un alias per qualunque nome di variabile sia stato passato come primo argomento- Si legge, si scorre e si modifica
refesattamente come se fosse l'array originale
In questo modo si evita di copiare l'intero array e la funzione può lavorare con array di qualsiasi dimensione.
#!/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 fruitsModificare l'array del chiamante all'interno di una funzione
Poiché un nameref è un vero alias, le scritture effettuate tramite il nameref modificano la variabile originale del chiamante. Non è necessario restituire i valori con echo né usare variabili globali.
Questo rende i nameref ideali per le funzioni che trasformano gli array direttamente, ad esempio normalizzando le stringhe, filtrando gli elementi o ordinandoli.
#!/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[@]}"Restituire un nuovo array tramite un nameref
Una funzione può anche scrivere in un array fornito dal chiamante accettandone il nome come parametro di output. Il chiamante crea un array vuoto, ne passa il nome e la funzione lo riempie.
Questo schema ricalca il classico idioma del C che consiste nel passare un puntatore a un buffer per il risultato, con la differenza che qui viene realizzato interamente nella 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 trappola della collisione dei nameref
Con i nameref esiste un rischio critico: se la variabile nameref locale ha lo stesso nome della variabile del chiamante, Bash entra in un ciclo infinito di autoriferimento e lo script ha esito negativo.
Ad esempio, se il chiamante assegna all'array il nome arr e la funzione esegue declare -n arr="$1", il nameref arr punta a sé stesso.
Convenzione per evitare il problema: anteporre un carattere di sottolineatura alle variabili nameref interne (ad esempio _arr, _ref, _out). È estremamente raro che i chiamanti usino nomi di questo tipo, quindi le collisioni diventano molto improbabili.
#!/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 arrPassare gli array associativi (mappe) tramite il nome
Gli array associativi (declare -A) funzionano allo stesso modo con i nameref. Si passa il nome della mappa come stringa e si dichiara un nameref all'interno della funzione: si ottiene pieno accesso in lettura e scrittura a tutte le coppie chiave-valore.
È indispensabile per le mappe di configurazione, i parser di opzioni e le tabelle di ricerca che devono essere condivisi tra le funzioni di una libreria.
#!/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 configUnire due mappe con un nameref
Poiché i nameref forniscono alla funzione un alias collegato direttamente alla variabile, è possibile implementare in modo ordinato utilità come l'unione di mappe. Si passano il nome della mappa di destinazione e quello di una o più mappe sorgente; la funzione scorre le chiavi delle sorgenti e le scrive nella destinazione.
I nameref permettono di scrivere un'unica funzione generica merge_map che funziona con qualsiasi array associativo presente nello 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]}"
doneEspansione indiretta: compatibilità con le versioni precedenti di Bash
Se è necessario supportare Bash 4.0–4.2 (o le shell predefinite meno recenti di macOS), declare -n non è disponibile. L'alternativa è l'espansione indiretta con ${!varname}.
L'espansione indiretta interpreta il valore di varname come nome di una variabile e restituisce il valore di quella variabile. Per gli array la si combina con una sintassi speciale per ottenere tutti gli elementi.
Limitazioni rispetto ai nameref:
- In pratica è di sola lettura (per riscrivere il valore è necessario usare
eval, che comporta rischi di injection) - Non è possibile scorrere in modo sicuro le chiavi degli array associativi senza
eval - Usi i nameref quando possibile; ricorra all'espansione indiretta solo quando è necessario
#!/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"Creare una libreria di script modulare
Nei progetti Bash professionali, le funzioni di utilità vengono memorizzate in file di libreria (ad esempio lib/array_utils.sh) e importate dagli script che ne hanno bisogno.
Lo schema è il seguente:
- Ogni file di libreria definisce esclusivamente funzioni, senza effetti collaterali a livello superiore
- Si impedisce il caricamento multiplo usando una variabile sentinella:
[[ -n $__LIB_LOADED ]] && return; __LIB_LOADED=1 - Gli script utilizzatori caricano la libreria con
sourceo. - Le funzioni scambiano dati complessi tramite nameref, mantenendo tutta la logica autonoma
Il risultato sono script testabili, riutilizzabili e manutenibili, non soluzioni improvvisate e usa e getta in shell.
Esempio pratico: un parser di opzioni che restituisce una mappa
Un caso d'uso reale è un parser di opzioni della riga di comando che riempie un array associativo fornito dal chiamante. Il chiamante passa il nome dell'array come primo argomento, seguito dagli argomenti effettivi "$@".
Questo schema permette a qualsiasi script di riutilizzare lo stesso parser senza duplicare il codice né dipendere da variabili globali.
#!/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]}"Combinare nameref di input e di output in un'unica funzione
Le funzioni possono contenere più nameref contemporaneamente: uno per l'input e uno per l'output. Questo consente di creare funzioni di trasformazione pure: leggono da una struttura sorgente, calcolano un risultato e lo scrivono in una struttura di destinazione, senza mai modificare lo stato globale.
Usare nomi distinti preceduti da un carattere di sottolineatura per ogni nameref impedisce le collisioni anche quando le variabili del chiamante hanno nomi semplici.
#!/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]}"
doneVerifica delle conoscenze: nameref e passaggio degli array
Verifichi di aver compreso come passare array e mappe tra le funzioni in Bash.
Riepilogo: passare strutture dati complesse in Bash
Ecco che cosa ha imparato in questa lezione:
- Gli array non possono essere passati per valore: passi invece il nome della variabile come stringa.
declare -n ref="$varname"crea un nameref, cioè un alias collegato direttamente alla variabile del chiamante. Sia le letture sia le scritture vengono applicate alla variabile originale. Richiede Bash 4.3+.- La collisione dei nameref si verifica quando la variabile interna e quella esterna hanno lo stesso nome. La si previene anteponendo un carattere di sottolineatura ai nameref interni (ad esempio
_arr,_map). - I parametri di output funzionano passando il nome di un array o di una mappa vuoti come risultato; la funzione li riempie tramite un nameref.
- Più nameref nella stessa funzione consentono trasformazioni pure dall'input all'output, senza effetti collaterali globali.
- L'espansione indiretta (
${!varname[@]}+eval) è un'alternativa precedente per Bash < 4.3, ma comporta rischi di injection ed è, in pratica, di sola lettura. - Combini queste tecniche con file di libreria caricati tramite source per creare basi di codice Bash modulari, riutilizzabili e testabili.
Domande Frequenti
La lezione «Passaggio di array e mappe associative tra funzioni» è gratuita?
Sì — il testo completo di «Passaggio di array e mappe associative tra funzioni» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso DevOps Bootcamp, passa a CoddyKit PRO. Il corso DevOps Bootcamp include 4 lezioni in totale.
Cosa imparerò in «Passaggio di array e mappe associative tra funzioni»?
Usi nameref ed espansione indiretta per passare in sicurezza strutture dati complesse dentro e fuori dalle funzioni. Eserciti DevOps Bootcamp con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare DevOps Bootcamp?
Non è richiesta alcuna esperienza precedente. DevOps Bootcamp su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.
Quanto tempo richiede la lezione «Passaggio di array e mappe associative tra funzioni»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione DevOps Bootcamp?
Sì. Ogni lezione DevOps Bootcamp include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Progettazione di funzioni con ambito locale e codici di ritorno
- Creazione e sourcing di librerie Bash riutilizzabili
- Analisi di flag e argomenti con getopts
- Passaggio di array e mappe associative tra funzioni