Cómo pasar argumentos de script bash a una subshell

13

Tengo un script de envoltura que funciona y luego pasa los parámetros originales a otra herramienta:

#!/bin/bash
# ...
other_tool -a -b "$@"

Esto funciona bien, a menos que la "otra herramienta" se ejecute en una subshell:

#!/bin/bash
# ...
bash -c "other_tool -a -b $@"

Si llamo a mi script de envoltura así:

wrapper.sh -x "blah blup"

entonces, solo el primer argumento original (-x) se entrega a "other_tool". En realidad, no creo un subshell, pero paso los argumentos originales a un shell en un teléfono Android, lo que no debería hacer ninguna diferencia:

#!/bin/bash
# ...
adb sh -c "other_tool -a -b $@"
Ralf Holly
fuente

Respuestas:

14

El printfcomando de Bash tiene una característica que citará / escapará / lo que sea una cadena, por lo que siempre que el padre y la subshell sean realmente bash, esto debería funcionar:

[Editar: como Siegi señaló en un comentario, si haces esto de la manera obvia, hay un problema cuando no se proporcionan argumentos, donde actúa como si realmente hubiera un único argumento vacío. He agregado una solución a continuación, envolviendo la cadena de formato ${1+}, que solo incluye la cadena de formato si se define el primer argumento . Es un poco complicado, pero funciona.]

#!/bin/bash

quoted_args="$(printf "${1+ %q}" "$@")" # Note: this will have a leading space before the first arg
# echo "Quoted args:$quoted_args" # Uncomment this to see what it's doing
bash -c "other_tool -a -b$quoted_args"

Tenga en cuenta que también puede hacerlo en una sola línea: bash -c "other_tool -a -b$(printf "${1+ %q}" "$@")"

Gordon Davisson
fuente
¡Gracias! Esto funcionó muy bien para mí.
DribblzAroundU82
Esto no funciona correctamente, cuando no se pasa ningún argumento (pasa un solo argumento vacío en lugar de ningún argumento).
siegi
1
@siegi Buena captura; He agregado una solución para esto.
Gordon Davisson el
4

Ninguna de las soluciones funciona bien. Simplemente pase x / \ \ \ "b \" / aaaaa / \ 'xxx \ aaaa \' / zz \ ​​"offf \" como parámetro y fallarán.

Aquí hay un contenedor simple que maneja cada caso. Tenga en cuenta cómo se escapa cada argumento dos veces.

#!/usr/bin/env bash
declare -a ARGS
COUNT=$#
for ((INDEX=0; INDEX<COUNT; ++INDEX))
do
    ARG="$(printf "%q" "$1")"
    ARGS[INDEX]="$(printf "%q" "$ARG")"
    shift
done

ls -l ${ARGS[*]}
jcayzac
fuente
1
también es útil si está tratando de concatenar $ @ como parte de un eventual argumento de cadena citado como '/ bin / foo' "$ @" '--opt' y descubre que simplemente no sale como cabría esperar .
jrg
1
Oh, gracias a Dios. He estado luchando con este problema y aparentemente nada funcionó. Esto lo resolvió. Sería bueno si de alguna manera fuera una incrustación incorporada para invocar escapes dobles teniendo en cuenta que este problema surge mucho.
MooGoo
2

Cambiar $@a $*. Hice una pequeña prueba local y funciona en mi caso.

#!/bin/sh
bash -c "echo $*"
bash -c "echo $@"

Guardar como test.shy hacerlo ejecutable da

$ ./test.sh foo bar
foo bar
foo

Hay una sutil diferencia entre $*y $@, como puede ver. Ver, por ejemplo, http://ss64.com/bash/syntax-parameters.html


Para la pregunta de seguimiento en los comentarios: debe escapar, por ejemplo, el espacio en blanco "dos veces" para pasar una cadena con un separador como argumento combinado, por ejemplo, con test.shmodificado a un wccontenedor:

#!/bin/sh
bash -c "wc $*"

Esto funciona:

$ touch test\ file
$ ./test.sh -l "test\ file"
0 test file

pero:

$ ./test.sh -l "test file"
wc: test: No such file or directory
wc: file: No such file or directory
0 total
Daniel Andersson
fuente
Lamentablemente, esto tampoco funciona. Si llama al contenedor de esta manera: wrapper.sh -x "blah blup"la subshell obtiene TRES parámetros (-x, bla, blup) en lugar de DOS (-x, "blah blup")
Ralf Holly
Necesita "doble escape" del argumento si desea preservar el separador de espacio, es decir "Blah\ blup". Pruébelo y vea si funciona.
Daniel Andersson
2
Parece que funciona, sin embargo, esto requeriría que la persona que llama de wrapper.sh agregue escapes, y estoy buscando una solución transparente.
Ralf Holly
2

