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

73

Considérons le code suivant:

foo () {
    echo $*
}

bar () {
    echo $@
}

foo 1 2 3 4
bar 1 2 3 4

Il produit:

1 2 3 4

1 2 3 4

J'utilise Ksh88, mais je m'intéresse également à d'autres coques communes. Si vous connaissez des particularités pour des coquilles spécifiques, veuillez les mentionner.

J'ai trouvé ce qui suit dans la page de manuel Ksh de Solaris:

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

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

Rahmu
la source

Réponses:

96

Quand ils ne sont pas cités $*et $@sont les mêmes. Vous ne devez utiliser ni l'un ni l'autre, car ils peuvent se rompre 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, il cs’agit d’un espace, mais c’est en fait le premier caractère de IFSvotre choix.

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

joindre des arguments avec une virgule (version simple)

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

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

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

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

C'est presque toujours ce que tu veux. Il étend chaque paramètre de position en un mot distinct, ce qui le rend idéal pour prendre des arguments de ligne de commande ou de fonction et les transmettre ensuite à une autre commande ou fonction. Et comme il utilise davantage les guillemets, les choses ne se cassent pas si, par exemple, "$1"contient un espace ou un astérisque ( *).


Écrivons un script appelé svimqui fonctionne vimavec 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 "$@"

Tous ces éléments conviendront pour des cas simples, par exemple un nom de fichier unique ne contenant 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 "$@"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, seul "$@"fonctionnera correctement tout le temps.


typesetest comment créer une variable locale ksh( bashet l’ ashutiliser à la localplace). Cela signifie que IFSsa valeur précédente sera rétablie lorsque la fonction reviendra. Ceci est important, car les commandes que vous exécuterez par la suite risquent de ne pas fonctionner correctement si vous IFSdéfinissez un paramètre non standard.

Mikel
la source
2
Excellente explication, merci beaucoup.
Rahmu
Merci pour un exemple d'utilisation $*. Je pensais toujours que c'était complètement inutile ... joindre avec un délimiteur est un bon cas d'utilisation.
Anishsane
35

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

"$@"est une syntaxe assez étrange. Il est remplacé par tous les paramètres de position, sous forme de champs distincts. S'il n'y a pas de paramètre de position ( $#vaut 0), alors se "$@"développe jusqu'à rien (pas une chaîne vide, mais une liste avec 0 éléments), s'il existe un paramètre de position "$@"équivalent à "$1", s'il existe deux paramètres de position "$@"é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 effectuant des tâches telles que la définition de variables d'environnement, la préparation de fichiers de données, etc. avant d'appeler une commande avec les mêmes arguments et options que ceux utilisés pour le wrapper.

Par exemple, la fonction suivante filtre la sortie de cvs -nq update. Outre le filtrage de sortie et le statut de retour (qui est celui de grepplutôt que celui de cvs), l'appel cvssmde certains arguments se comporte comme un appel cvs -nq updateavec ces arguments.

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

"$@"é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 dans zsh). Là encore, les guillemets doubles sont quelque peu trompeurs: ils protègent contre la division des champs et la génération de motifs des éléments du tableau, mais chaque élément du tableau se termine dans son propre champ.

