¿Cómo puedo generar argumentos para otro comando mediante la sustitución de comandos?

11

Siguiendo desde: comportamiento inesperado en la sustitución de comandos de shell

Tengo un comando que puede tomar una gran lista de argumentos, algunos de los cuales pueden contener espacios legítimamente (y probablemente otras cosas)

Escribí un script que puede generar esos argumentos para mí, con comillas, pero debo copiar y pegar la salida, por ejemplo

./somecommand
<output on stdout with quoting>
./othercommand some_args <output from above>

Traté de simplificar esto simplemente haciendo

./othercommand $(./somecommand)

y se topó con el comportamiento inesperado mencionado en la pregunta anterior. La pregunta es: ¿se puede usar la sustitución de comandos de manera confiable para generar los argumentos othercommanddado que algunos argumentos requieren comillas y esto no se puede cambiar?

usuario1207217
fuente
Depende de lo que quieras decir con "confiablemente". Si desea que el siguiente comando tome la salida exactamente como aparece en la pantalla y le aplique reglas de shell, entonces tal vez evalpodría usarse, pero generalmente no se recomienda. xargses algo a considerar también
Sergiy Kolodyazhnyy
Me gustaría (esperar) que la salida se somecommandsometa a un análisis de shell regular
user1207217
Como dije en mi respuesta, use algún otro carácter para la división de campos (como :) ... suponiendo que ese carácter no esté confiablemente en la salida.
Olorin
Pero eso no es realmente correcto porque no obedece las reglas de cotización, de eso se trata
user1207217
2
¿Podría publicar un ejemplo del mundo real? Quiero decir, la salida real del primer comando y cómo quieres que interactúe con el segundo comando.
nxnev

Respuestas:

10

Escribí un script que puede generar esos argumentos para mí, con citas

Si la salida se cita correctamente para el shell y confía en la salida , entonces podría ejecutarla eval.

Suponiendo que tiene un shell que admite matrices, sería mejor usar uno para almacenar los argumentos que obtiene.

Si ./gen_args.shproduce resultados como 'foo bar' '*' asdf, entonces podríamos ejecutar eval "args=( $(./gen_args.sh) )"para completar una matriz llamada argscon los resultados. Eso sería los tres elementos foo bar, *, asdf.

Podemos usar "${args[@]}"como de costumbre para expandir los elementos de la matriz individualmente:

$ eval "args=( $(./gen_args.sh) )"
$ for var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

(Tenga en cuenta las comillas. Se "${array[@]}"expande a todos los elementos como argumentos distintos sin modificar. Sin comillas, los elementos de la matriz están sujetos a la división de palabras. Consulte, por ejemplo, la página Arrays en BashGuide ).

Sin embargo , evalejecutará $HOMEcon gusto cualquier sustitución de shell, por lo que en la salida se expandiría a su directorio de inicio, y una sustitución de comando realmente ejecutaría un comando en la ejecución de shell eval. Una salida de "$(date >&2)"crearía un único elemento de matriz vacío e imprimiría la fecha actual en stdout. Esto es preocupante si gen_args.shobtiene los datos de alguna fuente no confiable, como otro host en la red, nombres de archivos creados por otros usuarios. La salida podría incluir comandos arbitrarios. (Si en get_args.shsí mismo fuera malicioso, no necesitaría generar nada, simplemente podría ejecutar los comandos maliciosos directamente).


Una alternativa a la cita de shell, que es difícil de analizar sin eval, sería usar algún otro carácter como separador en la salida de su script. Debería elegir uno que no sea necesario en los argumentos reales.

Vamos a elegir #y tener la salida del script foo bar#*#asdf. Ahora podemos usar la expansión de comandos sin comillas para dividir la salida del comando en los argumentos.

$ IFS='#'                          # split on '#' signs
$ set -f                           # disable globbing
$ args=( $( ./gen_args3.sh ) )     # assign the values to the arrayfor var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

Tendrá que volver a configurarlo IFSmás tarde si depende de la división de palabras en otra parte del script ( unset IFSdebería funcionar para que sea el predeterminado), y también usarlo set +fsi desea usar globbing más adelante.

