Comment passer des arguments de script bash à un sous-shell

13

J'ai un script wrapper qui fait du travail et passe ensuite les paramètres d'origine à un autre outil:

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

Cela fonctionne très bien, sauf si «l'autre outil» est exécuté dans un sous-shell:

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

Si j'appelle mon script wrapper comme ceci:

wrapper.sh -x "blah blup"

puis, seul le premier argument d'origine (-x) est remis à "other_tool". En réalité, je ne crée pas de sous-shell, mais passe les arguments d'origine à un shell sur un téléphone Android, ce qui ne devrait pas faire de différence:

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

Réponses:

14

La printfcommande de Bash a une fonctionnalité qui cite / échappe / quelque soit une chaîne, donc tant que le parent et le sous-shell sont en fait bash, cela devrait fonctionner:

[Edit: comme l'a souligné siegi dans un commentaire, si vous faites cela de manière évidente, il y a un problème quand aucun argument n'est fourni, où il agit comme s'il y avait en fait un seul argument vide. J'ai ajouté une solution de contournement ci-dessous, encapsulant la chaîne de format avec ${1+}, qui n'inclut la chaîne de format que si le premier argument est défini . C'est un peu kluge, mais ça marche.]

#!/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"

Notez que vous pouvez également le faire sur une seule ligne: bash -c "other_tool -a -b$(printf "${1+ %q}" "$@")"

Gordon Davisson
la source
Je vous remercie! Cela a très bien fonctionné pour moi.
DribblzAroundU82
Cela ne fonctionne pas correctement, quand aucun argument n'est passé (il passe un seul argument vide au lieu d'aucun argument).
siegi
1
@siegi Bonne prise; J'ai ajouté une solution de contournement pour cela.
Gordon Davisson
4

Aucune des solutions ne fonctionne bien. Passez simplement x / \ \ \ "b \" / aaaaa / \ 'xxx \ yyyy \' / zz \ ​​"offf \" comme paramètre et ils échoueront.

Voici un emballage simple qui gère chaque cas. Notez comment il échappe deux fois à chaque argument.

#!/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
la source
1
également utile si vous essayez de concaténer $ @ dans le cadre d'un éventuel argument de chaîne entre guillemets comme '/ bin / foo' "$ @" '--opt' et que vous découvrez qu'il ne sort tout simplement pas comme prévu .
JRG
1
Oh merci mon dieu. J'ai eu du mal avec ce problème et apparemment rien n'a fonctionné. Cela l'a résolu. Ce serait bien si c'était en quelque sorte un bash intégré pour invoquer des doubles échappements étant donné que ce problème se pose souvent.
MooGoo
2

Remplacez $@par $*. J'ai fait un petit test local et ça marche dans mon cas.

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

Enregistrer sous test.shet le rendre exécutable donne

$ ./test.sh foo bar
foo bar
foo

Il y a une différence subtile entre $*et $@, comme vous pouvez le voir. Voir par exemple http://ss64.com/bash/syntax-parameters.html


Pour la question de suivi dans les commentaires: vous devez échapper par exemple un espace blanc "deux fois" pour passer une chaîne avec un séparateur comme argument combiné, par exemple avec test.shmodifié à un wcwrapper:

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

Cela marche:

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

mais:

$ ./test.sh -l "test file"
wc: test: No such file or directory
wc: file: No such file or directory
0 total
Daniel Andersson
la source
Malheureusement, cela ne fonctionne pas non plus. Si vous appelez le wrapper comme ceci: wrapper.sh -x "blah blup"le sous-shell obtient TROIS paramètres (-x, blah, blup) au lieu de DEUX (-x, "blah blup")
Ralf Holly
Vous devez "double-échapper" l'argument si vous voulez que le séparateur d'espace soit préservé, ie "Blah\ blup". Essayez-le et voyez si cela fonctionne.
Daniel Andersson
2
Il semble que cela fonctionne, cependant, cela nécessiterait l'appelant de wrapper.sh pour ajouter des échappements, et je cherche une solution transparente.
Ralf Holly
2

