Más allá de las matrices asociativas, hay varias formas de lograr variables dinámicas en Bash. Tenga en cuenta que todas estas técnicas presentan riesgos, que se analizan al final de esta respuesta.
En los siguientes ejemplos, supondré que i=37
y desea alias la variable var_37
cuyo nombre es el valor inicial lolilol
.
Método 1. Usando una variable "puntero"
Simplemente puede almacenar el nombre de la variable en una variable de indirección, no muy diferente de un puntero en C. Bash luego tiene una sintaxis para leer la variable con alias: se ${!name}
expande al valor de la variable cuyo nombre es el valor de la variable name
. Puedes pensarlo como una expansión de dos etapas: se ${!name}
expande a $var_37
, que se expande a lolilol
.
name="var_$i"
echo "$name" # outputs “var_37”
echo "${!name}" # outputs “lolilol”
echo "${!name%lol}" # outputs “loli”
# etc.
Desafortunadamente, no hay una sintaxis equivalente para modificar la variable con alias. En cambio, puede lograr la asignación con uno de los siguientes trucos.
1a. Asignando coneval
eval
es malo, pero también es la forma más simple y portátil de lograr nuestro objetivo. Debe escapar con cuidado del lado derecho de la tarea, ya que se evaluará dos veces . Una manera fácil y sistemática de hacer esto es evaluar el lado derecho de antemano (o usar printf %q
).
Y debe verificar manualmente que el lado izquierdo es un nombre de variable válido o un nombre con índice (¿y si lo fuera evil_code #
?). Por el contrario, todos los otros métodos a continuación lo hacen cumplir automáticamente.
# 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”
Desventajas:
- no verifica la validez del nombre de la variable.
eval
es malvado
eval
es malvado
eval
es malvado
1b. Asignando conread
La función read
incorporada le permite asignar valores a una variable a la que le da el nombre, un hecho que puede explotarse junto con las siguientes cadenas:
IFS= read -r -d '' "$name" <<< 'babibab'
echo "$var_37" # outputs “babibab\n”
La IFS
parte y la opción -r
aseguran que el valor se asigne tal cual, mientras que la opción -d ''
permite asignar valores de varias líneas. Debido a esta última opción, el comando regresa con un código de salida distinto de cero.
Tenga en cuenta que, dado que estamos usando una cadena aquí, se agrega un carácter de nueva línea al valor.
Desventajas:
- algo oscuro;
- regresa con un código de salida distinto de cero;
- agrega una nueva línea al valor.
1c. Asignando conprintf
Desde Bash 3.1 (lanzado en 2005), el printf
incorporado también puede asignar su resultado a una variable cuyo nombre se le da. A diferencia de las soluciones anteriores, simplemente funciona, no se necesita ningún esfuerzo adicional para escapar de las cosas, evitar la división, etc.
printf -v "$name" '%s' 'babibab'
echo "$var_37" # outputs “babibab”
Desventajas:
- Menos portátil (pero, bueno).
Método 2. Usando una variable de "referencia"
Desde Bash 4.3 (lanzado en 2014), el declare
incorporado tiene una opción -n
para crear una variable que es una "referencia de nombre" a otra variable, muy similar a las referencias de C ++. Al igual que en el Método 1, la referencia almacena el nombre de la variable con alias, pero cada vez que se accede a la referencia (ya sea para leerla o asignarla), Bash resuelve automáticamente la indirección.
Además, Bash tiene un especial y una sintaxis muy confuso para conseguir el valor de la misma referencia, juez en solitario: ${!ref}
.
declare -n ref="var_$i"
echo "${!ref}" # outputs “var_37”
echo "$ref" # outputs “lolilol”
ref='babibab'
echo "$var_37" # outputs “babibab”
Esto no evita las trampas que se explican a continuación, pero al menos simplifica la sintaxis.
Desventajas:
Riesgos
Todas estas técnicas de alias presentan varios riesgos. El primero es ejecutar código arbitrario cada vez que resuelve la indirección (ya sea para leer o para asignar) . De hecho, en lugar de un nombre de variable escalar, como var_37
, también puede alias un subíndice de matriz, como arr[42]
. Pero Bash evalúa el contenido de los corchetes cada vez que se necesita, por lo que el alias arr[$(do_evil)]
tendrá efectos inesperados ... Como consecuencia, solo use estas técnicas cuando controle la procedencia del 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)
El segundo riesgo es crear un alias cíclico. Como las variables de Bash se identifican por su nombre y no por su alcance, puede crear inadvertidamente un alias para sí mismo (mientras piensa que alias una variable de un alcance adjunto). Esto puede suceder en particular cuando se usan nombres de variables comunes (como var
). Como consecuencia, solo use estas técnicas cuando controle el nombre de la variable 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!
Fuente: