web-dev-qa-db-fra.com

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 génère:

1 2 3 4

1 2 3 4

J'utilise Ksh88, mais je suis également intéressé par d'autres shells courants. S'il vous arrive de connaître une particularité pour des obus spécifiques, veuillez les mentionner.

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

La signification de $ * et $ @ est identique lorsqu'ils ne sont pas cités ou lorsqu'ils sont utilisés comme valeur de paramétrage ou comme nom de fichier. Cependant, lorsqu'il est utilisé comme argument de commande, $ * est équivalent à `` $ 1d $ 2d ... '', où d est le premier caractère de la variable IFS, tandis que $ @ est équivalent à $ 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?

92
rahmu

Lorsqu'elles ne sont pas citées, $* et $@ sont identiques. Vous ne devez utiliser aucun de ces éléments, car ils peuvent se briser de manière inattendue dès que vous avez des arguments contenant des espaces ou des caractères génériques.


"$*" se développe en un seul mot "$1c$2c...". Généralement, c est un espace, mais c'est en fait le premier caractère de IFS, il peut donc s'agir de tout ce que vous choisissez.

Le seul bon usage que j'aie jamais trouvé est:

joindre les arguments avec une virgule (version simple)

join1() {
    typeset IFS=,
    echo "$*"
}

join1 a b c   # => a,b,c

joindre les 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 + a b c   # => a+b+c

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

C'est presque toujours ce que vous voulez. Il étend chaque paramètre positionnel à un mot distinct, ce qui le rend parfait pour prendre des arguments de ligne de commande ou de fonction, puis les transmettre à une autre commande ou fonction. Et parce qu'il se développe à l'aide de guillemets doubles, cela signifie que les choses ne se cassent pas si, disons, "$1" contient un espace ou un astérisque (*).


Écrivons un script appelé svim qui exécute vim avec Sudo. Nous ferons trois versions pour illustrer la différence.

svim1

#!/bin/sh
Sudo vim $*

svim2

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

svim3

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

Tous conviendront parfaitement aux cas simples, par ex. un nom de fichier unique 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 "$@" fonctionne 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 "$@" fonctionne 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 temps.


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

114
Mikel

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

"$@" est une syntaxe assez étrange. Il est remplacé par tous les paramètres positionnels, sous forme de champs séparés. S'il n'y a pas de paramètres de position ($# est 0), puis "$@" ne se développe en rien (pas une chaîne vide, mais une liste avec 0 éléments), s'il y a un paramètre positionnel alors "$@" est équivalent à "$1", s'il y a deux paramètres de position, alors "$@" est équivalent à "$1" "$2", etc.

"$@" vous permet de transmettre les arguments d'un script ou d'une fonction à une autre commande. Il est très utile pour les wrappers qui font des choses comme définir des variables d'environnement, préparer des fichiers de données, etc. avant d'appeler une commande avec les mêmes arguments et options avec lesquels le wrapper a été appelé.

Par exemple, la fonction suivante filtre la sortie de cvs -nq update. Mis à part le filtrage de sortie et l'état de retour (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 liste des paramètres de position. Dans les shells qui prennent en charge les tableaux, il existe une syntaxe similaire pour développer la liste des éléments du tableau: "${array[@]}" (les accolades sont obligatoires sauf en zsh). Encore une fois, les guillemets doubles sont quelque peu trompeurs: ils protègent contre le fractionnement de champ et la génération de motifs des éléments du tableau, mais chaque élément du tableau se retrouve dans son propre champ.

Certains obus anciens avaient ce qui est sans doute un bug: quand il n'y avait pas d'arguments de position, "$@" développé en un seul champ contenant une chaîne vide, plutôt qu'en aucun champ. Cela a conduit à solution de contournement ${1+"$@"} (made célèbre via la documentation Perl ). Seules les versions plus anciennes du Bourne Shell actuel et de l'implémentation OSF1 sont affectées, aucun de ses remplacements compatibles modernes (ash, ksh, bash,…) le sont. /bin/sh n'est pas affecté sur les systèmes sortis au 21e siècle à ma connaissance (sauf si vous comptez la version de maintenance Tru64, et même là-bas /usr/xpg4/bin/sh est sûr donc seulement #!/bin/sh les scripts sont affectés, pas #!/usr/bin/env sh scripts tant que votre PATH est configuré pour la conformité POSIX). Bref, c'est une anecdote historique dont vous n'avez pas à vous soucier.


"$*" s'étend toujours à un seul mot. Ce mot contient les paramètres 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 chaîne vide, le séparateur est la chaîne vide.) S'il n'y a pas paramètres de position puis "$*" est la chaîne vide, s'il y a deux paramètres positionnels et IFS a sa valeur par défaut alors "$*" est équivalent à "$1 $2", etc.

$@ et $* les guillemets extérieurs sont équivalents. Ils s'étendent à la liste des paramètres de position, sous forme de champs séparés, comme "$@"; mais chaque champ résultant est ensuite divisé en champs séparés qui sont traités comme des modèles génériques de nom de fichier, comme d'habitude avec des extensions de variable non cotées.

Par exemple, si le répertoire actuel contient trois fichiers 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 "a b c"

Production:

% cuonglm at ~
% bash test.sh
Receive 4 parameters

Using $*
==>1<==
==>2<==
==>3<==
==>a<==
==>b<==
==>c<==

Using "$*"
==>1^2^3^a b c<==

Using $@
==>1<==
==>2<==
==>3<==
==>a<==
==>b<==
==>c<==

Using "$@"
==>1<==
==>2<==
==>3<==
==>a b c<==

Dans la syntaxe du tableau, il n'y a aucune différence lors de l'utilisation de $* ou $@. Cela n'a de sens que lorsque vous les utilisez avec des guillemets doubles "$*" et "$@".

27
cuonglm

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 j'obtiens:

$ 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, en citant $* traitera tout comme un seul mot et cite $@ traitera la liste 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ère ceci

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

Je reçois ceci en conséquence:

$ fooifs 1 2 3 4
1c2c3c4

De plus, je viens de confirmer que cela fonctionne de la même manière dans ksh. bash et ksh testés ici étaient sous OSX mais je ne vois pas en quoi cela importerait beaucoup.

11
Wojtek

La différence est importante lors de l'écriture de scripts qui doivent utiliser les paramètres positionnels de la bonne manière ...

Imaginez l'appel suivant:

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

Ici, il n'y a que 4 paramètres:

$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 paramètres, mais ajoute un quota pour l'utilisateur:

#!/bin/bash -e

useradd "$@"
setquota -u "${!#}" 10000 11000 1000 1100

Remarquez l'appel à useradd "$@", avec $@ cité. Cela respectera les paramètres et les enverra tels quels à useradd. Si vous deviez citer $@ (ou pour utiliser $* également sans guillemets), useradd verrait les paramètres 5, car le 3e paramètre qui contenait un espace serait divisé en deux:

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

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

Donc, en bref, si vous devez travailler avec des paramètres respectant les paramètres multi-Word, utilisez "$@".

6
Carlos Campderrós
   *      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 (i.e., they are removed).

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

4
rush

Parler des différences entre zsh et bash:

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

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

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

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

(Zsh ne divise généralement pas les données textuelles à l'aide d'IFS, sauf demande explicite, mais notez qu'ici l'argument vide manque de manière inattendue dans la liste.)

2
Stéphane Gimenez

L'une des réponses dit que $* (Que je considère comme un "splat") est rarement utile.

Je recherche sur google avec G() { IFS='+' ; w3m "https://encrypted.google.com/search?q=$*" ; }

Étant donné que les URL sont souvent divisées avec un +, Mais que mon clavier est plus facile à atteindre que +, $* + $IFS En vaut la peine.

0
isomorphismes