Un script shell peut-il imprimer son argument, cité comme vous l'écririez à l'invite du shell?

9

Dans un script shell, ma compréhension est que se "$@"développe aux arguments de script, en les citant au besoin. Par exemple, cela transmet les arguments de script à gcc:

gcc -fPIC "$@"

Cependant, lors de l'utilisation de la syntaxe bash pass-to-stdin <<<, "@$"ne fonctionne pas comme je m'y attendais.

#!/bin/bash
cat <<< "$@"

Appel du script comme ./test.sh foo "bar baz"donne

foo bar baz

Je m'attendrais

foo "bar baz"

Existe-t-il un moyen d'écrire un script shell qui affiche ses arguments comme vous les écririez à l'invite du shell? Par exemple: un indice sur la commande à utiliser ensuite, y compris les arguments de script dans l'indice.

Alex Jasmin
la source

Réponses:

5

Eh bien, se "$@"développe à la liste des paramètres de position, un argument par paramètre de position.

Quand vous faites:

set '' 'foo bar' $'blah\nblah'
cmd "$@"

cmdest invoqué avec ces 3 arguments: la chaîne vide, foo baret blah<newline>blah. Le shell appellera l' execve()appel système avec quelque chose comme:

execve("/path/to/cmd", ["cmd", "", "foo bar", "blah\nblah"], [envvars...]);

