Was ist der Unterschied zwischen $* und $@?

Was ist der Unterschied zwischen $* und $@?

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 verwende Ksh88, interessiere mich aber auch für andere gängige Shells. Wenn Sie Besonderheiten zu bestimmten Shells kennen, erwähnen Sie diese bitte.

Folgendes habe ich auf der Ksh-Manpage unter Solaris gefunden:

Die Bedeutung von $* und $@ ist identisch, wenn sie nicht in Anführungszeichen gesetzt sind oder als Parameterzuweisungswert oder als Dateiname verwendet werden. Wenn $* jedoch als Befehlsargument verwendet wird, entspricht es ``$1d$2d...'', wobei d das erste Zeichen der IFS-Variable ist, während $@ $1 $2 ... entspricht.

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

Antwort1

Wenn sie nicht in Anführungszeichen stehen $*und $@identisch 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 einzelnen Wort erweitert "$1c$2c...". cwar in der Bourne-Shell ein Leerzeichen, ist jetzt aber IFSin modernen Bourne-ähnlichen Shells (von ksh und von POSIX für sh spezifiziert) das erste Zeichen von . Es kann also alles sein, was Sie möchten¹.

Die einzige sinnvolle Verwendung, die ich je dafür gefunden habe, ist:

Argumente mit Komma verbinden(einfache Version)

function join1 {
    typeset IFS=,      # typeset makes a local variable in ksh²
    print -r -- "$*"   # using print instead of unreliable echo³
}

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

Argumente mit dem angegebenen Trennzeichen verbinden(bessere Version)

function join2 {
    typeset IFS="$1"
    shift
    print -r -- "$*"
}

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

"$@"wird in einzelne Wörter erweitert:"$1" "$2" ...

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


Schreiben wir ein Skript namens , svimdas vimmit ausgeführt wird sudo. Um den Unterschied zu verdeutlichen, führen wir drei Versionen durch.

svim1

#!/bin/sh
sudo vim $*

svim2

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

svim3

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

In einfachen Fällen sind alle ausreichend, z. B. ein einzelner Dateiname, der keine Leerzeichen enthält:

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

Aber nur $*und "$@"funktioniert ordnungsgemäß, 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 funktioniert nur "$*"dann "$@"ordnungsgemäß, 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"

Nur so "$@"wird es immer einwandfrei funktionieren.


¹ Vorsicht jedoch: In einigen Shells funktioniert es nicht für Multibyte-Zeichen.

² typeset, das zum Festlegen von Typen und Attributen von Variablen verwendet wird, macht eine Variable in ksh4 auch lokal (in ksh93 gilt dies nur für Funktionen, die mit der Korn- function f {}Syntax definiert sind, nicht mit der Bourne- f() ...Syntax). Das bedeutet, dass here IFSauf seinen vorherigen Wert zurückgesetzt wird, wenn die Funktion zurückkehrt. Das ist wichtig, weil die Befehle, die Sie anschließend ausführen, möglicherweise nicht wie erwartet funktionieren, wenn IFSauf einen nicht standardmäßigen Wert festgelegt ist und Sie vergessen haben, einige Erweiterungen in Anführungszeichen zu setzen.

³ echowird seine Argumente möglicherweise nicht richtig drucken, wenn das erste mit -Backslashes beginnt oder welche enthält. printMan kann angewiesen werden, keine Backslash-Verarbeitung durchzuführen -rund sich vor Argumenten zu hüten, die mit -oder +mit dem Optionstrennzeichen --(oder ) beginnen. wäre die Standardalternative, aber beachten Sie, dass ksh88 und pdksh und einige seiner Derivate noch keine integrierten Funktionen haben .-printf '%s\n' "$*"printf

4 Beachten Sie, dass dies "$@"in der Bourne-Shell und in ksh88 nicht richtig funktionierte, wenn $IFSdas Leerzeichen nicht enthalten war, da es effektiv so implementiert wurde, dass der Positionsparameter mit „nicht in Anführungszeichen gesetzten“ Leerzeichen verbunden wurde und das Ergebnis $IFSaufgeteilt wurde. Frühe Versionen der Bourne-Shell hatten auch diesen Fehler, der "$@"zu einem leeren Argument erweitert wurde, wenn kein Positionsparameter vorhanden war, was einer der Gründe ist, warum Sie manchmal ${1+"$@"}anstelle von sehen "$@". Keiner dieser Fehler betrifft moderne Bourne-ähnliche Shells.

5 Die Almquist-Shell und boshhaben localstattdessen dafür. bash, yashund zshhaben außerdem typeset, als Alias ​​zu local(auch zu declarein Bash und Zsh), mit dem Vorbehalt, dass in bashnur localin einer Funktion verwendet werden kann.

