Ejecutar un comando almacenado en una variable.

9

Tengo un comando almacenado en una variable. Vamos a pretender la variable $i tiene el valor:

cat -nT index.php |grep 'someregex'

Cuando trato de ejecutar la variable anterior escribiendo $i falla porque el shell intenta ejecutar la variable completa como un comando. También traté de usar eval($i) y poniendo $i en backticks.

¿Cómo puedo hacer que el shell se ejecute? $i ¿Como si fuera una orden? ¿Y por qué no está funcionando igual que?

$i='echo hi'; $i

¿Es porque tuve que hackear las comillas simples? (Porque no puedes anidarlos). Actualmente mi solución es

echo $i > /foo;  . /foo

Pero no quiero crear un archivo solo para esto, solo para borrarlo más tarde.

Lo que quiero decir con "pirateé las comillas simples es que hice

$i='cat index.php | grep -P '"'"'MYREGEXHERE'"'"
Leathan
fuente
4
La pregunta es, ¿por qué el comando se almacena en una variable en primer lugar? Sería mejor armar el comando cuando lo ejecute, almacenando los argumentos de las opciones en variables o en una matriz. ¿Puedes mostrar todo el script que tienes?
slhck
La misma causa raíz: superuser.com/questions/360966/…
Ciro Santilli 新疆改造中心 六四事件 法轮功

Respuestas:

15

Respuesta corta: ver BashAQ # 50: Estoy tratando de poner un comando en una variable, ¡pero los casos complejos siempre fallan! .

Respuesta larga: es debido al orden en que bash analiza la línea de comando. Específicamente, busca cosas como tuberías y redirecciones antes de expandir los valores variables, y no retrocede y vuelve a buscar tuberías, etc. en los valores expandidos. Esencialmente, las variables se sustituyen aproximadamente a la mitad del proceso de análisis, por lo que el valor solo se analiza a la mitad antes de ejecutarse.

Para resolver esto, necesita responder la pregunta de @ slhck: ¿por qué el comando se almacena en una variable en primer lugar? ¿Cuál es el problema real que estás tratando de resolver? Dependiendo del objetivo real, hay una serie de soluciones posibles:

  • No lo almacene en una variable, solo ejecútelo directamente. El almacenamiento de comandos para su uso posterior es complicado, y si realmente no lo necesita, simplemente no lo haga.

  • Usa una función en lugar de una variable. Para eso son, después de todo:

    i() { cat -nT index.php |grep 'someregex'; }
    i
    

    La principal desventaja de esto es que no puede crear la función dinámicamente, no puede incluir o excluir condicionalmente el código de la función (aunque la función en sí puede tener elementos condicionales que se seleccionan cuando se ejecuta).

  • Utilizar eval. Esto debe considerarse un último recurso, ya que es fácil tener un comportamiento inesperado. Básicamente, ejecuta el comando a través de otro pase de análisis completo, por lo que todas las canalizaciones, etc. obtienen su significado completo, pero también significa que las partes del comando que pensaste que eran solo datos también se analizarán y tal vez se ejecuten. Los nombres de archivo que contienen metacaracteres de shell (canalización, punto y coma, comillas / apóstrofes, etc.) pueden tener efectos extraños y, a veces, peligrosos. Si usas eval, al menos doble-comente la cadena, de lo contrario su contenido esencialmente se analiza una vez y media, con resultados aún más extraños.

    i="cat -nT index.php |grep 'someregex'"
    eval "$i"
    

EDITAR: Otro enfoque estándar de almacenar una orden de una variable es usar una matriz en lugar de una variable simple. Esto le permite almacenar un comando con argumentos complejos (por ejemplo, que contienen espacios) sin problemas, pero no almacenará elementos como tuberías y redirecciones. Por lo tanto, el enfoque de matriz no será útil en este caso particular.

Gordon Davisson
fuente
+1 para el eval método. Esto me impidió hacer la misma pregunta :). Tengo algo como sudo rsync -aAHXi -n --delete-excluded --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/lost+found","/mnt/DATA/*","/var/log/*","/var/swap","/var/cache/apt/archives/*.deb"} -e ssh root@piac_wireless:/ /home/mrx/Docs/RPi/backup/piac/piac_usb-root que no se estaba ejecutando las exclusiones correctamente.
dentex
@dentex Recomiendo encarecidamente que no uses eval para esto, hay demasiadas maneras para que salga mal. Una matriz sería una mejor manera de hacer esto. Ver aquí , aquí y aquí por ejemplo.
Gordon Davisson
Gracias por la sugerencia. Intentaré implementar la solución con la matriz para pasar la lista de exclusión a rsync.
dentex
4

Esto debería funcionar:

a="cat -nT index.php |grep 'someregex'"
eval "$a"

Ten cuidado con eso eval presenta posibles vulnerabilidades y situaciones de comportamiento inesperado, por lo que debe usarse, si es que lo hace, con cuidado adicional.

jlliagre
fuente
Si usas eval, realmente necesitas usar comillas dobles ( eval "$i" ) para evitar efectos extra extra extraños. Por ejemplo, intente esto con a="cat -nT index.php |grep ' .* '" (es decir, la expresión regular debe hacer coincidir dos espacios con cualquier secuencia de caracteres entre ellos) y ver qué sucede realmente. Para crédito adicional, explique por qué Sucede.
Gordon Davisson
@GordonDavisson comillas dobles añadidas.
jlliagre
3

Al declarar la variable, no debe usar el signo de dólar como prefijo.

Prueba esto:

i='echo hi'; $i
miq
fuente
Eso es correcto, pero no es una respuesta a la pregunta del OP, en realidad.
slhck
es para mi REALY: P
nwgat