Está fallando porque está forzando una matriz (los parámetros posicionales) en una cadena. "$@"es mágico porque te da cada parámetro por separado como una cadena correctamente citada. Agregar texto adicional rompe la magia: "blah $@"es solo una cadena.

Esto puede acercarte:

cmd="other_tool -a -b"
for parm in "$@"; do cmd+=" '$parm'"; done
adb sh -c "$cmd"

Por supuesto, cualquier parámetro que contenga una comilla simple causará problemas.

Glenn Jackman
fuente
2

Ok, más explicaciones:

$ cat /tmp/test.sh
#! /bin/bash

echo '$@='"$@"

set -v # "debug"

sh -c 'echo $@' "$@" # gremlins steal the first option

sh -c 'echo $@' -- "$@" # We defend the option with two knifes

$ bash -e /tmp/test.sh first second third
$@=first second third

sh -c 'echo $@' "$@" # gremlins steal the first option
second third

sh -c 'echo $@' -- "$@" # We defend the option with two knifes
first second third
alemán
fuente
1

Probé muchas soluciones diferentes, un buen recurso que incluye información de fondo y alternativas es, por ejemplo, BashFAQ / 096 en el Wiki de Greg (también conocido como GreyCat) . En total, encontré que los dos siguientes son los más legibles (de los que funcionan):


Desde Bash 4.4 (por lo que pude ver en las NOTICIAS ) es posible usar la expansión de parámetros de @Q esta manera:

adb sh -c "other_tool -a -b ${*@Q}"

Tenga en cuenta que uso $*aquí en lugar de $@porque desea "other_tool -a -b ${*@Q}"ser una sola cadena en lugar de una cadena por argumento pasado.

Si desea hacer lo mismo con una variable de matriz bash , necesitaría la sintaxis ${ARRAY[*]@Q}(entre comillas).


Si no tiene Bash 4.4 o posterior disponible o no está seguro, esta es mi solución preferida:

function escapeBashArgs() {
    local arg separator=""
    for arg
    do
        printf "%s%q" "$separator" "$arg"
        separator=" "
    done
}

adb sh -c "other_tool -a -b $(escapeBashArgs "$@")"

Tenga en cuenta que aquí se necesita para usar "$@"en lugar de $@o "$*", o $*porque no desea que la división de palabras dentro de los argumentos, por lo que las variantes sin citar no se puede utilizar, y desea que el número de argumentos que se conserva, por lo que "$*"no se puede utilizar ya que se uniría Todos los argumentos a una sola cadena. La función luego devuelve todos los argumentos en una sola cadena.

Si no le importa el espacio adicional delante del primer argumento, puede cambiar la printfcadena de formato " %q"y eliminar la separatorvariable. O puede usar la respuesta de Gordon Davisson .


Estas soluciones funcionan con todos los casos que se me ocurrieron, especialmente:

  • Sin argumentos: escapeBashArgsnada
  • Argumentos vacíos: escapeBashArgs "" ""'' ''
  • Argumentos con solo espacios: escapeBashArgs " " " "' ' ' 'o \ \ \ \ \( el último espacio lo ocupa el renderizador de este sitio )
  • Argumentos con espacios especiales y líneas nuevas: escapeBashArgs "a b" c\ d "arg with newline"'a b' 'c d' $'arg with\nnewline'o a\ \ \ \ \ \ b c\ d $'arg with\nnewline'( la línea nueva está entre withy newline, en otras posiciones, se debe al ajuste de línea en este sitio )
  • Argumentos con caracteres especiales: escapeBashArgs '$"'\''({:})''$"'\''({:})'o\$\"\'\(\{:\}\)
  • Ejemplo de respuesta jcayzacs : escapeBashArgs x/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"'x/ "b"/aaaaa/'\''xxx yyyy'\''/zz"offf"'ox/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"

(Probado con GNU bash 5.0.3 (1) -release.)

siegi
fuente
-2
#! /bin/bash
sh -c 'other_tool -a -b "$@"' -- "$@"
alemán
fuente
3
¡Bienvenido a Super User! Estamos buscando respuestas largas que brinden alguna explicación y contexto. No solo dé una respuesta de una línea; explica por qué tu respuesta es correcta, idealmente con citas. Las respuestas que no incluyen explicaciones pueden eliminarse.
G-Man dice 'Reincorporar a Monica'