Después de leer las páginas de manual de bash y con respecto a esta publicación .
Todavía tengo problemas para entender qué hace exactamente el eval
comando y cuáles serían sus usos típicos. Por ejemplo si hacemos:
bash$ set -- one two three # sets $1 $2 $3
bash$ echo $1
one
bash$ n=1
bash$ echo ${$n} ## First attempt to echo $1 using brackets fails
bash: ${$n}: bad substitution
bash$ echo $($n) ## Second attempt to echo $1 using parentheses fails
bash: 1: command not found
bash$ eval echo \${$n} ## Third attempt to echo $1 using 'eval' succeeds
one
¿Qué está sucediendo exactamente aquí y cómo el signo de dólar y la barra invertida se relacionan con el problema?
$($n)
se ejecuta$n
en una subshell. Intenta ejecutar el comando1
que no existe.echo $1
eventualmente, no1
. No creo que se pueda hacer usando subcapas.eval
.Respuestas:
eval
toma una cadena como argumento y la evalúa como si la hubiera escrito en una línea de comando. (Si pasa varios argumentos, primero se unen con espacios entre ellos).${$n}
es un error de sintaxis en bash. Dentro de las llaves, solo puede tener un nombre de variable, con algunos posibles prefijos y sufijos, pero no puede tener una sintaxis de bash arbitraria y, en particular, no puede usar la expansión de variables. Sin embargo, hay una manera de decir "el valor de la variable cuyo nombre está en esta variable":$(…)
ejecuta el comando especificado dentro de los paréntesis en un subshell (es decir, en un proceso separado que hereda todas las configuraciones, como los valores variables del shell actual), y recopila su salida. Entonces seecho $($n)
ejecuta$n
como un comando de shell y muestra su salida. Como se$n
evalúa como1
,$($n)
intenta ejecutar el comando1
, que no existe.eval echo \${$n}
ejecuta los parámetros pasados aeval
. Después de la expansión, los parámetros sonecho
y${1}
. Entonceseval echo \${$n}
ejecuta el comandoecho ${1}
.Tenga en cuenta que la mayor parte del tiempo, debe utilizar comillas dobles alrededor de sustituciones de variables y sustituciones de comando (es decir, en cualquier momento hay una
$
):"$foo", "$(foo)"
. Siempre ponga comillas dobles alrededor de las sustituciones de variables y comandos , a menos que sepa que necesita dejarlas. Sin las comillas dobles, el shell realiza la división del campo (es decir, divide el valor de la variable o la salida del comando en palabras separadas) y luego trata cada palabra como un patrón comodín. Por ejemplo:eval
No se usa con mucha frecuencia. En algunos shells, el uso más común es obtener el valor de una variable cuyo nombre no se conoce hasta el tiempo de ejecución. En bash, esto no es necesario gracias a la${!VAR}
sintaxis.eval
sigue siendo útil cuando necesita construir un comando más largo que contenga operadores, palabras reservadas, etc.fuente
eval
recibe una cadena (que puede ser el resultado del análisis y la evaluación) y la interpreta como un fragmento de código.eval eval
. No recuerdo haber sentido la necesidad. Si realmente necesita doseval
pases, usar una variable temporal, que será más fácil de depurar:eval tmp="\${$i}"; eval x="\${$tmp}"
.eval
solo toma el código en una cadena y lo evalúa de acuerdo con las reglas habituales. Técnicamente, ni siquiera es correcto, porque hay algunos casos en los que Bash modifica el análisis para ni siquiera realizar expansiones a los argumentos de eval, pero ese es un dato muy oscuro que dudo que alguien sepa.Simplemente piense en eval como "evaluar su expresión una vez más antes de la ejecución"
eval echo \${$n}
se convierteecho $1
después de la primera ronda de evaluación. Tres cambios para notar:\$
convirtió$
(se necesita la barra diagonal inversa; de lo contrario, intenta evaluar${$n}
, lo que significa una variable denominada{$n}
, que no está permitida)$n
fue evaluado a1
eval
desaparecidosEn la segunda ronda, es básicamente lo
echo $1
que se puede ejecutar directamente.Por
eval <some command>
lo tanto , primero evaluará<some command>
(por evaluar aquí me refiero a variables de sustitución, reemplazar caracteres escapados con los correctos, etc.), y luego ejecutará la expresión resultante una vez más.eval
se usa cuando desea crear dinámicamente variables o leer salidas de programas diseñados específicamente para leerse de esta manera. Consulte http://mywiki.wooledge.org/BashFAQ/048 para ver ejemplos. El enlace también contiene algunas formas típicas en queeval
se utiliza y los riesgos asociados con él.fuente
${VAR}
sintaxis está permitida y se prefiere cuando existe alguna ambigüedad (sí$VAR == $V
, seguidaAR
o$VAR == $VA
seguida deR
).${VAR}
es equivalente a$VAR
. De hecho, su nombre de variable$n
no está permitido.eval eval echo \\\${\${$i}}
hará una triple desreferenciación. No estoy seguro de si hay una manera más sencilla de hacerlo. Además,\${$n}
funciona bien (imprimeone
) en mi máquina ..echo \\\${\${$i}}
impresiones\${${n}}
.eval echo \\\${\${$i}}
es equivalente aecho \${${n}}`` and prints
$ {1}.
eval eval echo \\\ $ {\ $ {$ i}} `es equivalenteeval echo ${1}
e imprimeone
.` escapes the second one, and the third
`se escapa$
después de eso. Entonces se convierte\${${n}}
después de una ronda de evaluación\\
produciendo una barra invertida. Luego\$
ceder un dólar. Y así.En mi experiencia, un uso "típico" de eval es ejecutar comandos que generan comandos de shell para establecer variables de entorno.
Quizás tenga un sistema que use una colección de variables de entorno, y tenga un script o programa que determine cuáles deben establecerse y sus valores. Cada vez que ejecuta un script o programa, se ejecuta en un proceso bifurcado, por lo que todo lo que hace directamente a las variables de entorno se pierde cuando sale. Pero ese script o programa puede enviar los comandos de exportación a stdout.
Sin eval, necesitaría redirigir stdout a un archivo temporal, obtener el archivo temporal y luego eliminarlo. Con eval, puedes simplemente:
Tenga en cuenta que las citas son importantes. Tome este ejemplo (artificial):
fuente
${varname}
. Me parece conveniente usar archivos .conf idénticos en varios servidores diferentes con solo algunas cosas parametrizadas por variables de entorno. Edité / opt / lampp / xampp (que comienza apache) para hacer este tipo de evaluación con un script que se asoma por el sistema y generaexport
sentencias bash para definir variables para los archivos .conf.eval "$(pyenv init -)"
en tu.bash_profile
(o similar) archivo de configuración de shell. Eso construye un pequeño script de shell y luego lo evalúa en el shell actual.La instrucción eval le dice al shell que tome los argumentos de eval como comando y los ejecute a través de la línea de comandos. Es útil en una situación como la siguiente:
En su script, si está definiendo un comando en una variable y luego desea usar ese comando, entonces debe usar eval:
fuente
Actualización: Algunas personas dicen que uno nunca debe usar eval. Estoy en desacuerdo. Creo que el riesgo surge cuando se puede pasar información corrupta
eval
. Sin embargo, hay muchas situaciones comunes en las que eso no es un riesgo y, por lo tanto, vale la pena saber cómo usar eval en cualquier caso. Esta respuesta de stackoverflow explica los riesgos de eval y las alternativas a eval. En última instancia, depende del usuario determinar si / cuando eval es seguro y eficiente de usar.La
eval
instrucción bash le permite ejecutar líneas de código calculadas o adquiridas por su script bash.Quizás el ejemplo más sencillo sería un programa bash que abre otro script bash como un archivo de texto, lee cada línea de texto y lo utiliza
eval
para ejecutarlos en orden. Ese es esencialmente el mismo comportamiento que lasource
declaración bash , que es lo que se usaría, a menos que fuera necesario realizar algún tipo de transformación (por ejemplo, filtrado o sustitución) en el contenido del script importado.Raramente lo he necesitado
eval
, pero me ha resultado útil leer o escribir variables cuyos nombres estaban contenidos en cadenas asignadas a otras variables. Por ejemplo, para realizar acciones en conjuntos de variables, manteniendo la huella de código pequeña y evitando la redundancia.eval
Es conceptualmente simple. Sin embargo, la sintaxis estricta del lenguaje bash y el orden de análisis del intérprete bash pueden matizarse y hacer queeval
parezca críptico y difícil de usar o comprender. Aquí están los elementos esenciales:El argumento pasado a
eval
es una expresión de cadena que se calcula en tiempo de ejecución.eval
ejecutará el resultado analizado final de su argumento como una línea de código real en su script.La sintaxis y el orden de análisis son estrictos. Si el resultado no es una línea ejecutable de código bash, en el alcance de su secuencia de comandos, el programa se bloqueará en la
eval
declaración al intentar ejecutar basura.Al probar, puede reemplazar la
eval
declaración conecho
y ver lo que se muestra. Si es un código legítimo en el contexto actual, ejecutarloeval
funcionará.Los siguientes ejemplos pueden ayudar a aclarar cómo funciona eval ...
Ejemplo 1:
eval
declaración delante del código 'normal' es un NOPEn el ejemplo anterior, las primeras
eval
declaraciones no tienen ningún propósito y pueden eliminarse.eval
no tiene sentido en la primera línea porque no hay un aspecto dinámico en el código, es decir, ya se analizó en las líneas finales del código bash, por lo que sería idéntico a una declaración de código normal en el script bash. El segundo también noeval
tiene sentido, porque, aunque hay un paso de análisis que se convierte$a
en su equivalente de cadena literal, no hay indirección (por ejemplo, no se hace referencia a través del valor de cadena de un sustantivo bash real o una variable de script sostenida por bash), por lo que se comportaría de manera idéntica como una línea de código sin eleval
prefijoEjemplo 2
Realice la asignación var usando los nombres var pasados como valores de cadena.
Si fuera a hacerlo
echo $key=$val
, la salida sería:Que , siendo el resultado final del análisis de cadenas, es lo que ejecutará eval, de ahí el resultado de la declaración de eco al final ...
Ejemplo 3:
Agregar más indirección al Ejemplo 2
Lo anterior es un poco más complicado que el ejemplo anterior, ya que depende más del orden de análisis y de las peculiaridades de bash. La
eval
línea se analizaría internamente aproximadamente en el siguiente orden (tenga en cuenta que las siguientes declaraciones son pseudocódigo, no código real, solo para intentar mostrar cómo la declaración se desglosará internamente en pasos para llegar al resultado final) .Si el orden de análisis supuesto no explica qué eval está haciendo lo suficiente, el tercer ejemplo puede describir el análisis con más detalle para ayudar a aclarar lo que está sucediendo.
Ejemplo 4
Descubra si los vars, cuyos nombres están contenidos en cadenas, contienen valores de cadena.
En la primera iteración:
Bash analiza el argumento
eval
y loeval
ve literalmente en tiempo de ejecución:El siguiente pseudocódigo intenta ilustrar cómo bash interpreta la línea de código real anterior , para llegar al valor final ejecutado por
eval
. (las siguientes líneas son descriptivas, no el código bash exacto):Una vez que se realiza todo el análisis, el resultado es lo que se ejecuta, y su efecto es obvio, lo que demuestra que no hay nada particularmente misterioso sobre
eval
sí mismo, y la complejidad está en el análisis de su argumento.El código restante en el ejemplo anterior simplemente prueba para ver si el valor asignado a $ varval es nulo y, de ser así, solicita al usuario que proporcione un valor.
fuente
Originalmente, nunca aprendí intencionalmente cómo usar eval, porque la mayoría de las personas recomendarán mantenerse alejado de ella como la peste. Sin embargo, recientemente descubrí un caso de uso que me hizo facepalm por no reconocerlo antes.
Si tiene trabajos cron que desea ejecutar de forma interactiva para probar, puede ver el contenido del archivo con cat y copiar y pegar el trabajo cron para ejecutarlo. Desafortunadamente, esto implica tocar el mouse, lo cual es un pecado en mi libro.
Digamos que tiene un trabajo cron en /etc/cron.d/repeatme con el contenido:
*/10 * * * * root program arg1 arg2
No puede ejecutar esto como un script con toda la basura frente a él, pero podemos usar cut para deshacernos de toda la basura, envolverla en una subcapa y ejecutar la cadena con eval
eval $( cut -d ' ' -f 6- /etc/cron.d/repeatme)
El comando de corte solo imprime el sexto campo del archivo, delimitado por espacios. Eval entonces ejecuta ese comando.
Usé un trabajo cron aquí como ejemplo, pero el concepto es formatear el texto desde stdout, y luego evaluar ese texto.
El uso de eval en este caso no es inseguro, porque sabemos exactamente lo que evaluaremos de antemano.
fuente
Recientemente tuve que usar
eval
para forzar que se evaluaran múltiples expansiones de llaves en el orden que necesitaba. Bash hace múltiples expansiones de llaves de izquierda a derecha, así quese expande a
pero necesitaba la segunda expansión de llaves primero, produciendo
Lo mejor que se me ocurrió hacer fue
Esto funciona porque las comillas simples protegen el primer conjunto de llaves de la expansión durante el análisis de la
eval
línea de comando, dejándolas expandidas por la subshell invocada poreval
.Puede haber algún esquema astuto que implique expansiones de llaves anidadas que permita que esto suceda en un solo paso, pero si es así, soy demasiado viejo y estúpido para verlo.
fuente
Preguntaste sobre usos típicos.
Una queja común sobre las secuencias de comandos de shell es que usted (supuestamente) no puede pasar por referencia para recuperar los valores de las funciones.
Pero en realidad, a través de "eval", puede pasar por referencia. El destinatario de la llamada puede devolver una lista de asignaciones de variables para que el llamante las evalúe. Se pasa por referencia porque la persona que llama puede permitir especificar el nombre (s) de la (s) variable (s) resultante (s); consulte el siguiente ejemplo. Los resultados de error pueden ser devueltos nombres estándar como errno y errstr.
Aquí hay un ejemplo de pasar por referencia en bash:
El resultado se ve así:
¡Hay un ancho de banda casi ilimitado en esa salida de texto! Y hay más posibilidades si se usan las múltiples líneas de salida: por ejemplo, la primera línea podría usarse para asignaciones variables, la segunda para 'flujo continuo de pensamiento', pero eso está más allá del alcance de esta publicación.
fuente
Me gusta la respuesta "evaluar su expresión una vez más antes de la ejecución" y me gustaría aclarar con otro ejemplo.
Los curiosos resultados en la Opción 2 son que hubiéramos pasado 2 parámetros de la siguiente manera:
"value
content"
¿Cómo es eso para el contador intuitivo? El adicional
eval
lo arreglará.Adaptado de https://stackoverflow.com/a/40646371/744133
fuente
En la pregunta:
genera errores que afirman que los archivos a y tty no existen. Entendí que esto significa que tty no se está interpretando antes de la ejecución de grep, sino que bash pasó tty como un parámetro a grep, que lo interpretó como un nombre de archivo.
También existe una situación de redireccionamiento anidado, que debe manejarse entre paréntesis coincidentes que deben especificar un proceso secundario, pero bash es primitivamente un separador de palabras, creando parámetros para enviar a un programa, por lo tanto, los paréntesis no se coinciden primero, sino que se interpretan como visto
Me puse específico con grep, y especifiqué el archivo como un parámetro en lugar de usar una tubería. También simplifiqué el comando base, pasando la salida de un comando como un archivo, para que la tubería de E / S no se anidara:
funciona bien.
no es realmente deseado, pero elimina la tubería anidada y también funciona bien.
En conclusión, a bash no parece gustarle el pipping anidado. Es importante comprender que bash no es un programa de nueva ola escrito de manera recursiva. En cambio, bash es un antiguo programa 1,2,3, que se ha agregado con características. Para asegurar la compatibilidad con versiones anteriores, la forma inicial de interpretación nunca se ha modificado. Si bash se reescribe para que coincida entre paréntesis, ¿cuántos errores se introducirán en cuántos programas de bash? Muchos programadores adoran ser crípticos.
fuente