Si no está utilizando Bash o algún otro shell que tenga matrices, puede usar los parámetros posicionales para eso. Reemplace args=( $(...) )con set -- $(./gen_args.sh)y use en "$@"lugar de "${args[@]}"entonces. (Aquí, también, necesita comillas "$@", de lo contrario los parámetros posicionales están sujetos a la división de palabras).

ilkkachu
fuente
¡Lo mejor de ambos mundos!
Olorin
¿Añadiría un comentario que muestre la importancia de las ${args[@]}
citas
@ user1207217, sí, tienes razón. Es lo mismo con las matrices y "${array[@]}"con "$@". Ambos necesitan ser citados, o la división de palabras divide los elementos de la matriz en partes.
ilkkachu
6

El problema es que una vez que su somecommandscript genera las opciones othercommand, las opciones son realmente solo texto y a merced del análisis estándar del shell (afectado por lo $IFSque sea que sea y qué opciones de shell están vigentes, lo que en el caso general no tener el control sobre).

En lugar de usar somecommandpara generar las opciones, sería más fácil, más seguro y más robusto usarlo para llamar othercommand . La somecommandsecuencia de comandos sería una secuencia de comandos envolvente en othercommandlugar de una especie de secuencia de comandos auxiliar a la que tendría que recordar llamar de alguna manera especial como parte de la línea de comandos otherscript. Los scripts de envoltura son una forma muy común de proporcionar una herramienta que simplemente llama a otra herramienta similar con otro conjunto de opciones (solo verifique filequé comandos en /usr/binrealidad son envoltorios de secuencia de comandos de shell).

En bash, ksho zsh, podría fácilmente una secuencia de comandos de contenedor que utiliza una matriz para contener las opciones individuales de la siguiente othercommandmanera:

options=( "hi there" "nice weather" "here's a star" "*" )
options+=( "bonus bumblebee!" )  # add additional option

Luego llame othercommand(aún dentro de la secuencia de comandos del contenedor)

othercommand "${options[@]}"

La expansión de "${options[@]}"garantizaría que cada elemento de la optionsmatriz se cite individualmente y se presente othercommandcomo argumentos separados.

El usuario de la envoltura sería ajeno al hecho de que en realidad está llamando othercommand, algo que no sería cierto si el script solo generara las opciones de línea de comando othercommandcomo salida.

En /bin/sh, use $@para mantener las opciones:

set -- "hi there" "nice weather" "here's a star" "*"
set -- "$@" "bonus bumblebee!"  # add additional option

othercommand "$@"

( setEs el comando que se utiliza para ajustar los parámetros posicionales $1, $2, $3etc. Estos son lo que constituye la matriz $@en una cáscara de POSIX estándar. La primera --es para indicar a setque no existen opciones dadas, sólo argumentos. El --está realmente sólo es necesario si el el primer valor resulta ser algo que comienza con -).

Tenga en cuenta que son las comillas dobles $@y ${options[@]}eso asegura que los elementos no se dividan individualmente en palabras (y que el nombre de archivo sea global).

Kusalananda
fuente
podrías explicar set --?
user1207217
@ user1207217 Se agregó una explicación para responder.
Kusalananda
4

Si la somecommandsalida tiene una sintaxis de shell confiablemente buena, puede usar eval:

$ eval sh test.sh $(echo '"hello " "hi and bye"')
hello 
hi and bye

Pero debe asegurarse de que la salida tenga citas válidas y demás, de lo contrario podría terminar ejecutando comandos también fuera del script:

$ cat test.sh 
for var in "$@"
do
    echo "|$var|"
done
$ ls
bar  baz  test.sh
$ eval sh test.sh $(echo '"hello " "hi and bye"; echo rm *')
|hello |
|hi and bye|
rm bar baz test.sh

Tenga en cuenta que echo rm bar baz test.shno se pasó al script (debido a ;) y se ejecutó como un comando separado. Agregué el |alrededor $varpara resaltar esto.


En general, a menos que pueda confiar completamente en la salida de somecommand, no es posible usar de manera confiable su salida para construir una cadena de comando.

Olorin
fuente