Umschreiben einer jetzt gelöschten Antwort von VonC .
Die prägnante Antwort von Robert Gamble befasst sich direkt mit der Frage. Dieser wird bei einigen Problemen mit Dateinamen, die Leerzeichen enthalten, erweitert.
Siehe auch: $ {1: + "$ @"} in / bin / sh
Grundlegende These: "$@"
ist richtig und $*
(nicht zitiert) ist fast immer falsch. Dies liegt daran, dass es gut "$@"
funktioniert, wenn Argumente Leerzeichen enthalten, und genauso funktioniert, wie $*
wenn dies nicht der Fall ist. Unter bestimmten Umständen "$*"
ist das auch in Ordnung, "$@"
funktioniert aber normalerweise (aber nicht immer) an denselben Orten. Nicht zitiert $@
und $*
gleichwertig (und fast immer falsch).
Also, was ist der Unterschied zwischen $*
, $@
, "$*"
und "$@"
? Sie beziehen sich alle auf 'alle Argumente zur Shell', aber sie machen verschiedene Dinge. Wenn nicht zitiert, $*
und $@
machen Sie das Gleiche. Sie behandeln jedes 'Wort' (Folge von Nicht-Leerzeichen) als separates Argument. Die Anführungszeichen in Anführungszeichen sind jedoch sehr unterschiedlich: "$*"
Behandelt die Argumentliste als einzelne durch Leerzeichen getrennte Zeichenfolge, während "$@"
die Argumente fast genau so behandelt werden, wie sie in der Befehlszeile angegeben wurden.
"$@"
erweitert sich zu nichts, wenn es keine Positionsargumente gibt; "$*"
erweitert sich zu einer leeren Zeichenfolge - und ja, es gibt einen Unterschied, obwohl es schwierig sein kann, ihn wahrzunehmen. Weitere Informationen finden Sie unten nach der Einführung des Befehls (nicht Standard) al
.
Sekundärthese: Wenn Sie Argumente mit Leerzeichen verarbeiten und dann an andere Befehle weitergeben müssen, benötigen Sie manchmal nicht standardmäßige Tools zur Unterstützung. (Oder Sie sollten Arrays vorsichtig verwenden: "${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 funktioniert das nicht? Es funktioniert nicht, weil die Shell Anführungszeichen verarbeitet, bevor sie Variablen erweitert. Um die Shell dazu zu bringen, auf die darin eingebetteten Anführungszeichen zu achten $var
, müssen Sie Folgendes verwenden 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
$
Dies wird sehr schwierig, wenn Sie Dateinamen wie " He said,
"Don't do this!"
" (mit Anführungszeichen und doppelten Anführungszeichen und Leerzeichen) haben.
$ 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 Dingen umzugehen, so dass (komischerweise) viele Unix-Programme nicht gut damit umgehen können. Unter Unix kann ein Dateiname (einzelne Komponente) beliebige Zeichen außer Schrägstrich und NUL enthalten '\0'
. Die Shells empfehlen jedoch nachdrücklich keine Leerzeichen, Zeilenumbrüche oder Tabulatoren in Pfadnamen. Aus diesem Grund enthalten Standard-Unix-Dateinamen keine Leerzeichen usw.
Wenn Sie mit Dateinamen arbeiten, die Leerzeichen und andere störende Zeichen enthalten können, müssen Sie äußerst vorsichtig sein, und ich habe vor langer Zeit festgestellt, dass ich ein Programm benötige, das unter Unix nicht Standard ist. Ich nenne es escape
(Version 1.1 war vom 1989-08-23T16: 01: 45Z datiert).
Hier ist ein Beispiel für die escape
Verwendung - mit dem SCCS-Steuerungssystem. Es ist ein Coverskript, das sowohl ein delta
(Think Check-In ) als auch ein
get
(Think Check-Out ) ausführt . Insbesondere verschiedene Argumente -y
(der Grund, warum Sie die Änderung vorgenommen haben) würden Leerzeichen und Zeilenumbrüche enthalten. Beachten Sie, dass das Skript aus dem Jahr 1992 stammt und daher Back-Ticks anstelle der
$(cmd ...)
Notation verwendet und nicht #!/bin/sh
in der ersten Zeile verwendet wird.
: "@(#)$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"
(Ich würde Escape heutzutage wahrscheinlich nicht mehr so gründlich verwenden - es wird -e
zum Beispiel für das Argument nicht benötigt -, aber insgesamt ist dies eines meiner einfacheren Skripte escape
.)
Das escape
Programm gibt einfach seine Argumente aus, ähnlich wie dies der echo
Fall ist, stellt jedoch sicher, dass die Argumente für die Verwendung mit geschützt sind
eval
(eine Ebene von eval
; Ich habe ein Programm, das eine Remote-Shell-Ausführung durchgeführt hat und das der Ausgabe von entkommen muss 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
, das seine Argumente eins pro Zeile auflistet (und es ist noch älter: Version 1.1 vom 1987-01-27T14: 35: 49). Dies ist am nützlichsten 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 zu zeigen , hier noch ein 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 durch nichts die ursprünglichen Leerzeichen zwischen *
und */*
in der Befehlszeile erhalten bleiben . Beachten Sie außerdem, dass Sie die 'Befehlszeilenargumente' in der Shell ändern können, indem Sie Folgendes verwenden:
set -- -new -opt and "arg with space"
Dies setzt 4 Optionen, ' -new
', ' -opt
', ' and
' und ' arg with space
'.
]]
Hmm, das ist eine ziemlich lange Antwort - vielleicht ist Exegese der bessere Begriff. Quellcode für escape
auf Anfrage erhältlich (E-Mail an Vorname und Nachname bei gmail dot com). Der Quellcode für al
ist unglaublich einfach:
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
Das ist alles. Es entspricht dem test.sh
Skript, das Robert Gamble gezeigt hat, und kann als Shell-Funktion geschrieben werden (aber Shell-Funktionen waren in der lokalen Version der Bourne-Shell beim ersten Schreiben nicht vorhanden al
).
Beachten Sie auch, dass Sie al
als einfaches Shell-Skript schreiben können :
[ $# != 0 ] && printf "%s\n" "$@"
Die Bedingung wird benötigt, damit sie keine Ausgabe erzeugt, wenn keine Argumente übergeben werden. Der printf
Befehl erzeugt eine leere Zeile nur mit dem Formatzeichenfolgenargument, aber das C-Programm erzeugt nichts.