2012-10-08 21 views
5

Todo el mundo dice que eval es malo, y deberías usar $() como reemplazo. Pero me encontré con una situación en la que el sin comillas no se maneja igual dentro de $().

El fondo es que me han quemado demasiado a menudo por rutas de archivos con espacios en ellas, y me gusta citar todas esas rutas. Más paranoia sobre querer saber de dónde vienen todos mis ejecutables. Aún más paranoico, no confío en mí mismo, y me gusta poder mostrar los comandos creados que estoy a punto de ejecutar.

A continuación voy a tratar variaciones en el uso de eval vs. $(), y si el nombre del comando es citado (primo que podría contener espacios)

BIN_LS="/bin/ls" 
    thefile="arf" 
    thecmd="\"${BIN_LS}\" -ld -- \"${thefile}\"" 

    echo -e "\n Running command '${thecmd}'" 
    $($thecmd) 

      Running command '"/bin/ls" -ld -- "arf"' 
     ./foo.sh: line 8: "/bin/ls": No such file or directory 

    echo -e "\n Eval'ing command '${thecmd}'" 
    eval $thecmd 

      Eval'ing command '"/bin/ls" -ld -- "arf"' 
     /bin/ls: cannot access arf: No such file or directory 

    thecmd="${BIN_LS} -ld -- \"${thefile}\"" 

    echo -e "\n Running command '${thecmd}'" 
    $($thecmd) 

      Running command '/bin/ls -ld -- "arf"' 
     /bin/ls: cannot access "arf": No such file or directory 

    echo -e "\n Eval'ing command '${thecmd}'" 
    eval $thecmd 

      Eval'ing command '/bin/ls -ld -- "arf"' 
     /bin/ls: cannot access arf: No such file or directory 

    $("/bin/ls" -ld -- "${thefile}") 

     /bin/ls: cannot access arf: No such file or directory 

Así que ... esto es confuso. Una ruta de comando entre comillas es válida en todas partes, excepto dentro de un constructo $() A más corto, el ejemplo más directo:

$ c="\"/bin/ls\" arf" 
$ $($c) 
-bash: "/bin/ls": No such file or directory 
$ eval $c 
/bin/ls: cannot access arf: No such file or directory 
$ $("/bin/ls" arf) 
/bin/ls: cannot access arf: No such file or directory 
$ "/bin/ls" arf 
/bin/ls: cannot access arf: No such file or directory 

¿Cómo se explica el caso simple $($c)?

Respuesta

3

Con el hecho de que no tiene sentido en primer lugar. Use an array instead.

$ c=("/bin/ls" arf) 
$ "${c[@]}" 
/bin/ls: cannot access arf: No such file or directory 
+0

