La salida del script de Shell se divide incorrectamente mientras se pasa como argumento al script

La salida del script de Shell se divide incorrectamente mientras se pasa como argumento al script

Digamos que tengo los siguientes dos scripts de shell:

#!/bin/sh
#This script is named: args.sh

echo 1 "\"Two words\"" 3

, y:

#!/bin/sh
#This script is named: test.sh

echo "Argument 1: "$1
echo "Argument 2: "$2
echo "Argument 3: "$3

Cuando llamo a los scripts como:

sh test.sh $(sh args.sh)

, Yo recibo:

Argument 1: 1
Argument 2: "Two
Argument 3: words"

Cuando esperaba obtener:

Argument 1: 1
Argument 2: Two words
Argument 3: 3

Copiar la salida sh args.shy pegarla como entrada sh test.shfunciona bien; así que supongo que esto no es realmente lo que está haciendo el shell. Puedo lograr el resultado deseado/esperado llamando sh args.sh | xargs sh test.shen su lugar.

Sin embargo, me pregunto si existe una forma equivalente de hacer esto sin canalizar la salida del primer script (args.sh) a xargs. Quiero que los scripts se llamen en el orden original; el script de argumento para enviar los parámetros al segundo. También estoy buscando una explicación de por qué esta llamada no funciona como se esperaba.

Respuesta1

Parte del problema es que la cadena devuelta por args.sh no se analiza de la misma manera que un comando directo, sino solo por el valor de $IFS ( $' \t\n'). Intente activar el seguimiento de comandos con set -x:

$ sh /tmp/test.sh $(sh /tmp/args.sh)
++ sh /tmp/args.sh
+ sh /tmp/test.sh 1 '"Two' 'words"' 3
Argument 1: 1
Argument 2: "Two
Argument 3: words"
$

Observe la línea que comienza con un solo +: hay cuatro argumentos, los '"Two'y 'words"'son dos analizados como argumentos separados. Lo que querrías hacer es cambiar $IFS.

$ set -x
$ IFS='"'
+ IFS='"'
$ sh /tmp/test.sh $(sh /tmp/args.sh)
++ sh /tmp/args.sh
+ sh /tmp/test.sh '1 ' 'Two words' ' 3'
Argument 1: 1
Argument 2: Two words
Argument 3:  3
$

Esto no funcionará para todas las salidas. Lo mejor que puedes hacer es cambiar la salida de args.sh para separar la salida con algo diferente a un espacio, por ejemplo una coma o dos puntos:

$ cat /tmp/args.sh
#!/bin/sh
#This script is named: args.sh

echo "1,Two words,3"
$ IFS=,
$ sh /tmp/test.sh $(sh /tmp/args.sh)
+ sh /tmp/args.sh
+ sh /tmp/test.sh 1 Two words 3
Argument 1: 1
Argument 2: Two words
Argument 3: 3
$

Respuesta2

Cuando dejas una sustitución de variable $varo una sustitución de comando $(cmd)sin comillas, el resultado sufre las siguientes transformaciones:

  1. Divide la cadena resultante en palabras. La división ocurre en espacios en blanco (secuencias de espacios, tabulaciones y nuevas líneas); esto se puede configurar configurando IFS(ver1,2).
  2. Cada una de las palabras resultantes se trata como un patrón global y, si coincide con algunos archivos, la palabra se reemplaza por la lista de nombres de archivos.

Tenga en cuenta que el resultado no es una cadena sino una lista de cadenas. Además, tenga en cuenta que "aquí no intervienen caracteres como comillas; son parte de la sintaxis fuente del shell, no de la expansión de cadenas.

Una regla general de la programación de shell es poner siempre comillas dobles alrededor de las sustituciones de variables y comandos, a menos que sepa por qué debe omitirlas. Entonces en test.sh, escribe echo "Argument 1: $1". Para pasar argumentos a test.sh, estás en problemas: necesitas pasar una lista de palabras de args.sha test.sh, pero el método elegido implica una sustitución de comando y eso solo proporciona el camino para una cadena simple.

Si puede garantizar que los argumentos que se pasarán no contengan nuevas líneas y es aceptable cambiar ligeramente el proceso de invocación, puede configurarlo IFSpara que contenga solo una nueva línea. Asegúrese de que args.shgenere exactamente un nombre de archivo por línea sin comillas perdidas.

IFS='
'
test.sh $(args.sh)
unset IFS

Si los argumentos pueden contener caracteres arbitrarios (presumiblemente excepto bytes nulos, que no puede pasar como argumentos), deberá realizar alguna codificación. Cualquier codificación servirá; Por supuesto, no será lo mismo que pasar los argumentos directamente: eso no es posible. Por ejemplo, en args.sh(reemplace \tpor un carácter de tabulación real si su shell no lo admite):

for x; do
  printf '%s_\n' "$x" |
  sed -e 's/q/qq/g' -e 's/ /qs/g' -e 's/\t/qt/g' -e 's/$/qn/'
done

y en test.sh:

for arg; do
  decoded=$(printf '%s\n' "$arg" |
            sed -e 's/qs/ /g' -e 's/qt/\t/g' -e 's/qn/\n/g' -e 's/qq/q/g')
  decoded=${decoded%_qn}
  # process "$decoded"
done

Es posible que prefiera cambiar test.shpara aceptar una lista de cadenas como entrada. Si las cadenas no contienen nuevas líneas, invoque args.sh | test.shy use esto en args.sh(explicación):

while IFS= read -r line; do
  # process "$line"
done

Otro método que evita por completo la necesidad de citar es invocar el segundo script del primero.


args.sh "$@"

información relacionada