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 othercommand
dado que algunos argumentos requieren comillas y esto no se puede cambiar?
shell
arguments
command-substitution
usuario1207217
fuente
fuente
eval
podría usarse, pero generalmente no se recomienda.xargs
es algo a considerar tambiénsomecommand
someta a un análisis de shell regular:
) ... suponiendo que ese carácter no esté confiablemente en la salida.Respuestas:
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.sh
produce resultados como'foo bar' '*' asdf
, entonces podríamos ejecutareval "args=( $(./gen_args.sh) )"
para completar una matriz llamadaargs
con los resultados. Eso sería los tres elementosfoo bar
,*
,asdf
.Podemos usar
"${args[@]}"
como de costumbre para expandir los elementos de la matriz individualmente:(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 ,
eval
ejecutará$HOME
con 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 shelleval
. 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 sigen_args.sh
obtiene 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 enget_args.sh
sí 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 scriptfoo bar#*#asdf
. Ahora podemos usar la expansión de comandos sin comillas para dividir la salida del comando en los argumentos.Tendrá que volver a configurarlo
IFS
más tarde si depende de la división de palabras en otra parte del script (unset IFS
debería funcionar para que sea el predeterminado), y también usarloset +f
si 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=( $(...) )
conset -- $(./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).fuente
${args[@]}
"${array[@]}"
con"$@"
. Ambos necesitan ser citados, o la división de palabras divide los elementos de la matriz en partes.El problema es que una vez que su
somecommand
script genera las opcionesothercommand
, las opciones son realmente solo texto y a merced del análisis estándar del shell (afectado por lo$IFS
que 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
somecommand
para generar las opciones, sería más fácil, más seguro y más robusto usarlo para llamarothercommand
. Lasomecommand
secuencia de comandos sería una secuencia de comandos envolvente enothercommand
lugar 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 comandosotherscript
. 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 verifiquefile
qué comandos en/usr/bin
realidad son envoltorios de secuencia de comandos de shell).En
bash
,ksh
ozsh
, podría fácilmente una secuencia de comandos de contenedor que utiliza una matriz para contener las opciones individuales de la siguienteothercommand
manera:Luego llame
othercommand
(aún dentro de la secuencia de comandos del contenedor)La expansión de
"${options[@]}"
garantizaría que cada elemento de laoptions
matriz se cite individualmente y se presenteothercommand
como 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 comandoothercommand
como salida.En
/bin/sh
, use$@
para mantener las opciones:(
set
Es el comando que se utiliza para ajustar los parámetros posicionales$1
,$2
,$3
etc. Estos son lo que constituye la matriz$@
en una cáscara de POSIX estándar. La primera--
es para indicar aset
que 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).fuente
set --
?Si la
somecommand
salida tiene una sintaxis de shell confiablemente buena, puede usareval
: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:
Tenga en cuenta que
echo rm bar baz test.sh
no se pasó al script (debido a;
) y se ejecutó como un comando separado. Agregué el|
alrededor$var
para 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.fuente