Die allgemeine Regel in Shell-Skripten lautet, dass Variablen immer in Anführungszeichen gesetzt werden sollten, es sei denn, es gibt einen zwingenden Grund, dies nicht zu tun. Weitere Einzelheiten, als Sie wahrscheinlich wissen möchten, finden Sie in diesem großartigen Q & A: Security-Artikel .
Betrachten Sie jedoch eine Funktion wie die folgende:
run_this(){
$@
}
Sollte $@
dort zitiert werden oder nicht? Ich spielte ein bisschen damit und konnte keinen Fall finden, bei dem der Mangel an Zitaten ein Problem verursachte. Wenn Sie dagegen Anführungszeichen verwenden, wird der Befehl unterbrochen, wenn Sie Leerzeichen als Variable in Anführungszeichen übergeben:
#!/usr/bin/sh
set -x
run_this(){
$@
}
run_that(){
"$@"
}
comm="ls -l"
run_this "$comm"
run_that "$comm"
Das Ausführen des obigen Skripts gibt Folgendes zurück:
$ a.sh
+ comm='ls -l'
+ run_this 'ls -l'
+ ls -l
total 8
-rw-r--r-- 1 terdon users 0 Dec 22 12:58 da
-rw-r--r-- 1 terdon users 45 Dec 22 13:33 file
-rw-r--r-- 1 terdon users 43 Dec 22 12:38 file~
+ run_that 'ls -l'
+ 'ls -l'
/home/terdon/scripts/a.sh: line 7: ls -l: command not found
Ich kann das umgehen, wenn ich run_that $comm
stattdessen benutze run_that "$comm"
, aber da die run_this
(nicht zitierte) Funktion mit beiden funktioniert, scheint es die sicherere Wette zu sein.
Sollte also im konkreten Fall der Verwendung $@
in einer Funktion, deren Aufgabe es ist, $@
als Befehl auszuführen , $@
in Anführungszeichen gesetzt werden? Bitte erläutern Sie, warum es zitiert werden sollte / nicht und geben Sie ein Beispiel für Daten an, die es zerstören können.
quelle
run_that
Das Verhalten von ist definitiv das, was ich erwarten würde (was ist, wenn der Pfad zum Befehl ein Leerzeichen enthält?). Wenn Sie das andere Verhalten wollte, sicherlich würden Sie es am Ende des Zitats Anruf -Ort , wo Sie wissen , was die Daten? Ich würde erwarten, diese Funktion als zu bezeichnenrun_that ls -l
, die in beiden Versionen gleich funktioniert . Gibt es einen Fall, den Sie anders erwarten ließen?${mycmd[@]}
.Antworten:
Das Problem liegt darin, wie der Befehl an die Funktion übergeben wird:
"$@"
sollte in dem allgemeinen Fall verwendet werden, in dem Ihrerrun_this
Funktion ein normal geschriebener Befehl vorangestellt wird.run_this
führt zur Hölle zu zitieren:Ich bin mir nicht sicher, wie ich einen Dateinamen mit Leerzeichen übergeben soll
run_this
.quelle
run_this
.$@
nicht zitiert haben . Ich hätte ein Beispiel hinterlassen sollen. : Drun_this
. Dies ist im Grunde das gleiche Problem, mit dem Sie konfrontiert werden, wenn Sie komplexe Befehle in Zeichenfolgen einfügen, wie in Bash FAQ 050 beschrieben .Es ist entweder:
Oder:
oder:
Aber:
Macht nicht viel Sinn.
Wenn Sie den
ls -l
Befehl ausführen möchten (nicht denls
Befehl mitls
und-l
als Argumente), tun Sie Folgendes:Aber wenn es (wahrscheinlicher) der
ls
Befehl mitls
und-l
als Argument ist, würden Sie Folgendes ausführen:Wenn es sich um mehr als einen einfachen Befehl handelt, den Sie ausführen möchten, reicht es aus, wenn Sie Variablenzuweisungen, Umleitungen, Pipes ... ausführen möchten
interpret_this_shell_code
:Natürlich können Sie immer Folgendes tun:
quelle
Betrachtet man es aus der bash / KSH / zsh Perspektive,
$*
und$@
ist ein Spezialfall der allgemeinen Array - Erweiterung. Array-Erweiterungen sind nicht wie normale Variablenerweiterungen:Mit den
$*
/${a[*]}
-Erweiterungen wird das Array mit dem ersten WertIFS
(standardmäßig Leerzeichen) in einer riesigen Zeichenfolge verbunden. Wenn Sie es nicht zitieren, wird es wie eine normale Zeichenfolge aufgeteilt.Bei den
$@
/${a[@]}
-Erweiterungen hängt das Verhalten davon ab, ob die$@
/${a[@]}
-Erweiterung in Anführungszeichen steht oder nicht:"$@"
oder"${a[@]}"
) steht, erhalten Sie das Äquivalent von"$1" "$2" "$3" #...
oder"${a[1]}" "${a[2]}" "${a[3]}" # ...
$@
oder${a[@]}
) steht, erhalten Sie das Äquivalent von$1 $2 $3 #...
oder${a[1]} ${a[2]} ${a[3]} # ...
Zum Umbrechen von Befehlen möchten Sie auf jeden Fall die @ -Erweiterungen in Anführungszeichen (1.).
Weitere nützliche Informationen zu Bash-Arrays (und Bash-ähnlichen Arrays): https://lukeshu.com/blog/bash-arrays.html
quelle
Wenn Sie nicht doppelt zitieren
$@
, haben Sie alle wichtigen Punkte in dem Link belassen, den Sie zu Ihrer Funktion gegeben haben.Wie können Sie einen Befehl mit dem Namen ausführen
*
? Du kannst es nicht machen mitrun_this
:Und Sie sehen, auch wenn ein Fehler aufgetreten ist,
run_that
haben Sie eine aussagekräftigere Nachricht erhalten.Die einzige Möglichkeit,
$@
einzelne Wörter zu erweitern , besteht darin, sie in Anführungszeichen zu setzen. Wenn Sie es als Befehl ausführen möchten, müssen Sie den Befehl und die Parameter als getrennte Wörter übergeben. Dass das, was Sie auf der Anruferseite getan haben, nicht in Ihrer Funktion.ist eine bessere Wahl. Oder wenn Ihre Shell Arrays unterstützt:
Auch wenn die Shell Array überhaupt nicht unterstützt, können Sie mit
"$@"
Array spielen .quelle
Das Ausführen von Variablen in
bash
ist eine fehleranfällige Technik. Es ist einfach unmöglich, einerun_this
Funktion zu schreiben , die alle Kantenfälle korrekt behandelt, wie:ls | grep filename
)ls > /dev/null
)if
while
etc.Wenn Sie nur die Wiederholung von Code vermeiden möchten, sollten Sie die Funktionen besser nutzen. Zum Beispiel anstelle von:
Du solltest schreiben
Wenn die Befehle nur zur Laufzeit verfügbar sind, sollten Sie Folgendes verwenden
eval
, das speziell für die Behandlung aller Macken entwickelt wurde, die zumrun_this
Fehlschlagen führen:Beachten Sie, dass
eval
ist bekannt für Sicherheitsfragen, aber wenn Sie Variablen aus nicht vertrauenswürdigen Quellen zu passierenrun_this
, werden Sie genauso gut der Ausführung beliebigen Codes stellen.quelle
Es ist deine Entscheidung. Wenn Sie
$@
keinen seiner Werte angeben, werden diese zusätzlich erweitert und interpretiert. Wenn Sie es zitieren, werden alle übergebenen Argumente der Funktion in ihrer Erweiterung wörtlich wiedergegeben. Sie werden niemals in der Lage sein, zuverlässig mit Shell-Syntax-Token wie&>|
und usw. umzugehen, ohne die Argumente selbst zu analysieren - und so bleibt Ihnen die vernünftigere Wahl, Ihre Funktion einer der folgenden Möglichkeiten zu übergeben:"$@"
....oder...
$@
.Keiner der Wege ist falsch, wenn er absichtlich ist und die Auswirkungen Ihrer Wahl gut verstanden werden. Beide Möglichkeiten haben Vorteile gegenüber den anderen, obwohl die Vorteile der zweiten selten besonders nützlich sein dürften. Immer noch...
... es ist nicht nutzlos , nur selten von großem Nutzen . Und in einer
bash
Schale, weil diebash
Voreinstellung nicht hält eine Variablendefinition an seine Umgebung , auch wenn die Definition der Befehlszeile eines speziellen builtin oder zu einer Funktion vorangestellt wird, für den globalen Wert$IFS
ist nicht betroffen, und ihre Erklärung ist lokal nur zumrun_this()
anruf.Ähnlich:
... das Globbing ist auch konfigurierbar. Zitate dienen einem Zweck - sie sind nicht umsonst. Ohne sie wird die Shell-Erweiterung einer zusätzlichen Interpretation unterzogen - konfigurierbare Interpretation. Früher war es - mit einigen sehr alten Shells - das,
$IFS
was global auf alle Eingaben angewendet wurde , und nicht nur auf Erweiterungen. Tatsächlich verhielten sich die Shells sehr ähnlich dahingehend,run_this()
dass sie alle Eingabewörter auf den Wert von brachen$IFS
. Wenn Sie also nach diesem sehr alten Shell-Verhalten suchen, sollten Sie es verwendenrun_this()
.Ich suche nicht danach und bin im Moment ziemlich gedrängt, ein nützliches Beispiel dafür zu finden. Ich bevorzuge im Allgemeinen, dass die Befehle, die meine Shell ausführt, die sind, die ich tippe. Und wenn ich die Wahl hätte, würde ich es fast immer tun
run_that()
. Außer dass...Fast alles kann zitiert werden. Befehle werden in Anführungszeichen ausgeführt. Dies funktioniert, da zum Zeitpunkt der tatsächlichen Ausführung des Befehls alle Eingabewörter bereits in Anführungszeichen gesetzt wurden - dies ist die letzte Stufe des Eingabe-Interpretationsprozesses der Shell. So ist der Unterschied zwischen
'ls'
undls
kann nur Materie , während die Shell interpretiert - und deshalb zitiert wirdls
sichergestellt , dass Alias Namels
nicht für mein zitierte ersetzt wirdls
Befehlswort. Ansonsten wirken sich die Anführungszeichen nur auf die Abgrenzung von Wörtern aus (das ist, wie und warum das Zitieren von Variablen / Eingabe-Leerzeichen funktioniert) sowie auf die Interpretation von Metazeichen und reservierten Wörtern.So:
Sie werden das niemals mit
run_this()
oder tun könnenrun_that()
.Aber Funktionsnamen,
$PATH
'd-Befehle oder integrierte Befehle werden in Anführungszeichen oder ohne Anführungszeichen ausgeführt, und genau sorun_this()
und überhaupt nichtrun_that()
. Sie werden mit all diesen Dingen nichts Nützliches$<>|&(){}
anfangen können. Kurzeval
ist.Andernfalls sind Sie aufgrund der von Ihnen verwendeten Anführungszeichen an die Grenzen eines einfachen Befehls gebunden (auch wenn dies nicht der
$@
Fall ist, da der Befehl wie ein Anführungszeichen zu Beginn des Prozesses behandelt wird, wenn er nach Metazeichen analysiert wird) . Dieselbe Einschränkung gilt für Befehlszeilenzuweisungen und -umleitungen, die auf die Befehlszeile der Funktion beschränkt sind. Aber das ist keine große Sache:Ich hätte dort genauso leicht
<
Ein- oder>
Ausgänge umleiten können, wie ich die Pipe geöffnet habe.Auf jeden Fall gibt es hier keinen richtigen oder falschen Weg - jeder Weg hat seinen Nutzen. Sie sollten es nur so schreiben, wie Sie es verwenden möchten, und Sie sollten wissen, was Sie vorhaben. Das Weglassen von Zitaten kann einen Zweck haben - sonst gäbe es keinen sein , überhaupt zitiert - aber wenn man sie weglassen Gründen nicht relevant für Ihre Zwecke, sind Sie nur schlechten Code zu schreiben. Tu was du meinst; Ich versuche es trotzdem.
quelle