Estoy confundido acerca de un script bash.
Tengo el siguiente código:
function grep_search() {
magic_way_to_define_magic_variable_$1=`ls | tail -1`
echo $magic_variable_$1
}
Quiero poder crear un nombre de variable que contenga el primer argumento del comando y que tenga el valor de, por ejemplo, la última línea de ls
.
Entonces, para ilustrar lo que quiero:
$ ls | tail -1
stack-overflow.txt
$ grep_search() open_box
stack-overflow.txt
Entonces, ¿cómo debo definir / declarar $magic_way_to_define_magic_variable_$1
y cómo debo llamarlo dentro del script?
He tratado eval
, ${...}
, \$${...}
, pero todavía estoy confundido.
Respuestas:
Use una matriz asociativa, con nombres de comandos como claves.
Si no puede usar matrices asociativas (por ejemplo, debe admitir
bash
3), puede usardeclare
para crear nombres de variables dinámicas:y use la expansión de parámetros indirectos para acceder al valor.
Ver BashFAQ: Indirección - Evaluación de variables indirectas / de referencia .
fuente
-a
declara una matriz indexada, no una matriz asociativa. A menos que el argumentogrep_search
sea un número, se tratará como un parámetro con un valor numérico (que por defecto es 0 si el parámetro no está configurado).4.2.45(2)
y declare no lo enumera como una opcióndeclare: usage: declare [-afFirtx] [-p] [name[=value] ...]
. Sin embargo, parece estar funcionando correctamente.declare -h
en 4.2.45 (2) para mí muestradeclare: usage: declare [-aAfFgilrtux] [-p] [name[=value] ...]
. Puede verificar que esté ejecutando 4.xy no 3.2.declare $varname="foo"
?${!varname}
es mucho más simple y ampliamente compatibleHe estado buscando una mejor manera de hacerlo recientemente. La matriz asociativa sonó como una exageración para mí. Mira lo que he encontrado:
...y entonces...
fuente
prefix_${middle}_postfix
(es decir, su formato no funcionaríavarname=$prefix_suffix
)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 variablevar_37
cuyo nombre es el valor iniciallolilol
.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 variablename
. Puedes pensarlo como una expansión de dos etapas: se${!name}
expande a$var_37
, que se expande alolilol
.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 con
eval
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 usarprintf %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.Desventajas:
eval
es malvadoeval
es malvadoeval
es malvado1b. Asignando con
read
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: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:
1c. Asignando con
printf
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.Desventajas:
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}
.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, comoarr[42]
. Pero Bash evalúa el contenido de los corchetes cada vez que se necesita, por lo que el aliasarr[$(do_evil)]
tendrá efectos inesperados ... Como consecuencia, solo use estas técnicas cuando controle la procedencia del alias .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 .Fuente:
fuente
${!varname}
técnica requiere una var intermedia paravarname
.El siguiente ejemplo devuelve el valor de $ name_of_var
fuente
echo
s con una sustitución de comando (que no incluye comillas) es innecesario. Además, se-n
debe dar opción aecho
. Y, como siempre,eval
no es seguro. Pero todo esto es innecesario, ya que Bash tiene una sintaxis más seguro, más clara y más corto para este mismo propósito:${!var}
.Esto debería funcionar:
fuente
Esto también funcionará
En tu caso
fuente
Según BashFAQ / 006 , se puede utilizar
read
con aquí sintaxis de cadena para la asignación de las variables indirectas:Uso:
fuente
Utilizar
declare
No es necesario usar prefijos como en otras respuestas, ni matrices. Utilice sólo
declare
, comillas dobles , y la expansión de parámetros .A menudo utilizo el siguiente truco para analizar listas de argumentos que contienen
one to n
argumentos formateados comokey=value otherkey=othervalue etc=etc
, Me gusta:Pero expandiendo la lista argv como
Consejos extra
fuente
printf
oreval
¡Guau, la mayor parte de la sintaxis es horrible! Aquí hay una solución con una sintaxis más simple si necesita hacer referencia indirectamente a matrices:
Para casos de uso más simples, recomiendo la sintaxis descrita en la Guía avanzada de secuencias de comandos Bash .
fuente
foo_1
yfoo_2
están libres de espacios en blanco y símbolos especiales. Ejemplos de entradas problemáticas:'a b'
creará dos entradas dentromine
.''
no creará una entrada dentromine
.'*'
se expandirá al contenido del directorio de trabajo. Puede evitar estos problemas citando:eval 'mine=( "${foo_'"$i"'[@]}" )'
[@]
construcciones."${array[@]}"
siempre se expandirá a la lista correcta de entradas sin problemas como división de palabras o expansión de*
. Además, el problema de división de palabras solo se puede eludirIFS
si conoce algún carácter no nulo que nunca aparece dentro de la matriz. Además, el tratamiento literal de*
no se puede lograr estableciendoIFS
. O estableceIFS='*'
y divide en las estrellas o estableceIFS=somethingOther
y*
expande.|
oLF
como IFS. Una vez más, el problema general en los bucles es que la tokenización ocurre de manera predeterminada, por lo que las citas son la solución especial para permitir cadenas extendidas que contienen tokens. (Se trata de expansión global / de parámetros o cadenas extendidas entre comillas, pero no ambas). Si se necesitan 8 comillas para leer una variable, shell es el idioma incorrecto.Para las matrices indexadas, puede hacer referencia a ellas de esta manera:
Las matrices asociativas se pueden referenciar de manera similar, pero necesitan
-A
activarse endeclare
lugar de-a
.fuente
Un método adicional que no depende de la versión de shell / bash que tiene es mediante el uso
envsubst
. Por ejemplo:fuente
script.sh
expediente:Prueba:
Según
help eval
:También puede usar
${!var}
la expansión indirecta de Bash , como ya se mencionó, sin embargo, no admite la recuperación de índices de matriz.Para más información o ejemplos, consulte BashFAQ / 006 sobre Indirección .
Sin embargo, debe volver a considerar el uso de la indirección según las siguientes notas.
fuente
para el
varname=$prefix_suffix
formato, solo use:fuente