Oltre agli array associativi, ci sono diversi modi per ottenere variabili dinamiche in Bash. Si noti che tutte queste tecniche presentano rischi, che sono discussi alla fine di questa risposta.
Negli esempi seguenti assumerò che i=37
e che si desidera alias della variabile denominata il var_37
cui valore iniziale è lolilol
.
Metodo 1. Utilizzo di una variabile "puntatore"
Puoi semplicemente memorizzare il nome della variabile in una variabile indiretta, non diversamente da un puntatore C. Bash ha quindi una sintassi per leggere la variabile con alias: si ${!name}
espande al valore della variabile il cui nome è il valore della variabile name
. Puoi pensarlo come un'espansione in due fasi: si ${!name}
espande in $var_37
, che si espande in lolilol
.
name="var_$i"
echo "$name" # outputs “var_37”
echo "${!name}" # outputs “lolilol”
echo "${!name%lol}" # outputs “loli”
# etc.
Sfortunatamente, non esiste sintassi di controparte per la modifica la variabile con alias. Invece, puoi ottenere un incarico con uno dei seguenti trucchi.
1 bis. Assegnare coneval
eval
è malvagio, ma è anche il modo più semplice e portatile per raggiungere il nostro obiettivo. Devi scappare con cura dal lato destro dell'incarico, poiché verrà valutato due volte . Un modo semplice e sistematico per farlo è quello di valutare in anticipo il lato destro (o di usarloprintf %q
).
E dovresti verificare manualmente che il lato sinistro sia un nome di variabile valido o un nome con indice (e se fosse evil_code #
?). Al contrario, tutti gli altri metodi seguenti lo applicano automaticamente.
# check that name is a valid variable name:
# note: this code does not support variable_name[index]
shopt -s globasciiranges
[[ "$name" == [a-zA-Z_]*([a-zA-Z_0-9]) ]] || exit
value='babibab'
eval "$name"='$value' # carefully escape the right-hand side!
echo "$var_37" # outputs “babibab”
Svantaggi:
- non verifica la validità del nome della variabile.
eval
è malvagio.
eval
è malvagio.
eval
è malvagio.
1b. Assegnare conread
Il read
builtin ti consente di assegnare valori a una variabile di cui dai il nome, un fatto che può essere sfruttato insieme alle stringhe qui:
IFS= read -r -d '' "$name" <<< 'babibab'
echo "$var_37" # outputs “babibab\n”
La IFS
parte e l'opzione -r
assicurano che il valore sia assegnato così com'è, mentre l'opzione -d ''
consente di assegnare valori multilinea. A causa di quest'ultima opzione, il comando restituisce con un codice di uscita diverso da zero.
Si noti che, poiché stiamo usando una stringa qui, un valore di nuova riga viene aggiunto al valore.
Svantaggi:
- un po 'oscuro;
- ritorna con un codice di uscita diverso da zero;
- aggiunge una nuova riga al valore.
1 quater. Assegnare conprintf
A partire da Bash 3.1 (rilasciato nel 2005), l' printf
integrato può anche assegnare il suo risultato a una variabile il cui nome è dato. Contrariamente alle soluzioni precedenti, funziona, non è necessario alcuno sforzo aggiuntivo per sfuggire alle cose, prevenire la scissione e così via.
printf -v "$name" '%s' 'babibab'
echo "$var_37" # outputs “babibab”
Svantaggi:
- Meno portatile (ma, bene).
Metodo 2. Utilizzo di una variabile di "riferimento"
Dalla versione 4.3 di Bash (rilasciata nel 2014), l' declare
integrato ha un'opzione -n
per creare una variabile che è un "riferimento al nome" per un'altra variabile, proprio come i riferimenti C ++. Proprio come nel Metodo 1, il riferimento memorizza il nome della variabile con alias, ma ogni volta che si accede al riferimento (sia per la lettura che per l'assegnazione), Bash risolve automaticamente il riferimento indiretto.
Inoltre, Bash ha un particolare e sintassi molto confusa per ottenere il valore del riferimento stesso, giudice da soli: ${!ref}
.
declare -n ref="var_$i"
echo "${!ref}" # outputs “var_37”
echo "$ref" # outputs “lolilol”
ref='babibab'
echo "$var_37" # outputs “babibab”
Questo non evita le insidie spiegate di seguito, ma almeno rende la sintassi semplice.
Svantaggi:
rischi
Tutte queste tecniche di aliasing presentano diversi rischi. Il primo esegue il codice arbitrario ogni volta che si risolve l'indirizzamento indiretto (per la lettura o l'assegnazione) . In effetti, invece di un nome di variabile scalare, come var_37
, puoi anche alias un indice di array, come arr[42]
. Ma Bash valuta il contenuto delle parentesi quadre ogni volta che è necessario, quindi l'aliasing arr[$(do_evil)]
avrà effetti inaspettati ... Di conseguenza, usi queste tecniche solo quando controlli la provenienza dell'alias .
function guillemots() {
declare -n var="$1"
var="«${var}»"
}
arr=( aaa bbb ccc )
guillemots 'arr[1]' # modifies the second cell of the array, as expected
guillemots 'arr[$(date>>date.out)1]' # writes twice into date.out
# (once when expanding var, once when assigning to it)
Il secondo rischio è la creazione di un alias ciclico. Poiché le variabili di Bash sono identificate dal loro nome e non dal loro ambito, è possibile che inavvertitamente si crei un alias su se stesso (pensando che sarebbe alias di una variabile da un ambito racchiuso). Ciò può accadere in particolare quando si utilizzano nomi di variabili comuni (come var
). Di conseguenza, utilizzare queste tecniche solo quando si controlla il nome della variabile con alias .
function guillemots() {
# var is intended to be local to the function,
# aliasing a variable which comes from outside
declare -n var="$1"
var="«${var}»"
}
var='lolilol'
guillemots var # Bash warnings: “var: circular name reference”
echo "$var" # outputs anything!
Fonte: