$ ls -l /tmp/test/my\ dir/
total 0
Ich habe mich gefragt, warum die folgenden Methoden zum Ausführen des obigen Befehls fehlschlagen oder erfolgreich sind.
$ abc='ls -l "/tmp/test/my dir"'
$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory
$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory
$ bash -c $abc
'my dir'
$ bash -c "$abc"
total 0
$ eval $abc
total 0
$ eval "$abc"
total 0
Antworten:
Dies wurde in einer Reihe von Fragen zu Unix besprochen. In diesem Artikel versuche ich, alle Probleme zu sammeln, die mir einfallen. Referenzen am Ende.
Warum es fehlschlägt
Der Grund für diese Probleme ist die Wortteilung und die Tatsache, dass aus Variablen erweiterte Anführungszeichen keine Anführungszeichen sind, sondern nur gewöhnliche Zeichen.
Die Fälle in der Frage vorgestellt:
Hier
$abc
wird aufgeteilt undls
erhält die beiden Argumente"/tmp/test/my
unddir"
(mit den Anführungszeichen vor dem ersten und hinter dem zweiten):Hier wird die Erweiterung in Anführungszeichen gesetzt, sodass sie als einzelnes Wort beibehalten wird. Die Shell versucht, ein aufgerufenes Programm mit
ls -l "/tmp/test/my dir"
Leerzeichen und Anführungszeichen zu finden.Und hier wird nur das erste Wort oder
$abc
als Argument verwendet-c
, sodass Bash nurls
im aktuellen Verzeichnis ausgeführt wird. Die anderen Worten sind Argumente zu bash, und werden verwendet , um zu füllen$0
,$1
usw.Mit
bash -c "$abc"
undeval "$abc"
gibt es einen zusätzlichen Shell-Verarbeitungsschritt, der die Anführungszeichen funktionsfähig macht, aber auch alle Shell-Erweiterungen erneut verarbeitet , sodass die Gefahr besteht, dass versehentlich eine Befehlserweiterung aus vom Benutzer bereitgestellten Daten ausgeführt wird, es sei denn, Sie sind sehr Vorsicht beim Zitieren.Bessere Möglichkeiten
Die zwei besseren Möglichkeiten zum Speichern eines Befehls sind: a) Verwenden Sie stattdessen eine Funktion, b) Verwenden Sie eine Array-Variable (oder die Positionsparameter).
Verwendung einer Funktion:
Deklarieren Sie einfach eine Funktion mit dem darin enthaltenen Befehl und führen Sie die Funktion aus, als wäre es ein Befehl. Erweiterungen in Befehlen innerhalb der Funktion werden nur verarbeitet, wenn der Befehl ausgeführt wird, nicht, wenn er definiert ist, und Sie müssen die einzelnen Befehle nicht in Anführungszeichen setzen.
Verwenden eines Arrays:
Mit Arrays können Variablen mit mehreren Wörtern erstellt werden, bei denen die einzelnen Wörter Leerzeichen enthalten. Hier werden die einzelnen Wörter als unterschiedliche Array-Elemente gespeichert, und die
"${array[@]}"
Erweiterung erweitert jedes Element als separate Shell-Wörter:Die Syntax ist etwas schrecklich, aber mit Arrays können Sie die Befehlszeile auch Stück für Stück erstellen. Beispielsweise:
Oder halten Sie Teile der Befehlszeile konstant und verwenden Sie das Array, füllen Sie nur einen Teil davon, Optionen oder Dateinamen:
Der Nachteil von Arrays ist, dass sie kein Standardfeature sind, so dass einfache POSIX-Shells (wie
dash
der Standard/bin/sh
in Debian / Ubuntu) sie nicht unterstützen (siehe aber unten). Bash, ksh und zsh tun dies jedoch, sodass Ihr System wahrscheinlich über eine Shell verfügt, die Arrays unterstützt.Verwenden
"$@"
In Shells ohne Unterstützung für benannte Arrays können die Positionsparameter (das Pseudo-Array
"$@"
) weiterhin zum Speichern der Argumente eines Befehls verwendet werden.Bei den folgenden sollte es sich um portable Skriptbits handeln, die den Codebits im vorherigen Abschnitt entsprechen. Das Array wird durch
"$@"
die Liste der Positionsparameter ersetzt. Die Einstellung"$@"
erfolgt mitset
und die doppelten Anführungszeichen"$@"
sind wichtig (diese bewirken, dass die Elemente der Liste einzeln in Anführungszeichen gesetzt werden).Speichern Sie zunächst einfach einen Befehl mit Argumenten
"$@"
und führen Sie ihn aus:Bedingtes Festlegen von Teilen der Befehlszeilenoptionen für einen Befehl:
Nur
"$@"
für Optionen und Operanden verwenden:(Natürlich sind
"$@"
in der Regel die Argumente für das Skript selbst enthalten, daher müssen Sie sie irgendwo speichern, bevor Sie sie erneut verwenden können"$@"
.)Sei vorsichtig mit
eval
!Da
eval
eine zusätzliche Ebene der Angebots- und Erweiterungsverarbeitung eingeführt wird, müssen Sie mit Benutzereingaben vorsichtig sein. Dies funktioniert beispielsweise, solange der Benutzer keine einfachen Anführungszeichen eingibt:Wenn sie jedoch die Eingabe geben
'$(uname)'.txt
, führt Ihr Skript die Befehlsersetzung problemlos aus.Eine Version mit Arrays ist dagegen immun, da die Wörter für die gesamte Zeit getrennt bleiben und für den Inhalt von kein Zitat oder eine andere Verarbeitung erforderlich ist
filename
.Verweise
quelle
cmd="ls -l $(printf "%q" "$filename")"
. nicht hübsch, aber wenn der Benutzer nicht mehr mit einem zufrieden isteval
, hilft es. Es ist auch sehr nützlich für den Befehl , obwohl ähnliche Dinge zu senden, wiessh foohost "ls -l $(printf "%q" "$filename")"
oder in dem sprit dieser Frage:ssh foohost "$cmd"
.$ alias abc='ls -l "/tmp/test/my dir"'
Der sicherste Weg, einen (nicht trivialen) Befehl auszuführen, ist
eval
. Dann können Sie den Befehl so schreiben, wie Sie es in der Befehlszeile tun würden, und er wird genau so ausgeführt, als ob Sie ihn gerade eingegeben hätten. Aber du musst alles zitieren.Einfacher Fall:
nicht so einfacher Fall:
quelle
Das zweite Anführungszeichen unterbricht den Befehl.
Wenn ich renne:
Es gab mir einen Fehler.
Aber wenn ich renne
Es liegt überhaupt kein Fehler vor
Zur Zeit gibt es keine Möglichkeit, dies zu beheben (für mich), aber Sie können den Fehler vermeiden, indem Sie im Verzeichnisnamen kein Leerzeichen verwenden.
Diese Antwort besagt, dass der eval-Befehl verwendet werden kann, um dies zu beheben, aber er funktioniert bei mir nicht :(
quelle
Wenn dies nicht funktioniert
''
, sollten Sie verwenden``
:Besser aktualisieren:
quelle
$( command... )
Backticks zu verwenden.Wie wäre es mit einem Python3-Einzeiler?
quelle