Suite de: comportement inattendu dans la substitution de commandes shell
J'ai une commande qui peut prendre une énorme liste d'arguments, dont certains peuvent légitimement contenir des espaces (et probablement d'autres choses)
J'ai écrit un script qui peut générer ces arguments pour moi, avec des guillemets, mais je dois copier et coller la sortie par exemple
./somecommand
<output on stdout with quoting>
./othercommand some_args <output from above>
J'ai essayé de rationaliser cela en faisant simplement
./othercommand $(./somecommand)
et a rencontré le comportement inattendu mentionné dans la question ci-dessus. La question est - la substitution de commandes peut-elle être utilisée de manière fiable pour générer les arguments othercommand
étant donné que certains arguments nécessitent des guillemets et cela ne peut pas être changé?
shell
arguments
command-substitution
user1207217
la source
la source
eval
-être pourrait être utilisé, mais ce n'est généralement pas recommandé.xargs
est aussi quelque chose à considérersomecommand
subisse une analyse régulière du shell:
) ... en supposant que ce caractère ne sera pas fiable dans la sortie.Réponses:
Si la sortie est correctement citée pour le shell et que vous faites confiance à la sortie , vous pouvez l'exécuter
eval
.En supposant que vous avez un shell qui prend en charge les tableaux, il serait préférable d'en utiliser un pour stocker les arguments que vous obtenez.
Si
./gen_args.sh
produit une sortie comme'foo bar' '*' asdf
, alors nous pourrions exécutereval "args=( $(./gen_args.sh) )"
pour remplir un tableau appeléargs
avec les résultats. Ce serait les trois élémentsfoo bar
,*
,asdf
.Nous pouvons utiliser
"${args[@]}"
comme d'habitude pour développer individuellement les éléments du tableau:(Notez que les guillemets
"${array[@]}"
s'étendent à tous les éléments en tant qu'arguments distincts non modifiés. Sans guillemets, les éléments du tableau sont sujets au fractionnement des mots. Voir par exemple la page Tableaux sur BashGuide .)Cependant ,
eval
exécutera heureusement toutes les substitutions de shell, donc$HOME
dans la sortie s'étendrait à votre répertoire personnel, et une substitution de commande exécuterait en fait une commande dans le shell en cours d'exécutioneval
. Une sortie de"$(date >&2)"
créerait un seul élément de tableau vide et afficherait la date actuelle sur stdout. C'est un problème si vousgen_args.sh
obtenez les données d'une source non fiable, comme un autre hôte sur le réseau, des noms de fichiers créés par d'autres utilisateurs. La sortie pourrait inclure des commandes arbitraires. (Siget_args.sh
lui-même était malveillant, il n'aurait besoin de rien produire, il pourrait simplement exécuter directement les commandes malveillantes.)Une alternative à la citation de shell, qui est difficile à analyser sans eval, serait d'utiliser un autre caractère comme séparateur dans la sortie de votre script. Vous devez en choisir un qui n'est pas nécessaire dans les arguments réels.
Choisissons
#
et ayons la sortie du scriptfoo bar#*#asdf
. Nous pouvons maintenant utiliser l' expansion de commande sans guillemets pour diviser la sortie de la commande en arguments.Vous devrez
IFS
reculer plus tard si vous dépendez de la division des mots ailleurs dans le script (unset IFS
devrait fonctionner pour en faire la valeur par défaut), et également utiliserset +f
si vous souhaitez utiliser la globalisation plus tard.Si vous n'utilisez pas Bash ou un autre shell qui a des tableaux, vous pouvez utiliser les paramètres de position pour cela. Remplacez
args=( $(...) )
parset -- $(./gen_args.sh)
et utilisez"$@"
plutôt"${args[@]}"
qu'alors. (Ici aussi, vous avez besoin de guillemets"$@"
, sinon les paramètres de position sont sujets au fractionnement des mots.)la source
${args[@]}
- cela n'a pas fonctionné pour moi autrement"${array[@]}"
qu'avec"$@"
. Les deux doivent être cités, sinon la séparation des mots rompt les éléments du tableau en parties.Le problème est qu'une fois que votre
somecommand
script affiche les options pourothercommand
, les options ne sont en fait que du texte et à la merci de l'analyse standard du shell (affectées par tout ce qui$IFS
se passe et quelles options de shell sont en vigueur, ce que vous ne feriez pas dans le cas général contrôler).Au lieu d'utiliser
somecommand
pour afficher les options, il serait plus facile, plus sûr et plus robuste de l'utiliser pour appelerothercommand
. Lesomecommand
scénario serait alors un script wrapper autour de laothercommand
place d'une sorte de script d'aide que vous devez vous rappeler d'appeler d'une manière spéciale dans le cadre de la ligne de commandeotherscript
. Les scripts Wrapper sont un moyen très courant de fournir un outil qui appelle simplement un autre outil similaire avec un autre ensemble d'options (vérifiez simplement avecfile
quelles commandes/usr/bin
sont en fait des wrappers de script shell).Dans
bash
,ksh
ouzsh
, vous pourriez facilement un script wrapper qui utilise un tableau pour contenir les options individuellesothercommand
comme suit:Appelez ensuite
othercommand
(toujours dans le script wrapper):L'extension de
"${options[@]}"
garantirait que chaque élément duoptions
tableau est cité individuellement et présentéothercommand
comme des arguments distincts.L'utilisateur du wrapper serait inconscient du fait qu'il appelle réellement
othercommand
, ce qui ne serait pas vrai si le script générait simplement les options de ligne de commande pour enothercommand
tant que sortie.Dans
/bin/sh
, utilisez$@
pour maintenir les options:(
set
Est la commande utilisée pour le réglage des paramètres de position$1
,$2
,$3
etc. Ce sont ce qui fait le tableau$@
dans une coquille standard POSIX. La première--
est de signaler àset
qu'il n'y a pas d' options données, seuls les arguments. Le--
est vraiment nécessaire que si la la première valeur se trouve être quelque chose commençant par-
).Notez que ce sont les guillemets autour
$@
et${options[@]}
cela garantit que les éléments ne sont pas séparés par des mots (et le nom de fichier globbed).la source
set --
?Si la
somecommand
sortie est dans une syntaxe shell fiable, vous pouvez utilisereval
:Mais vous devez être sûr que la sortie a des guillemets valides et autres, sinon vous pourriez également exécuter des commandes en dehors du script:
Notez que cela
echo rm bar baz test.sh
n'a pas été transmis au script (à cause de;
) et a été exécuté comme une commande distincte. J'ai ajouté le|
tour$var
pour le souligner.En règle générale, à moins que vous ne puissiez faire entièrement confiance à la sortie de
somecommand
, il n'est pas possible d'utiliser de manière fiable sa sortie pour créer une chaîne de commande.la source