Il échoue parce que vous contraignez un tableau (les paramètres positionnels) dans une chaîne. "$@"est magique car il vous donne chaque paramètre séparé comme une chaîne correctement citée. L'ajout de texte supplémentaire brise la magie: "blah $@"n'est qu'une seule chaîne.

Cela peut vous rapprocher:

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

Bien sûr, tout paramètre contenant une seule citation causera des problèmes.

glenn jackman
la source
2

Ok, plus d'explications:

$ 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
Jerry
la source
1

J'ai essayé de nombreuses solutions différentes, une bonne ressource comprenant des informations de base et des alternatives est par exemple BashFAQ / 096 sur le wiki de Greg (aka. GreyCat) . Au total, j'ai trouvé que les deux suivants étaient les plus lisibles (parmi ceux qui fonctionnent):


Depuis Bash 4.4 (pour autant que je sache d'après les NOUVELLES ), il est possible d'utiliser l' expansion des paramètres @Q comme ceci:

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

Notez que j'utilise $*ici au lieu de $@parce que vous voulez "other_tool -a -b ${*@Q}"être une seule chaîne au lieu d'une chaîne par argument passé.

Si vous voulez faire de même avec une variable de tableau bash, vous aurez besoin de la syntaxe ${ARRAY[*]@Q}(entre guillemets).


Si vous n'avez pas Bash 4.4 ou version ultérieure disponible ou n'êtes pas sûr, c'est ma solution préférée:

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

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

Notez que vous devez utiliser ici à la "$@"place de $@ou "$*"ou $*parce que vous ne voulez pas que les mots soient séparés dans les arguments, donc les variantes sans guillemets ne peuvent pas être utilisées, et vous voulez que le nombre d'arguments soit conservé, donc "$*"ne peut pas être utilisé comme il se joindrait tous les arguments d'une seule chaîne. La fonction renvoie ensuite tous les arguments dans une seule chaîne.

Si vous ne vous souciez pas de l'espace supplémentaire devant le premier argument, vous pouvez remplacer la printfchaîne de format par " %q"et supprimer la separatorvariable. Ou vous pouvez utiliser la réponse à une ligne de Gordon Davissons .


Ces solutions fonctionnent avec tous les cas que je pourrais trouver, en particulier:

  • Aucun argument: escapeBashArgsrien
  • Arguments vides: escapeBashArgs "" ""'' ''
  • Arguments avec juste des espaces: escapeBashArgs " " " "' ' ' 'ou \ \ \ \ \( le dernier espace est consommé par le rendu de ce site )
  • Arguments avec espacement spécial et sauts de ligne: escapeBashArgs "a b" c\ d "arg with newline"'a b' 'c d' $'arg with\nnewline'ou a\ \ \ \ \ \ b c\ d $'arg with\nnewline'( le saut de ligne est entre withet newline, sur d'autres positions, c'est à cause du retour à la ligne sur ce site )
  • Arguments avec des caractères spéciaux: escapeBashArgs '$"'\''({:})''$"'\''({:})'ou\$\"\'\(\{:\}\)
  • Exemple de réponse jcayzacs : escapeBashArgs x/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"'x/ "b"/aaaaa/'\''xxx yyyy'\''/zz"offf"'oux/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"

(Testé avec la version GNU bash 5.0.3 (1).)

siegi
la source
-2
#! /bin/bash
sh -c 'other_tool -a -b "$@"' -- "$@"
Jerry
la source
3
Bienvenue sur Super User! Nous recherchons de longues réponses qui fournissent une explication et un contexte. Ne vous contentez pas de donner une réponse sur une seule ligne; expliquez pourquoi votre réponse est juste, idéalement avec des citations. Les réponses qui ne contiennent pas d'explications peuvent être supprimées.
G-Man dit `` Réintègre Monica ''