Quelle est la différence entre $ * et $ @?

Considérez le code suivant:

foo () { echo $* } bar () { echo $@ } foo 1 2 3 4 bar 1 2 3 4 

Il sort:

1 2 3 4

1 2 3 4

J'utilise Ksh88, mais je m'intéresse aussi à d'autres coquillages communs. S'il vous arrive de connaître une particularité pour des coquilles spécifiques, veuillez les mentionner.

J'ai trouvé la suite dans la page de manuel de Ksh sur Solaris:

La signification de $ * et $ @ est identique lorsqu'elle n'est pas citée ou lorsqu'elle est utilisée comme valeur de paramètre ou comme nom de file. Cependant, lorsqu'il est utilisé comme argument de command, $ * est équivalent à “ $ 1d $ 2d … '', où d est le premier caractère de la variable IFS, alors que $ @ équivaut à $ 1 $ 2 ….

J'ai essayé de modifier la variable IFS , mais cela ne modifie pas la sortie. Peut-être que je fais quelque chose de mal?

    Quand ils ne sont pas cités, $* et $@ sont les mêmes. Vous ne devez pas utiliser l'un ou l'autre de ces éléments, car ils peuvent se rompre de manière inattendue dès que vous disposez d'arguments contenant des espaces ou des caractères generics.


    "$*" transforme en un seul mot "$1c$2c..." . Habituellement c est un espace, mais c'est en fait le premier caractère d' IFS , donc ça peut être n'importe quoi.

    Le seul bon usage que j'ai jamais trouvé pour cela est:

    joindre des arguments avec une virgule (version simple)

     join1() { IFS=, echo "$*" } join1 abc # => a,b,c 

    joindre des arguments avec le délimiteur spécifié (meilleure version)

     join2() { typeset IFS=$1 # typeset makes a local variable in ksh (see footnote) shift echo "$*" } join2 + abc # => a+b+c 

    "$@" développe pour séparer les mots: "$1" "$2" ...

    C'est presque toujours ce que vous voulez. Il étend chaque paramètre de position à un mot séparé, ce qui le rend parfait pour prendre des arguments de command line ou de fonction, puis les transmettre à une autre command ou fonction. Et comme il se développe en utilisant des guillemets doubles, cela signifie que les choses ne se cassent pas si, par exemple, "$1" contient un espace ou un astérisque ( * ).


    svim un script appelé svim qui exécute vim avec sudo . Nous allons faire trois versions pour illustrer la différence.

    svim1

     #!/bin/sh sudo vim $* 

    svim2

     #!/bin/sh sudo vim "$*" 

    svim3

     #!/bin/sh sudo vim "$@" 

    Tout ira bien pour les cas simples, par exemple un seul nom de file qui ne contient pas d'espaces:

     svim1 foo.txt # == sudo vim foo.txt svim2 foo.txt # == sudo vim "foo.txt" svim2 foo.txt # == sudo vim "foo.txt" 

    Mais seulement $* et "$@" fonctionnent correctement si vous avez plusieurs arguments.

     svim1 foo.txt bar.txt # == sudo vim foo.txt bar.txt svim2 foo.txt bar.txt # == sudo vim "foo.txt bar.txt" # one file name! svim3 foo.txt bar.txt # == sudo vim "foo.txt" "bar.txt" 

    Et seulement "$*" et "$@" fonctionnent correctement si vous avez des arguments contenant des espaces.

     svim1 "shopping list.txt" # == sudo vim shopping list.txt # two file names! svim2 "shopping list.txt" # == sudo vim "shopping list.txt" svim3 "shopping list.txt" # == sudo vim "shopping list.txt" 

    Donc seulement "$@" fonctionnera correctement tout le time.


    typeset est de savoir comment créer une variable locale dans ksh ( bash et ash utilisent local ). Cela signifie que l' IFS sera restauré à sa valeur précédente lorsque la fonction reviendra. Ceci est important, car les commands que vous exécutez par la suite peuvent ne pas fonctionner correctement si IFS est défini sur quelque chose de non standard.

    Réponse courte: utilisez "$@" (notez les guillemets doubles). Les autres forms sont très rarement utiles.

    "$@" est une syntaxe assez étrange. Il est remplacé par tous les parameters de position, en tant que champs séparés. S'il n'y a pas de parameters de position ( $# est 0), alors "$@" prend plus rien (pas une string vide mais une list avec 0 éléments), s'il y a un paramètre positionnel, "$@" équivaut à "$1" , s'il y a deux parameters de position, "$@" équivaut à "$1" "$2" , etc.

    "$@" vous permet de transmettre les arguments d'un script ou d'une fonction à une autre command. Il est très utile pour les wrappers qui configurent des variables d'environnement, préparent des files de données, etc. avant d'appeler une command avec les mêmes arguments et options avec lesquels l'encapsuleur a été appelé.

    Par exemple, la fonction suivante filter la sortie de la cvs -nq update . Mis à part le filtrage de sortie et l'état de return (qui est celui de grep plutôt que celui de cvs ), appeler cvssm sur certains arguments se comporte comme appeler cvs -nq update avec ces arguments.

     cvssm () { cvs -nq update "$@" | egrep -v '^[?A]'; } 

    "$@" s'étend à la list des parameters de position. Dans les shells qui prennent en charge les arrays, il existe une syntaxe similaire à la list des éléments du tableau: "${array[@]}" (les accolades sont obligatoires sauf dans zsh). Encore une fois, les guillemets doubles sont quelque peu trompeurs: ils protègent contre la division de champ et la génération de model des éléments de tableau, mais chaque élément de tableau finit dans son propre champ.

    Certains coquillages anciens avaient ce qui est sans doute un bug: lorsqu'il n'y avait pas d'arguments positionnels, "$@" étendu à un seul champ contenant une string vide, plutôt que dans aucun champ. Cela a conduit à la solution ${1+"$@"} contournement ${1+"$@"} (rendu célèbre grâce à la documentation Perl ). Seules les versions plus anciennes du shell Bourne actuel et de l'implémentation OSF1 sont affectées, aucun de ses rlocations compatibles modernes (ash, ksh, bash, …) ne l'est. /bin/sh n'est pas affecté sur un système qui a été publié au 21ème siècle que je connaisse (sauf si vous countz la version de maintenance de Tru64 et même là /usr/xpg4/bin/sh est sûr donc seulement #!/bin/sh script sont affectés, pas les scripts #!/usr/bin/env sh tant que votre PATH est configuré pour la conformité POSIX). En bref, c'est une anecdote historique que vous n'avez pas à vous soucier.


    "$*" développe toujours un seul mot. Ce mot contient les parameters de position, concaténés avec un espace entre les deux. (Plus généralement, le séparateur est le premier caractère de la valeur de la variable IFS Si la valeur de IFS est la string vide, le séparateur est la string vide.) S'il n'y a pas de parameters de position, alors "$*" est vide string, s'il y a deux parameters de position et IFS a sa valeur par défaut, "$*" équivaut à "$1 $2" , etc.

    $@ et $* cotations externes sont équivalentes. Ils élargissent à la list des parameters de position, en tant que champs distincts, comme "$@" ; mais chaque champ résultant est ensuite divisé en champs séparés qui sont traités comme des templates generics de nom de file, comme d'habitude avec des extensions de variable non cotées.

    Par exemple, si le directory actuel contient trois files bar , baz et foo , alors:

     set -- # no positional parameters for x in "$@"; do echo "$x"; done # prints nothing for x in "$*"; do echo "$x"; done # prints 1 empty line for x in $*; do echo "$x"; done # prints nothing set -- "b* c*" "qux" echo "$@" # prints `b* c* qux` echo "$*" # prints `b* c* qux` echo $* # prints `bar baz c* qux` for x in "$@"; do echo "$x"; done # prints 2 lines: `b* c*` and `qux` for x in "$*"; do echo "$x"; done # prints 1 lines: `b* c* qux` for x in $*; do echo "$x"; done # prints 4 lines: `bar`, `baz`, `c*` and `qux` 

    Voici un script simple pour démontrer la différence entre $* et $@ :

     #!/bin/bash test_param() { echo "Receive $# parameters" echo Using '$*' echo for param in $*; do printf '==>%s<==\n' "$param" done; echo echo Using '"$*"' for param in "$*"; do printf '==>%s<==\n' "$param" done; echo echo Using '$@' for param in $@; do printf '==>%s<==\n' "$param" done; echo echo Using '"$@"'; for param in "$@"; do printf '==>%s<==\n' "$param" done } IFS="^${IFS}" test_param 1 2 3 "abc" 

    Sortie:

     % cuonglm at ~ % bash test.sh Receive 4 parameters Using $* ==>1<== ==>2<== ==>3<== ==>a<== ==>b<== ==>c<== Using "$*" ==>1^2^3^ab c<== Using $@ ==>1<== ==>2<== ==>3<== ==>a<== ==>b<== ==>c<== Using "$@" ==>1<== ==>2<== ==>3<== ==>ab c<== 

    Dans la syntaxe de tableau, le non différent lorsque vous utilisez $* ou $@ . Cela n'a de sens que si vous utilisez des guillemets doubles "$*" et "$@" .

    Le code que vous avez fourni donnera le même résultat. Pour mieux le comprendre, essayez ceci:

     foo () { for i in "$*"; do echo "$i" done } bar () { for i in "$@"; do echo "$i" done } 

    La sortie devrait maintenant être différente. Voici ce que je reçois:

     $ foo() 1 2 3 4 1 2 3 4 $ bar() 1 2 3 4 1 2 3 4 

    Cela a fonctionné pour moi sur bash . Pour autant que je sache, ksh ne devrait pas différer beaucoup. Essentiellement, citer $* traitera tout comme un seul mot, et citer $@ traitera la list comme des mots séparés, comme on peut le voir dans l'exemple ci-dessus.

    Comme exemple d'utilisation de la variable IFS avec $* , considérez ceci

     fooifs () { IFS="c" for i in "$*"; do echo "$i" done unset IFS # reset to the original value } 

    Je comprends ceci:

     $ fooifs 1 2 3 4 1c2c3c4 

    En outre, je viens de confirmer que cela fonctionne de la même façon dans ksh . Les deux bash et ksh testés ici étaient sous OSX mais je ne peux pas voir comment cela importerait beaucoup.

    La différence est importante lors de l'écriture de scripts qui doivent utiliser correctement les parameters de position …

    Imaginez l'appel suivant:

     $ myuseradd -m -c "Carlos Campderrós" ccampderros 

    Ici, il n'y a que 4 parameters:

     $1 => -m $2 => -c $3 => Carlos Campderrós $4 => ccampderros 

    Dans mon cas, myuseradd est juste un wrapper pour useradd qui accepte les mêmes parameters, mais ajoute un quota pour l'user:

     #!/bin/bash -e useradd "$@" setquota -u "${!#}" 10000 11000 1000 1100 

    Notez l'appel à useradd "$@" , avec $@ cité. Cela respectera les parameters et les enverra comme ils sont à useradd . Si vous deviez annuler $@ (ou utiliser $* également sans guillemets), useradd verrait 5 parameters, car le 3ème paramètre contenant un espace serait divisé en deux:

     $1 => -m $2 => -c $3 => Carlos $4 => Campderrós $5 => ccampderros 

    (et inversement, si vous utilisiez "$*" , useradd ne verrait qu'un seul paramètre: -m -c Carlos Campderrós ccampderros )

    Donc, en résumé, si vous avez besoin de travailler avec des parameters respectant les parameters multi-mot, utilisez "$@" .

      * Expands to the positional parameters, starting from one. When the expansion occurs within double quotes, it expands to a sin‐ gle word with the value of each parameter separated by the first character of the IFS special variable. That is, "$*" is equiva‐ lent to "$1c$2c...", where c is the first character of the value of the IFS variable. If IFS is unset, the parameters are sepa‐ rated by spaces. If IFS is null, the parameters are joined without intervening separators. @ Expands to the positional parameters, starting from one. When the expansion occurs within double quotes, each parameter expands to a separate word. That is, "$@" is equivalent to "$1" "$2" ... If the double-quoted expansion occurs within a word, the expansion of the first parameter is joined with the begin‐ ning part of the original word, and the expansion of the last parameter is joined with the last part of the original word. When there are no positional parameters, "$@" and $@ expand to nothing (ie, they are removed). 

    // homme bash . est ksh, afair, comportement similaire.

    Parler des différences entre zsh et bash :

    Avec des guillemets autour de $@ et $* , zsh et bash se comportent de la même façon, et je suppose que le résultat est assez standard parmi tous les shells:

      $ f () { for i in "$@"; do echo +"$i"+; done; }; f 'aa' 'b' '' +a a+ +b+ ++ $ f () { for i in "$*"; do echo +"$i"+; done; }; f 'aa' 'b' '' +aab + 

    Sans les guillemets, les résultats sont les mêmes pour $* et $@ , mais différents dans bash et dans zsh . Dans ce cas, zsh montre un comportement étrange:

     bash$ f () { for i in $*; do echo +"$i"+; done; }; f 'aa' 'b' '' +a+ +a+ +b+ zsh% f () { for i in $*; do echo +"$i"+; done; }; f 'aa' 'b' '' +a a+ +b+ 

    (En général, Zsh ne divise pas datatables textuelles en utilisant IFS, sauf si explicitement demandé, mais remarquez que l'argument vide est inattendu dans la list.)