Certains anciens coquillages avaient ce qu’on pourrait appeler un bogue: quand il n’y avait pas d’arguments de position, ils étaient "$@"étendus à un seul champ contenant une chaîne vide plutôt qu’à aucun champ. Cela a conduit à la solution de contournement${1+"$@"} (rendue célèbre via la documentation Perl ). Seules les versions plus anciennes du shell Bourne et de l'implémentation OSF1 sont concernées, aucun de ses remplacements compatibles actuels (ash, ksh, bash,…) ne le sont. /bin/shn'est pas affecté par les systèmes publiés au 21ème siècle que je connaisse (à moins que vous ne comptiez la version de maintenance Tru64, et même s'il /usr/xpg4/bin/shexiste en toute sécurité, seuls les #!/bin/shscripts sont affectés, pas les #!/usr/bin/env shscripts tant que votre PATH est configuré pour la conformité POSIX) . En bref, il s’agit d’une anecdote historique dont vous ne devez pas vous inquiéter.


"$*"s'étend toujours à un mot. Ce mot contient les paramètres de position, concaténés avec un espace entre eux. (Plus généralement, le séparateur est le premier caractère de la valeur de la IFSvariable. Si la valeur de IFSest la chaîne vide, le séparateur est la chaîne vide.) S'il n'y a pas de paramètre de position, "$*"la chaîne est vide, s'il y en a deux paramètres de position et IFSsa valeur par défaut est alors "$*"équivalente à "$1 $2", etc.

$@et les $*citations extérieures sont équivalentes. Ils développent 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 distincts qui sont traités comme des modèles de caractère générique de nom de fichier, comme d'habitude avec les extensions de variable non citées.

Par exemple, si le répertoire actuel contient trois fichiers bar, bazet fooalors:

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`
Gilles, arrête de faire le mal
la source
1
Note historique: sur quelques anciens coquillages Bourne, "$@"s’est en fait étendue à une liste constituée de la chaîne vide: unix.stackexchange.com/questions/68484/…
ninjalj Le
25

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"

Sortie:

% 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 de tableau, il n'y a pas de différence lors de l'utilisation de $*ou $@. Cela n'a de sens que lorsque vous les utilisez avec des guillemets doubles "$*"et "$@".

cuonglm
la source
Excellent exemple! Pouvez-vous expliquer l'utilisation de IFS="^${IFS}", cependant?
Russ
@ Russ: Il vous montre comment la valeur est concat avec le premier caractère de IFS.
jeudi
De la même manière que IFS="^xxxxx"ferait-on? Le ${IFS}suffixe de fin m'a fait penser que vous faisiez quelque chose de plus délicat, comme de récupérer automatiquement le fichier IFS d'origine à la fin (par exemple: le premier caractère décalé automatiquement ou quelque chose du genre).
Russ
11

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 bash. Pour autant que je sache, ksh ne devrait pas différer beaucoup. Essentiellement, citer $*traitera tout comme un seul mot et $@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 IFSvariable avec $*, considérons ceci

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

Je reçois ceci comme résultat:

$ fooifs 1 2 3 4
1c2c3c4

En outre, je viens de confirmer que cela fonctionne de la même manière ksh. Les deux bashet kshtesté ici étaient sous OSX mais je ne vois pas en quoi cela importait beaucoup.

Wojtek Rzepala
la source
"changé dans une fonction - n'affecte pas le global". Avez-vous testé cela?
Mikel
Oui, j'ai vérifié ça. Pour la tranquillité d'esprit, j'ajouterai le unset IFSà la fin pour le réinitialiser à l'original, mais cela n'a fonctionné pour moi aucun problème et cela a echo $IFSeu pour résultat la sortie standard que j'en tire . Le fait de IFSdéfinir les accolades introduit une nouvelle portée. Par conséquent, si vous ne l’exportez pas, cela n’affectera pas l’extérieur IFS.
Wojtek Rzepala Le
echo $IFSne prouve rien, car le shell voit le ,, mais fait ensuite le fractionnement de mots en utilisant IFS! Essayez echo "$IFS".
Mikel
bon point. le désarmement IFSdevrait résoudre cela.
Wojtek Rzepala
À moins d' IFSavoir une valeur personnalisée différente avant d'appeler la fonction. Mais oui, la plupart du temps, désactiver l'IFS fonctionnera.
Mikel
6

La différence est importante lors de l'écriture de scripts qui doivent utiliser les paramètres de position 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, myuseraddc'est juste un wrapper car il useraddaccepte les mêmes paramètres, mais ajoute un quota pour l'utilisateur:

#!/bin/bash -e

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

Notez l'appel à useradd "$@", avec $@cité. Cela respectera les paramètres et les enverra en l'état useradd. Si vous deviez donner un guillemet $@(ou utiliser $*également un guillemet), useradd verrait 5 paramètres, le 3ème paramètre contenant un espace serait scindé en deux:

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

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

Donc, en bref, si vous avez besoin de travailler avec des paramètres respectant les paramètres multi-mots, utilisez "$@".

Carlos Campderrós
la source
4
   *      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, un comportement similaire.

se ruer
la source
2

Parler des différences entre zshet bash:

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

 $ 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 les guillemets, les résultats sont les mêmes pour $*et $@, mais sont différents dans bashet dans zsh. Dans ce cas, zshmontre 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 remarque qu'ici l'argument vide manque de manière inattendue dans la liste.)

Stéphane Gimenez
la source
1
En zsh, ce $@n'est pas spécial à cet égard: $xétend au plus un mot, mais les variables vides développent à rien (pas un mot vide). Essayez print -l a $foo bavec foovide ou indéfini.
Gilles, arrête de faire le mal
0

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

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

Étant donné que les URL sont souvent divisés avec un +, mais mon clavier rend   plus facile à atteindre que +, $*+ se $IFSsentir utile.

isomorphismes
la source