Was ist der Unterschied zwischen $ * und $ @?

73

Betrachten Sie den folgenden Code:

foo () {
    echo $*
}

bar () {
    echo $@
}

foo 1 2 3 4
bar 1 2 3 4

Es gibt aus:

1 2 3 4

1 2 3 4

Ich benutze Ksh88, aber ich interessiere mich auch für andere gebräuchliche Shells. Wenn Sie eine Besonderheit für bestimmte Muscheln kennen, erwähnen Sie diese bitte.

Ich habe folgendes in der Ksh-Manpage auf Solaris gefunden:

Die Bedeutung von $ * und $ @ ist identisch, wenn sie nicht in Anführungszeichen oder als Parametrierungswert oder als Dateiname verwendet werden. Bei Verwendung als Befehlsargument entspricht $ * jedoch "$ 1d $ 2d ...", wobei d das erste Zeichen der IFS-Variablen ist, während $ @ $ 1 $ 2 entspricht ....

Ich habe versucht, die IFSVariable zu ändern, aber die Ausgabe wird nicht geändert. Vielleicht mache ich etwas falsch?

rahmu
quelle

Antworten:

96

Wenn sie nicht zitiert werden $*und gleich $@sind. Sie sollten keines von beiden verwenden, da sie unerwartet abbrechen können, sobald Sie Argumente haben, die Leerzeichen oder Platzhalter enthalten.


"$*"wird zu einem einzigen Wort erweitert "$1c$2c...". Normalerweise cist es ein Leerzeichen, aber es ist tatsächlich das erste Zeichen von IFS, also kann es alles sein, was Sie wählen.

Die einzige gute Verwendung, die ich jemals dafür gefunden habe, ist:

Argumente mit Komma verbinden (einfache Version)

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

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

Argumente mit dem angegebenen Trennzeichen verbinden (bessere Version)

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

join2 + a b c   # => a+b+c

"$@" erweitert, um Wörter zu trennen: "$1" "$2" ...

Das ist fast immer was Sie wollen. Es erweitert jeden Positionsparameter zu einem eigenen Wort, wodurch es sich perfekt eignet, um Befehlszeilen- oder Funktionsargumente aufzunehmen und sie dann an einen anderen Befehl oder eine andere Funktion weiterzuleiten. Und weil es mit doppelten Anführungszeichen erweitert wird, bedeutet dies, dass Dinge nicht kaputt gehen, wenn sie beispielsweise "$1"ein Leerzeichen oder ein Sternchen ( *) enthalten.


Lassen Sie uns ein Skript namens schreiben svim, das vimmit läuft sudo. Wir werden drei Versionen machen, um den Unterschied zu veranschaulichen.

svim1

#!/bin/sh
sudo vim $*

svim2

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

svim3

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

Für einfache Fälle, z. B. einen einzelnen Dateinamen, der keine Leerzeichen enthält, sind alle in Ordnung:

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

Aber nur $*und "$@"richtig arbeiten, wenn Sie mehrere Argumente haben.

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"

Und nur "$*"und "$@"richtig funktionieren, wenn Sie Argumente haben, die Leerzeichen enthalten.

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"

So "$@"funktioniert nur die ganze Zeit richtig.


typesetSo erstellen Sie eine lokale Variable in ksh( bashund ashverwenden sie localstattdessen). Es bedeutet IFS, dass der vorherige Wert wiederhergestellt wird, wenn die Funktion zurückkehrt. Dies ist wichtig, da die Befehle, die Sie anschließend ausführen, möglicherweise nicht ordnungsgemäß funktionieren, wenn sie IFSauf einen nicht standardmäßigen Wert festgelegt sind.

Mikel
quelle
2
Wunderbare Erklärung, vielen Dank.
Rahmu
Vielen Dank für ein Anwendungsbeispiel $*. Ich habe immer gedacht, dass es völlig nutzlos ist. Join with Delimiter ist ein guter Anwendungsfall.
Anishsane
35

Kurze Antwort: Verwenden Sie"$@" (beachten Sie die doppelten Anführungszeichen). Die anderen Formen sind sehr selten nützlich.

