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:
$ abc='ls -l "/tmp/test/my dir"'
Hier $abc
wird aufgeteilt und ls
erhält die beiden Argumente "/tmp/test/my
und dir"
(mit den Anführungszeichen vor dem ersten und hinter dem zweiten):
$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory
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.
$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory
Und hier wird nur das erste Wort oder $abc
als Argument verwendet -c
, sodass Bash nur ls
im aktuellen Verzeichnis ausgeführt wird. Die anderen Worten sind Argumente zu bash, und werden verwendet , um zu füllen $0
, $1
usw.
$ bash -c $abc
'my dir'
Mit bash -c "$abc"
und eval "$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.
# define it
myls() {
ls -l "/tmp/test/my dir"
}
# run it
myls
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:
# define the array
mycmd=(ls -l "/tmp/test/my dir")
# run the command
"${mycmd[@]}"
Die Syntax ist etwas schrecklich, aber mit Arrays können Sie die Befehlszeile auch Stück für Stück erstellen. Beispielsweise:
mycmd=(ls) # initial command
if [ "$want_detail" = 1 ]; then
mycmd+=(-l) # optional flag
fi
mycmd+=("$targetdir") # the filename
"${mycmd[@]}"
Oder halten Sie Teile der Befehlszeile konstant und verwenden Sie das Array, füllen Sie nur einen Teil davon, Optionen oder Dateinamen:
options=(-x -v)
files=(file1 "file name with whitespace")
target=/somedir
transmutate "${options[@]}" "${files[@]}" "$target"
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 mit set
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:
set -- ls -l "/tmp/test/my dir"
"$@"
Bedingtes Festlegen von Teilen der Befehlszeilenoptionen für einen Befehl:
set -- ls
if [ "$want_detail" = 1 ]; then
set -- "$@" -l
fi
set -- "$@" "$targetdir"
"$@"
Nur "$@"
für Optionen und Operanden verwenden:
set -- -x -v
set -- "$@" file1 "file name with whitespace"
set -- "$@" /somedir
transmutate "$@"
(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:
read -r filename
cmd="ls -l '$filename'"
eval "$cmd";
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
.
read -r filename
cmd=(ls -ld -- "$filename")
"${cmd[@]}"
Verweise