Las cadenas sin cotejar se evalúan inmediatamente. c = ("/ bin/ls"/bin/l *) hace algo bastante diferente de lo deseado. (mira con echo "'$ {c [@]}) c = ("/bin/ls ""/bin/l * ") luego z = $ (" $ {c [@]} ") falla. ¿No se puede usar una buena cantidad de argumentos con $()? – Shenme

+1

Sin embargo, c = ("/ bin/ls" "/ bin/l *") luego z = $ ($ {c [@]}) parece funcionar bien. ¿Es este el ejecutable de $() que pretendías mostrarme? – Shenme

5

El uso de " citar las palabras es parte de su interacción con Bash. Al escribir

$ "/bin/ls" arf 

en el indicador, o en un script, usted está diciendo que el comando Bash consiste en las palabras /bin/ls y arf, y las comillas dobles están realmente haciendo hincapié en que /bin/ls es una sola palabra.

Al escribir

$ eval '"/bin/ls" arf' 

usted está diciendo a Bash que el comando consiste en las palabras eval y "/bin/ls" arf. Puesto que el propósito de eval es pretender que su argumento es un comando real-intervención humana, esto es equivalente a ejecutar

$ "/bin/ls" arf 

y la " se procesa igual que en el indicador.

Tenga en cuenta que este simulacro es específico de eval; Bash no suele hacer todo lo posible para pretender que algo era un comando real de tipo humano.

Al escribir

$ c='"/bin/ls" arf' 
$ $c 

la $c consigue sustituido, y luego se somete a división de palabras (ver §3.5.7 "Word Splitting" in the Bash Reference Manual), por lo que las palabras de la orden son "/bin/ls" (nótese las comillas dobles!) Y arf. No hace falta decir que esto no funciona. (Tampoco es muy seguro, ya que además de división de palabras, $c también se somete a expansión de nombre de archivo y otras cosas. Por lo general, las expansiones de tus parámetros siempre deben estar entre comillas dobles, y si no pueden ser así, debes reescribir tu código para que puedan ser. Las expansiones de parámetros sin cita están pidiendo problemas.)

Al escribir

$ c='"/bin/ls" arf' 
$ $($c) 

esto es lo mismo que antes, excepto que ahora también estamos tratando de utilizar la salida de la orden de no trabajo como un nuevo comando . Huelga decir que eso no hace que el comando que no funciona funcione de repente.

como apunta Ignacio Vazquez-Abrams en su respuesta, la solución es utilizar una matriz, y manejar la cita correctamente:

$ c=("/bin/ls" arf) 
$ "${c[@]}" 

que establece c a una matriz con dos elementos, /bin/ls y arf, y usa esos dos elementos como la palabra de un comando.

+0

(detesta el límite de 5 min.) La última línea no muestra la ejecución de un comando empaquetado en un formato donde se puede capturar la salida (que aunque no se dijo en la pregunta original) Try c = ("/ bin/ls" "/ bin/l * ") y c = ("/bin/ls "/ bin/l *) con echo" '$ {c [@]}' ". Este método no me permitirá construir una matriz con argumentos entre comillas para su uso posterior con $() ...? – Shenme

+1

@Shenme: Hasta donde puedo decir, lo que estás diciendo es, "Quiero usar' eval', porque quiero procesar una cadena arbitraria como un comando Bash ". Lo que todos los demás dicen es que "eval" es malo, porque procesa una cadena arbitraria como un comando Bash."Estás tratando de conciliar estos puntos de vista preguntando:" ¿Hay alguna forma * no * -evil de hacer esta cosa malvada? ", Pero eso no funcionará. Los puntos de vista son fundamentalmente irreconciliables. Para hacer algo malvado, usa una herramienta malvada, o, para evitar la herramienta malvada, reescriba su secuencia de comandos para evitar hacer lo malo. – ruakh

+0

No, no hay comandos arbitrarios, no hay malas intenciones. :-) Sólo quería construir un comando y ejecutarlo de forma controlada Controlaré las entradas, e incluso soy lo suficientemente paranoico como para usar '-'. La gente ve las respuestas simplistas a meta preguntas complejas y te critica si 'no' usas eval, así que quería evitar la crítica. - (De todos modos, z = $ ($ {c [@]}) funciona bien – Shenme

2

Desde el man page for bash, con respecto eval:

eval [arg ...]: Los args se leen y se concatenan en un solo comando. Este comando luego es leído y ejecutado por el shell, y su estado de salida se devuelve como el valor de eval.

Cuando c se define como "\"/bin/ls\" arf", las cotizaciones externas provocarán toda la cosa para ser procesado como primer argumento a eval, que se espera que sea un comando o programa. Debe pasar sus argumentos eval de tal manera que el comando objetivo y sus argumentos se enumeren por separado.

La construcción $(...) se comporta de manera diferente a eval porque no es un comando que lleva argumentos. Puede procesar el comando completo a la vez en lugar de procesar los argumentos de uno en uno.

Una nota sobre su premisa original: La razón principal por la que las personas dicen que eval es malo fue porque es comúnmente utilizada por los scripts para ejecutar una cadena proporcionada por el usuario como un comando de shell. Aunque a veces es útil, este es un problema de seguridad mayor (normalmente no hay una forma práctica de verificar la cadena de seguridad antes de ejecutarla). El problema de seguridad no se aplica si está usando eval en cadenas codificadas dentro de la secuencia de comandos, como lo está haciendo. Sin embargo, normalmente es más fácil y más limpio usar $(...) o `...` dentro de las secuencias de comandos para la sustitución de comandos, sin dejar ningún caso de uso real para eval.

+0

Consejo adicional: cuando intentamos ver cómo el shell ve un comando expandido, usar 'set -v 'a veces puede dar resultados más precisos que' echo'. – bta

+0

así que básicamente, 'eval' es seguro si la cadena evaluada no está expuesta de ninguna manera a la entrada del usuario; pero si fallamos en cualquier punto al prevenir eso, puede ser explotado; por lo tanto, usar '' ... '' o '$ (...)' requiere menos precauciones en comparación con 'eval'; ¡hombre! este fue el consejo específico que estaba buscando antes de cambiar mis scripts, jeje! :) –