Si vous voulez reconstruire une ligne de commande shell (c'est-à-dire du code dans le langage shell) qui reproduirait cette même invocation, vous pourriez faire quelque chose comme:

awk -v q="'" '
  function shellquote(s) {
    gsub(q, q "\\" q q, s)
    return q s q
  }
  BEGIN {
    for (i = 1; i < ARGC; i++) {
      printf "%s", sep shellquote(ARGV[i])
      sep = " "
    }
    printf "\n"
  }' cmd "$@"

Ou avec zsh, en demandant différents types de devis:

set '' 'foo bar' $'blah\nblah'
$ print -r -- cmd "${(q)@}"
cmd '' foo\ bar blah$'\n'blah
$ print -r -- cmd "${(qq)@}"
cmd '' 'foo bar' 'blah
blah'
$ print -r -- cmd "${(qqq)@}"
cmd "" "foo bar" "blah
blah"
$ print -r -- cmd "${(qqqq)@}"
cmd $'' $'foo bar' $'blah\nblah'

Ou avec zsh, bashou ksh93(ici pour bash, YMMV avec d'autres coques):

$ set '' 'foo bar' $'blah\nblah'
$ printf cmd; printf ' %q' "$@"; printf '\n'
cmd '' foo\ bar $'blah\nblah'

Vous pouvez également utiliser l'option xtrace du shell qui oblige le shell à imprimer ce qu'il va exécuter:

(PS4=; set -x; : cmd "$@")
: cmd '' 'foo bar' 'blah
blah'

Ci-dessus, nous avons exécuté la :commande no-op avec cmdet les paramètres positionnels comme argument. Ma coquille les a imprimés d'une belle façon citée appropriée pour être réintroduite dans la coquille. Tous les obus ne font pas ça.

Stéphane Chazelas
la source
5
`"$@"` expands to the script arguments, quoting them as needed

Non, ce n'est pas ce qui se passe. L'appel d'un programme prend une liste d'arguments, chaque argument étant une chaîne. Lorsque vous exécutez le programme shell ./test.sh foo "bar baz", cela construit un appel avec trois arguments: ./test.sh, fooet bar baz. (L'argument zéro est le nom du programme; cela permet aux programmes de savoir sous quel nom ils sont appelés.) La citation est une caractéristique du shell, pas une caractéristique des appels de programme. Le shell construit cette liste lors de l'appel.

"$@"copie directement la liste des arguments passés au script ou à la fonction dans la liste des arguments de l'appel où elle est utilisée. Aucune citation n'est impliquée car aucune analyse de shell n'est effectuée sur ces listes.

Dans cat <<< "$@", vous utilisez "$@"dans un contexte où une seule chaîne est requise. L' <<<opérateur` requiert une chaîne, pas une liste de chaînes. Dans ce contexte, bash prend les éléments de la liste et les joint avec un espace entre les deux.

Pour le débogage de script, si vous exécutez set -x( set +xpour désactiver), cela active un mode de trace où chaque commande est imprimée avant d'être exécutée. En bash, cette trace a des guillemets qui permettent de recoller la commande dans un shell (ce n'est pas le cas pour toutes les shimplémentations).

Si vous avez une chaîne et que vous souhaitez la transformer en syntaxe source shell qui analyse la chaîne d'origine, vous pouvez l'entourer de guillemets simples et remplacer chaque guillemet simple à l'intérieur de la chaîne par '\''.

for x do
  printf %s "'${x//\'/\'\\\'\'}' "
done
echo

La syntaxe de remplacement de chaîne est spécifique à ksh93 / bash / zsh / mksh. En clair, vous devez boucler sur la chaîne.

for raw do
  quoted=
  while case "$raw" in *\'*) true;; *) false;; esac; do
    quoted="$quoted'\\''${raw%%\'*}"
    raw="${raw#*\'}"
  done
  printf %s "'$quoted$raw' "
done
echo
Gilles 'SO- arrête d'être méchant'
la source
2

"$@" se développe aux arguments de script, en les citant au besoin

Eh bien, en quelque sorte. Pour des raisons pratiques qui devraient être suffisamment proches, et le manuel de référence dit que"$@" is equivalent to "$1" "$2" ...

Donc, avec les deux paramètres fooet bar baz, ce serait pareil:

echo "$@"
echo "$1" "$2"
echo "foo" "bar baz"

(Sauf que si les paramètres contenaient des caractères spéciaux au lieu de simples chaînes, ils ne seraient pas développés à nouveau après l'expansion $@et $1...)

Mais même si nous considérons $@remplacé par les paramètres entre guillemets, les guillemets ne seraient pas là pour echovoir, de la même manière que cela gccn'obtient pas non plus les guillemets.

<<<est un peu une exception à la "$@"== "$1" "$2" ...règle, il est explicitement mentionné que The result is supplied as a single string to the command on its standard inputaprès être passé par des paramètres et des variables et la suppression des protections entre autres. Donc, comme d'habitude, <<< "foo"donne juste fooen entrée, de la même manière somecmd "foo"ne donne fooqu'en argument.

Appeler le script comme ./test.sh foo "bar baz"je [...] m'attendrais foo "bar baz"

Si les citations restaient, elles devraient l'être "foo" "bar baz". Le shell ou toute commande en cours d'exécution n'a aucune idée de ce qu'était la citation lorsque la commande a été exécutée. Ou s'il y avait même des citations dont parler, l'appel système ne reçoit qu'une liste de chaînes terminées par des valeurs nulles et les citations ne sont qu'une caractéristique du langage shell. D'autres langues peuvent avoir d'autres conventions.

ilkkachu
la source
0

Une solution alternative pour bash

q='"'; t=( "${@/#/$q}" ); u=( "${t[@]/%/$q}" ); echo ${u[@]}

Bash ne prend pas en charge les substitutions imbriquées, donc merci à /programming/12303974/assign-array-to-variable#12304017 pour avoir montré comment réaffecter un tableau. Voir man bash( https://linux.die.net/man/1/bash ) pour plus de détails sur les tableaux, l'expansion et la substitution de modèle (sous expansion des paramètres).

Une analyse

Bash place les paramètres de ligne de commande sous forme de tableau dans $@

q contient le caractère de citation.

Les guillemets doubles autour de l'expansion des paramètres ${ ... }préservent les paramètres individuels en tant qu'éléments distincts et leur ( )encapsulation vous permet de les affecter en tant que tableau à une variable.

/#/$qdans un paramètre, l'expansion remplace le début du motif (comme regex ^) par le caractère guillemet.

/%/$qdans un paramètre, l'expansion remplace la fin du motif (comme l'expression régulière $) par le caractère guillemet.

Cas d'utilisation: interroger MySQL pour une liste d'adresses e-mail à partir de la ligne de commande

Il y a quelques changements aux instructions ci-dessus pour utiliser un caractère de guillemet différent, ajouter des virgules entre les paramètres et supprimer la virgule finale. Et bien sûr, je suis mauvais en mettant le mot de passe dans l'invocation mysql. Alors, poursuivez-moi.

q="'"; t=( "${@/#/$q}" ); u="${t[@]/%/$q,}"; v="u.email in( ${u%,} )"
mysql -uprod_program -h10.90.2.11 -pxxxxxxxxxxxx my_database <<END
select ...
from users u
join ....
where $v # <<<<<<<<<<<<<<<<<< here is where all the hard work pays off :-)
group by user_id, prog_id
;
END
Jeff
la source
Notez simplement que les guillemets doubles ne sont pas très utiles pour protéger les barres obliques inverses, les $extensions et autres guillemets doubles. C'est pourquoi les autres réponses utilisent des guillemets simples tout en allant à certaines longueurs pour gérer les guillemets simples à l' intérieur de la chaîne, ou utilisent les propres fonctionnalités du shell pour produire une copie entre guillemets de la chaîne.
ilkkachu
@ilkkachu Dûment noté! C'est pourquoi (entre autres raisons) que j'ai voté contre toutes les réponses précédentes. Aussi pourquoi j'ai ajouté ce cas d'utilisation. Espérons que le parfait n'est pas l'ennemi du bien.
Jeff