Neufassung eines jetzt gelöschten respuesta por VonC .
Robert Gamble Die lapidare Antwort des Autors geht direkt auf diese Frage ein. In dieser Antwort wird auf einige Probleme mit Dateinamen, die Leerzeichen enthalten, eingegangen.
Siehe auch: ${1:+"$@"} in /bin/sh
Grundlegende These: "$@"
korrekt ist, und $*
(nicht zitiert) ist fast immer falsch. Der Grund dafür ist "$@"
funktioniert einwandfrei, wenn die Argumente Leerzeichen enthalten, und funktioniert genauso wie $*
wenn sie es nicht tun. Unter bestimmten Umständen, "$*"
ist auch in Ordnung, aber "$@"
normalerweise (aber nicht immer) an denselben Orten. Nicht zitiert, $@
y $*
sind gleichwertig (und fast immer falsch).
Was ist also der Unterschied zwischen $*
, $@
, "$*"
und "$@"
? Sie beziehen sich alle auf "alle Argumente für die Shell", aber sie tun unterschiedliche Dinge. Wenn unquotiert, $*
y $@
das Gleiche tun. Sie behandeln jedes "Wort" (Folge von Nicht-Leerzeichen) als separates Argument. Die Anführungszeichen sind jedoch recht unterschiedlich: "$*"
behandelt die Argumentliste als eine einzige durch Leerzeichen getrennte Zeichenkette, während "$@"
behandelt die Argumente fast genau so, wie sie in der Befehlszeile angegeben wurden. "$@"
expandiert zu nichts, wenn es keine Positionsargumente gibt; "$*"
zu einer leeren Zeichenkette expandiert - und ja, es gibt einen Unterschied, auch wenn er manchmal schwer zu erkennen ist. Weitere Informationen finden Sie unten, nach der Einführung des (nicht standardisierten) Befehls al
.
Sekundäre These: wenn Sie Argumente mit Leerzeichen verarbeiten müssen und dann an andere Befehle weitergeben müssen, dann brauchen Sie manchmal nicht standardisierte Werkzeuge zur Unterstützung. (Oder Sie sollten Arrays verwenden, und zwar vorsichtig: "${array[@]}"
verhält sich analog zu "$@"
.)
Beispiel:
$ mkdir "my dir" anotherdir
$ ls
anotherdir my dir
$ cp /dev/null "my dir/my file"
$ cp /dev/null "anotherdir/myfile"
$ ls -Fltr
total 0
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 my dir/
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 anotherdir/
$ ls -Fltr *
my dir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file
anotherdir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile
$ ls -Fltr "./my dir" "./anotherdir"
./my dir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file
./anotherdir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile
$ var='"./my dir" "./anotherdir"' && echo $var
"./my dir" "./anotherdir"
$ ls -Fltr $var
ls: "./anotherdir": No such file or directory
ls: "./my: No such file or directory
ls: dir": No such file or directory
$
Warum klappt das nicht? Es funktioniert nicht, weil die Shell Anführungszeichen verarbeitet, bevor sie die Variablen expandiert. Um die Shell dazu zu bringen, die Anführungszeichen zu beachten, die in $var
, müssen Sie eval
:
$ eval ls -Fltr $var
./my dir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file
./anotherdir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile
$
Richtig knifflig wird es, wenn Sie Dateinamen haben wie " He said, "Don't do this!"
"(mit Anführungszeichen, doppelten Anführungszeichen und Leerzeichen).
$ cp /dev/null "He said, \"Don't do this!\""
$ ls
He said, "Don't do this!" anotherdir my dir
$ ls -l
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 15:54 He said, "Don't do this!"
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 anotherdir
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 my dir
$
Die Shells (alle) machen es nicht besonders einfach, mit solchen nicht besonders einfach, so dass (lustigerweise) viele Unix-Programme keine gute Arbeit leisten, um sie zu behandeln. Unter Unix kann ein Dateiname (eine einzelne Komponente) alle Zeichen enthalten außer Schrägstrich und NUL '\0'
. Die Shells empfehlen jedoch dringend, keine Leerzeichen, Zeilenumbrüche oder Tabulatoren in einem Pfadnamen zu vermeiden. Das ist auch der Grund, warum die Standard-Unix-Dateinamen keine Leerzeichen usw. enthalten.
Beim Umgang mit Dateinamen, die Leerzeichen und andere Zeichen enthalten können müssen Sie extrem vorsichtig sein, und ich habe vor langer Zeit herausgefunden Ich habe vor langer Zeit festgestellt, dass ich ein Programm brauche, das unter Unix nicht zum Standard gehört. Ich nenne es escape
(Version 1.1 war datiert auf 1989-08-23T16:01:45Z).
Hier ist ein Beispiel für escape
im Einsatz - mit dem SCCS-Kontrollsystem. Es handelt sich um ein Cover-Skript, das sowohl eine delta
(denken Einchecken ) und eine get
(denken Check-out ). Verschiedene Argumente, insbesondere -y
(der Grund, warum Sie die Änderung vorgenommen haben) würde Leerzeichen und Zeilenumbrüche enthalten. Beachten Sie, dass das Skript aus dem Jahr 1992 stammt und daher Back-Ticks anstelle von $(cmd ...)
Notation und verwendet nicht #!/bin/sh
in der ersten Zeile.
: "@(#)$Id: delget.sh,v 1.8 1992/12/29 10:46:21 jl Exp $"
#
# Delta and get files
# Uses escape to allow for all weird combinations of quotes in arguments
case `basename $0 .sh` in
deledit) eflag="-e";;
esac
sflag="-s"
for arg in "$@"
do
case "$arg" in
-r*) gargs="$gargs `escape \"$arg\"`"
dargs="$dargs `escape \"$arg\"`"
;;
-e) gargs="$gargs `escape \"$arg\"`"
sflag=""
eflag=""
;;
-*) dargs="$dargs `escape \"$arg\"`"
;;
*) gargs="$gargs `escape \"$arg\"`"
dargs="$dargs `escape \"$arg\"`"
;;
esac
done
eval delta "$dargs" && eval get $eflag $sflag "$gargs"
(Heutzutage würde ich escape wahrscheinlich nicht mehr ganz so gründlich verwenden - es ist nicht benötigt mit dem -e
Argument, zum Beispiel - aber insgesamt ist das eines meiner einfacheren Skripte mit escape
.)
Le site escape
Programm gibt einfach seine Argumente aus, ähnlich wie echo
tut, aber es stellt sicher, dass die Argumente für die Verwendung mit eval
(eine Ebene von eval
Ich habe ein Programm, das eine entfernte Shell ausführt, und das die Ausgabe von escape
).
$ escape $var
'"./my' 'dir"' '"./anotherdir"'
$ escape "$var"
'"./my dir" "./anotherdir"'
$ escape x y z
x y z
$
Ich habe ein anderes Programm namens al
die ihre Argumente einzeln pro Zeile auflistet (und es ist sogar noch älter: Version 1.1 vom 1987-01-27T14:35:49). Es ist sehr nützlich beim Debuggen von Skripten, da es in eine Befehlszeile eingefügt werden kann, um zu sehen, welche Argumente tatsächlich an den Befehl übergeben werden.
$ echo "$var"
"./my dir" "./anotherdir"
$ al $var
"./my
dir"
"./anotherdir"
$ al "$var"
"./my dir" "./anotherdir"
$
[ Hinzugefügt: Um nun den Unterschied zwischen den verschiedenen "$@"
Notationen, hier ist ein weiteres Beispiel:
$ cat xx.sh
set -x
al $@
al $*
al "$*"
al "$@"
$ sh xx.sh * */*
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al 'He said, "Don'\''t do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file'
He said, "Don't do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file
+ al 'He said, "Don'\''t do this!"' anotherdir 'my dir' xx.sh anotherdir/myfile 'my dir/my file'
He said, "Don't do this!"
anotherdir
my dir
xx.sh
anotherdir/myfile
my dir/my file
$
Beachten Sie, dass die ursprünglichen Leerzeichen zwischen den Zeilen nicht erhalten bleiben. *
y */*
in der Befehlszeile. Beachten Sie auch, dass Sie die "Befehlszeilenargumente" in der Shell ändern können, indem Sie:
set -- -new -opt and "arg with space"
Dies setzt 4 Optionen, ' -new
', ' -opt
', ' and
', und ' arg with space
'.
]
Hmm, das ist eine ziemlich lange respuesta - vielleicht Exegese ist der bessere Begriff. Quellcode für escape
auf Anfrage erhältlich (E-Mail an vorname punkt nachname at gmail dot com). Der Quellcode für al
ist denkbar einfach:
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
Das war's schon. Es ist gleichbedeutend mit dem test.sh
Skript, das Robert Gamble gezeigt hat, und könnte als Shell-Funktion geschrieben werden (aber Shell-Funktionen gab es in der lokalen Version der Bourne-Shell noch nicht, als ich das erste Mal schrieb al
).
Beachten Sie auch, dass Sie schreiben können al
als einfaches Shell-Skript:
[ $# != 0 ] && printf "%s\n" "$@"
Die Bedingung wird benötigt, damit es keine Ausgabe erzeugt, wenn keine Argumente übergeben werden. Die printf
erzeugt eine leere Zeile nur mit dem Argument format string, aber das C-Programm erzeugt nichts.