Antwort2

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

"$@"ist eine ziemlich seltsame Syntax. Sie wird durch alle Positionsparameter als separate Felder ersetzt. Wenn keine Positionsparameter vorhanden sind ( $#ist 0), "$@"wird nichts ausgegeben (keine leere Zeichenfolge, sondern eine Liste mit 0 Elementen), wenn ein Positionsparameter vorhanden ist, "$@"ist äquivalent zu "$1", wenn zwei Positionsparameter vorhanden sind, "$@"ist äquivalent zu "$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 beispielsweise Umgebungsvariablen festlegen, Datendateien vorbereiten usw., bevor ein Befehl mit denselben Argumenten und Optionen aufgerufen wird, mit denen der Wrapper aufgerufen wurde.

Beispielsweise filtert die folgende Funktion die Ausgabe von cvs -nq update. Abgesehen von der Ausgabefilterung und dem Rückgabestatus (der der von grepund nicht der von ist cvs) cvssmverhält sich der Aufruf mit einigen Argumenten wie ein Aufruf cvs -nq updatemit diesen Argumenten.

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

"$@"expandiert zur Liste der Positionsparameter. In Shells, die Arrays unterstützen, gibt es eine ähnliche Syntax, um zur Liste der Elemente des Arrays zu expandieren: "${array[@]}"(die Klammern sind obligatorisch, außer in zsh). Auch hier sind die doppelten Anführungszeichen etwas irreführend: Sie schützen vor Feldaufteilung und Mustergenerierung der Array-Elemente, aber jedes Array-Element landet in seinem eigenen Feld.

Einige alte Shells hatten einen Bug: Wenn es keine Positionsargumente gab, "$@"wurde auf ein einzelnes Feld mit einer leeren Zeichenfolge erweitert, anstatt auf kein Feld. Dies führte zu demProblemumgehung${1+"$@"}(gemachtbekannt durch die Perl-Dokumentation). Betroffen sind nur ältere Versionen der eigentlichen Bourne-Shell und der OSF1-Implementierung, keine ihrer modernen kompatiblen Ersetzungen (ash, ksh, bash, …). /bin/shist meines Wissens auf keinem System betroffen, das im 21. Jahrhundert veröffentlicht wurde (es sei denn, Sie zählen die Wartungsversion von Tru64 dazu, und selbst dort /usr/xpg4/bin/shist es sicher, sodass nur #!/bin/shSkripte betroffen sind, nicht #!/usr/bin/env shSkripte, solange Ihr PATH für POSIX-Konformitä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. (Allgemeiner gesagt ist das Trennzeichen das erste Zeichen des Wertes 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 Positionsparameter vorhanden sind und IFSseinen Standardwert hat, "$*"ist äquivalent zu "$1 $2"usw.

$@und $*Anführungszeichen außerhalb sind gleichwertig. Sie werden in die Liste der Positionsparameter als separate Felder erweitert, wie "$@";, aber jedes resultierende Feld wird dann in separate Felder aufgeteilt, die als Platzhaltermuster für Dateinamen behandelt werden, wie bei nicht in Anführungszeichen gesetzten Variablenerweiterungen üblich.

Wenn das aktuelle Verzeichnis beispielsweise die drei Dateien bar, bazund enthält foo, dann gilt:

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`

Antwort3

Hier ist ein einfaches Skript, das den Unterschied zwischen $*und demonstriert $@:

#!/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 macht es keinen Unterschied, wenn Sie $*oder verwenden $@. Es macht nur Sinn, wenn Sie sie mit doppelten Anführungszeichen "$*"und verwenden "$@".

Antwort4

Der Unterschied ist wichtig beim Schreiben von Skripten, die die Positionsparameter auf die richtige Weise verwenden sollen ...

Stellen Sie sich folgenden Anruf 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 myuseraddhandelt es sich lediglich um einen Wrapper, useraddder dieselben 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 von useradd "$@", mit $@Anführungszeichen. Dadurch werden die Parameter berücksichtigt und so wie sie sind an gesendet useradd. Wenn Sie die Anführungszeichen entfernen $@(oder auch ohne Anführungszeichen verwenden $*), würde useradd sehen5Parameter, da der 3. Parameter, der ein Leerzeichen enthielt, in zwei Teile aufgeteilt würde:

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

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

Kurz gesagt: Wenn Sie mit Parametern arbeiten müssen, die aus mehreren Wörtern bestehen, verwenden Sie "$@".

verwandte Informationen