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 IFS
Variable 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..."
. c
war in der Bourne-Shell ein Leerzeichen, ist jetzt aber IFS
in 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 , svim
das vim
mit 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 ksh
4 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 IFS
auf 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 IFS
auf einen nicht standardmäßigen Wert festgelegt ist und Sie vergessen haben, einige Erweiterungen in Anführungszeichen zu setzen.
³ echo
wird seine Argumente möglicherweise nicht richtig drucken, wenn das erste mit -
Backslashes beginnt oder welche enthält. print
Man kann angewiesen werden, keine Backslash-Verarbeitung durchzuführen -r
und 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 $IFS
das 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 $IFS
aufgeteilt 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 bosh
haben local
stattdessen dafür. bash
, yash
und zsh
haben außerdem typeset
, als Alias zu local
(auch zu declare
in Bash und Zsh), mit dem Vorbehalt, dass in bash
nur local
in 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 grep
und nicht der von ist cvs
) cvssm
verhält sich der Aufruf mit einigen Argumenten wie ein Aufruf cvs -nq update
mit 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/sh
ist 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/sh
ist es sicher, sodass nur #!/bin/sh
Skripte betroffen sind, nicht #!/usr/bin/env sh
Skripte, 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 IFS
Variablen. Wenn der Wert von IFS
die leere Zeichenfolge ist, ist das Trennzeichen die leere Zeichenfolge.) Wenn keine Positionsparameter vorhanden sind, "$*"
ist die leere Zeichenfolge, wenn zwei Positionsparameter vorhanden sind und IFS
seinen 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
, baz
und 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 myuseradd
handelt es sich lediglich um einen Wrapper, useradd
der 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 "$@"
.