"$@"ist eine ziemlich seltsame Syntax. Es wird durch alle Positionsparameter als separate Felder ersetzt. Wenn es keine Positionsparameter gibt ( $#ist 0), wird "$@"zu nichts erweitert (keine leere Zeichenfolge, sondern eine Liste mit 0 Elementen). Wenn es einen Positionsparameter "$@"gibt, entspricht dies "$1", wenn es zwei Positionsparameter "$@"gibt, entspricht dies "$1" "$2", usw.

"$@"Ermöglicht es Ihnen, die Argumente eines Skripts oder einer Funktion an einen anderen Befehl weiterzugeben. Dies ist sehr nützlich für Wrapper, die z. B. Umgebungsvariablen festlegen, Datendateien vorbereiten usw., bevor sie einen Befehl mit denselben Argumenten und Optionen aufrufen, mit denen der Wrapper aufgerufen wurde.

Die folgende Funktion filtert beispielsweise die Ausgabe von cvs -nq update. Abgesehen von der Ausgabefilterung und dem Rückgabestatus (der dem von grepund nicht dem von entspricht cvs) cvssmverhält sich das Aufrufen einiger Argumente wie das Aufrufen cvs -nq updatemit diesen Argumenten.

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

"$@"Erweitert die Liste der Positionsparameter. In Shells, die Arrays unterstützen, gibt es eine ähnliche Syntax, um die Liste der Elemente des Arrays zu erweitern: "${array[@]}"(die geschweiften Klammern sind mit Ausnahme von zsh obligatorisch). Auch hier sind die Anführungszeichen etwas irreführend: Sie schützen vor Feldteilung und Mustererzeugung der Array-Elemente, aber jedes Array-Element landet in einem eigenen Feld.

Einige alte Muscheln hatten vermutlich einen Fehler: Wenn es keine Positionsargumente gab, wurden "$@"sie auf ein einzelnes Feld erweitert, das eine leere Zeichenfolge enthielt, und nicht auf ein Feld. Dies führte zur Problemumgehung${1+"$@"} ( bekannt geworden durch die Perl-Dokumentation ). Es sind nur ältere Versionen der eigentlichen Bourne-Shell und der OSF1-Implementierung betroffen, keine der modernen kompatiblen Ersetzungen (ash, ksh, bash,…). /bin/shist auf keinem System betroffen, das im 21. Jahrhundert auf den Markt gebracht wurde (es sei denn, Sie zählen die Tru64-Wartungsversion und es /usr/xpg4/bin/shist auch sicher, dass nur Skripte #!/bin/shbetroffen sind, keine #!/usr/bin/env shSkripte, solange Ihr PATH für die POSIX-Kompatibilität eingerichtet ist). . Kurz gesagt, dies ist eine historische Anekdote, über die Sie sich keine Sorgen machen müssen.


"$*"wird immer zu einem Wort erweitert. Dieses Wort enthält die Positionsparameter, die mit einem Leerzeichen dazwischen verkettet sind. (Im Allgemeinen ist das Trennzeichen das erste Zeichen des Werts der IFSVariablen. Wenn der Wert von IFSdie leere Zeichenfolge ist, ist das Trennzeichen die leere Zeichenfolge.) Wenn keine Positionsparameter vorhanden sind, "$*"ist die leere Zeichenfolge, wenn zwei vorhanden sind Positionsparameter und IFShat einen Standardwert dann "$*"entspricht "$1 $2"usw.

$@und $*äußere Anführungszeichen sind gleichwertig. Sie erweitern die Liste der Positionsparameter als separate Felder, z "$@". Jedes resultierende Feld wird dann jedoch in separate Felder aufgeteilt, die wie üblich mit nicht zitierten Variablenerweiterungen als Platzhaltermuster für Dateinamen behandelt werden.

Wenn das aktuelle Verzeichnis beispielsweise drei Dateien enthält bar, bazund foodann:

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
quelle
1
Historische Anmerkung: Auf einigen alten Bourne-Muscheln wurde "$@"tatsächlich eine Liste erweitert, die aus der leeren Zeichenfolge besteht: unix.stackexchange.com/questions/68484/…
ninjalj
25

Hier ist ein einfaches Skript, um den Unterschied zwischen $*und zu demonstrieren $@:

#!/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"

Ausgabe:

% 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<==

In der Array-Syntax gibt es keinen Unterschied, wenn Sie $*oder verwenden $@. Es macht nur Sinn, wenn Sie sie mit doppelten Anführungszeichen "$*"und verwenden "$@".

cuonglm
quelle
Exzellentes Beispiel! Können Sie die Verwendung von jedoch erklären IFS="^${IFS}"?
Russ
@Russ: Es zeigt Ihnen, wie der Wert mit dem ersten Zeichen in konkat ist IFS.
Donnerstag,
Auf die gleiche Weise IFS="^xxxxx"würde das tun? Das abschließende ${IFS}Suffix hat mich glauben lassen, dass Sie etwas schwierigeres tun, etwa das automatische Wiederherstellen des ursprünglichen IFS am Ende (z. B .: erstes Zeichen wird automatisch verschoben oder so).
Russ
11

Der von Ihnen eingegebene Code liefert das gleiche Ergebnis. Versuchen Sie Folgendes, um es besser zu verstehen:

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

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

Die Ausgabe sollte jetzt anders sein. Folgendes bekomme ich:

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

Das hat bei mir geklappt bash. Soweit ich weiß, sollte sich ksh nicht wesentlich unterscheiden. Im Wesentlichen wird beim Zitieren $*alles als ein Wort behandelt, und beim Zitieren $@wird die Liste als separate Wörter behandelt, wie im obigen Beispiel zu sehen ist.

Betrachten Sie dies als Beispiel für die Verwendung der IFSVariablen mit$*

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

Ich bekomme das als Ergebnis:

$ fooifs 1 2 3 4
1c2c3c4

Außerdem habe ich gerade bestätigt, dass es in der gleichen Weise funktioniert ksh. Beide bashund kshdie hier getesteten waren unter OSX, aber ich kann nicht sehen, wie wichtig das sein würde.

Wojtek Rzepala
quelle
msgstr "innerhalb einer Funktion geändert - wirkt sich nicht auf die globale aus". Hast du das getestet?
Mikel
Ja, das habe ich geprüft. Zur Beruhigung füge ich das unset IFSam Ende hinzu, um es auf das Original zurückzusetzen, aber es funktionierte für mich kein Problem und echo $IFSführte zu der Standardausgabe, die ich davon bekomme. Durch das IFSFestlegen der geschweiften Klammern wird ein neuer Bereich eingeführt. Wenn Sie ihn also nicht exportieren, wirkt er sich nicht auf die Außenseite aus IFS.
Wojtek Rzepala
echo $IFSbeweist gar nichts, denn die Shell sieht das ,, macht dann aber Wortspaltung mit IFS! Versuchen Sie es echo "$IFS".
Mikel
guter Punkt. Uneinstellung IFSsollte das lösen.
Wojtek Rzepala
Es IFSsei denn, vor dem Aufruf der Funktion wurde ein anderer benutzerdefinierter Wert angegeben. Aber ja, meistens funktioniert das Deaktivieren von IFS.
Mikel
6

Der Unterschied ist wichtig, wenn Sie Skripte schreiben, die die Positionsparameter richtig verwenden sollen ...

Stellen Sie sich den folgenden Aufruf vor:

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

Hier gibt es nur 4 Parameter:

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

In meinem Fall myuseraddist dies nur ein Wrapper useradd, der die gleichen Parameter akzeptiert, aber ein Kontingent für den Benutzer hinzufügt:

#!/bin/bash -e

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

Beachten Sie den Aufruf an useradd "$@", mit $@zitiert. Dies respektiert die Parameter und sendet sie so, wie sie sind useradd. Wenn Sie das Anführungszeichen aufheben $@(oder $*auch das Anführungszeichen nicht verwenden), würde useradd 5 Parameter anzeigen , da der dritte Parameter, der ein Leerzeichen enthält, zweigeteilt wird:

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

(und umgekehrt, wenn Sie verwenden würden "$*", würde useradd nur einen Parameter siehe: -m -c Carlos Campderrós ccampderros)

Kurz gesagt, wenn Sie mit Parametern arbeiten müssen, die Mehrwortparameter berücksichtigen, verwenden Sie "$@".

Carlos Campderrós
quelle
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).

// Mann, schlag . ist ksh, afair, ähnliches Verhalten.

eilen
quelle
2

Über Unterschiede zwischen zshund sprechen bash:

Mit Anführungszeichen $@und $*, zshund bashverhalten sich genauso, und ich denke , das Ergebnis unter allen Schalen ziemlich Standard ist:

 $ 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 +

Ohne Anführungszeichen sind die Ergebnisse für $*und gleich $@, unterscheiden sich jedoch in bashund in zsh. In diesem Fall zshzeigt sich ein merkwürdiges Verhalten:

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 teilt normalerweise keine Textdaten mit IFS, es sei denn, dies wird ausdrücklich angefordert. Beachten Sie jedoch, dass hier das leere Argument unerwartet in der Liste fehlt.)

Stéphane Gimenez
quelle
1
In zsh $@ist diesbezüglich nichts Besonderes: Erweitert $xsich zu höchstens einem Wort, aber leere Variablen werden zu nichts erweitert (kein leeres Wort). Versuchen Sie es print -l a $foo bmit fooleeren oder undefinierten.
Gilles
0

Eine der Antworten besagt, dass $*(was ich als "Splat" betrachte) selten nützlich ist.

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

Da URLs oft mit einem geteilt werden +, aber meine Tastatur   leichter zu erreichen ist als +, $*+ $IFSfühle mich lohnenswert.

Isomorphismen
quelle