$ 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
Kommentare
Antwort
Dies wurde in einer Reihe von Fragen zu unix.SE besprochen. Ich werde versuchen, alle zu sammeln Probleme, die ich hier finden kann. Referenzen am Ende.
Warum es fehlschlägt
Der Grund, warum Sie mit diesen Problemen konfrontiert sind, ist Wortteilung und die Tatsache, dass aus Variablen erweiterte Anführungszeichen nicht als Anführungszeichen dienen, sondern nur gewöhnliche Zeichen sind.
Die Fälle in der Frage dargestellt:
Die Zuweisung hier weist die einzelne Zeichenfolge bis abc
:
$ abc="ls -l "/tmp/test/my dir""
Unten $abc
wird in Leerzeichen aufgeteilt und ls
erhält die drei Argumente -l
, "/tmp/test/my
und dir"
(mit einem Zitat vorne im zweiten und einem Zitat hinten im dritten). Die Option funktioniert, aber der Pfad wird falsch verarbeitet:
$ $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 es um ein Programm zu finden, das buchstäblich ls -l "/tmp/test/my dir"
heißt, einschließlich Leerzeichen und Anführungszeichen.
$ "$abc" bash: ls -l "/tmp/test/my dir": No such file or directory
Und hier $abc
wird geteilt, und nur das erste resultierende Wort wird als Argument für -c
verwendet, sodass Bash nur ls
im aktuellen Verzeichnis. Die anderen Wörter sind Argumente für bash und werden verwendet, um $0
, $1
usw. zu füllen.
$ bash -c $abc "my dir"
Mit bash -c "$abc"
und eval "$abc"
gibt es eine zusätzliche Shell-Verarbeitungsschritt, bei dem die Anführungszeichen zwar funktionieren, aber auch alle Shell-Erweiterungen erneut verarbeitet werden , sodass „die Gefahr besteht, dass versehentlich eine Befehlsersetzung aus vom Benutzer bereitgestellten Daten ausgeführt wird, es sei denn, Sie“. Ich bin sehr besorgt
Bessere Möglichkeiten, dies zu tun
Die beiden besseren Möglichkeiten zum Speichern eines Befehls sind: a) Verwenden Sie stattdessen eine Funktion, b) Verwenden Sie eine Array-Variable ( oder die Positionsparameter).
Verwenden einer Funktion:
Einfach deklarieren eine Funktion mit dem Befehl darin, 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 die einzelnen Befehle nicht zitieren müssen.
# 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 Weiß enthalten Raum. Hier werden die einzelnen Wörter als unterschiedliche Array-Elemente gespeichert, und die Erweiterung "${array[@]}"
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. Beispiel:
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, um nur einen Teil davon zu füllen, z. B. 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 keine Standardfunktion sind, also einfache POSIX-Shells (wie dash
, die Standardeinstellung /bin/sh
in Debian / Ubuntu) unterstützt sie nicht (siehe unten). Bash, ksh und zsh tun dies jedoch, sodass Ihr System wahrscheinlich über eine Shell verfügt, die Arrays unterstützt.
Verwenden von "$@"
In Shells ohne Unterstützung für benannte Arrays können weiterhin die Positionsparameter verwendet werden (das Pseudo-Array "$@"
), um die Argumente eines Befehls zu speichern.
Das Folgende sollten tragbare Skriptbits sein, die den Codebits im vorherigen Abschnitt entsprechen. Das Array wird durch "$@"
, die Liste der Positionsparameter. Das Festlegen von "$@"
erfolgt mit set
und den doppelten Anführungszeichen um "$@"
sind wichtig (dies führt dazu, dass die Elemente der Liste einzeln in Anführungszeichen gesetzt werden).
Speichern Sie zunächst einfach einen Befehl mit Argumenten in "$@"
und führen Sie ihn aus:
set -- ls -l "/tmp/test/my dir" "$@"
Teile der Befehlszeilenoptionen für einen Befehl bedingt festlegen:
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 ist "$@"
normalerweise mit den Argumenten für das Skript selbst gefüllt, also Sie “ Sie müssen sie irgendwo speichern, bevor Sie "$@"
neu verwenden.)
Seien Sie vorsichtig mit eval
!
Da eval
eine zusätzliche Ebene der Angebots- und Erweiterungsverarbeitung einführt, müssen Sie mit Benutzereingaben vorsichtig sein. Dies funktioniert beispielsweise, solange der Benutzer dies tut Geben Sie keine einfachen Anführungszeichen ein:
read -r filename cmd="ls -l "$filename"" eval "$cmd";
Wenn sie jedoch die Eingabe "$(uname)".txt
geben, ist Ihr Skript zufrieden führt die Befehlssubstitution aus.
Eine Version mit Arrays ist gegen th immun Da die Wörter für die gesamte Zeit getrennt gehalten werden, gibt es kein Zitat oder eine andere Verarbeitung für den Inhalt von filename
.
read -r filename cmd=(ls -ld -- "$filename") "${cmd[@]}"
Referenzen
- Wortaufteilung in BashGuide
- BashFAQ / 050 oder “ Ich versuche, einen Befehl zu setzen in einer Variablen, aber die komplexen Fälle schlagen immer fehl! “
- Die Frage Warum? Mein Shell-Skript verschluckt sich an Leerzeichen oder anderen Sonderzeichen? , in dem eine Reihe von Problemen im Zusammenhang mit Anführungszeichen und Leerzeichen behandelt werden, einschließlich des Speicherns von Befehlen.
Kommentare
- Sie können das eval-Zitat umgehen, indem Sie
cmd="ls -l $(printf "%q" "$filename")"
ausführen. nicht schön, aber wenn der Benutzer fest entschlossen ist, eineeval
zu verwenden, hilft es. Es ist ‚ auch sehr nützlich, um den Befehl durch ähnliche Dinge wiessh foohost "ls -l $(printf "%q" "$filename")"
oder im Sprit dieser Frage zu senden:ssh foohost "$cmd"
. - Nicht direkt verwandt, aber haben Sie das Verzeichnis fest codiert? In diesem Fall möchten Sie möglicherweise den Alias anzeigen. So etwas wie:
$ alias abc='ls -l "/tmp/test/my dir"'
Antwort
Der sicherste Weg zu Führen Sie einen (nicht trivialen) Befehl aus: eval
. Dann können Sie den Befehl wie in der Befehlszeile schreiben und er wird genau so ausgeführt, als hätten Sie ihn gerade eingegeben. Aber Sie müssen alles zitieren.
Einfacher Fall:
abc="ls -l "/tmp/test/my dir"" eval "$abc"
nicht so einfacher Fall:
# command: awk "! a[$0]++ { print "foo: " $0; }" inputfile abc="awk "\""! a[$0]++ { print "foo: " $0; }"\"" inputfile" eval "$abc"
Kommentare
- Hallo @HaukeLaging, was ist \ ‚ ‚?
- @jumping_monkey Sie können keine
'
in einer'
-Zitierten Zeichenfolge haben . Sie müssen also (1) das Zitat beenden / unterbrechen, (2) dem nächsten'
entkommen, um es wörtlich zu erhalten, (3) das wörtliche Zitat eingeben und (4) eingeben Im Falle einer Unterbrechung setzen Sie die angegebene Zeichenfolge fort:(1)'(2)\(3)'(4)'
- Hallo @HaukeLaging, interessant, und warum nicht einfach
abc='awk \'! a[$0]++ { print "foo: " $0; }\' inputfile'
? - @jumping_monkey Ganz zu schweigen davon, dass ich Ihnen gerade erklärt habe, warum das nicht funktioniert: Wäre es ‚ nicht sinnvoll, Code vor dem Posten zu testen?
Antwort
Das zweite Anführungszeichen unterbricht den Befehl.
Wenn ich Folgendes ausführe:
abc="ls -l "/home/wattana/Desktop"" $abc
Es gab einen Fehler.
Aber wenn ich
abc="ls -l /home/wattana/Desktop" $abc
ausführe
Es gibt überhaupt keinen Fehler
Es gibt derzeit keine Möglichkeit, dies zu beheben (für mich), aber Sie können den Fehler vermeiden, indem Sie keinen Platz im Verzeichnisnamen haben.
Diese Antwort besagt, dass der Befehl eval verwendet werden kann, um dies zu beheben, aber bei mir nicht funktioniert 🙁
Kommentare
- Ja, das funktioniert, solange ‚ z. Dateinamen mit eingebetteten Leerzeichen (oder solchen mit Glob-Zeichen).
Antwort
Wenn dies mit ""
, dann sollten Sie ``
verwenden:
abc=`ls -l /tmp/test/my\ dir/`
Besser aktualisieren Weg:
abc=$(ls -l /tmp/test/my\ dir/)
Kommentare
- Hiermit wird das Ergebnis des Befehls in einer Variablen gespeichert. Das OP möchte (seltsamerweise) den Befehl selbst in einer Variablen speichern. Oh, und Sie sollten wirklich anfangen,
$( command... )
anstelle von Backticks zu verwenden. - Vielen Dank für die Erläuterungen und Tipps!
Antwort
Wie wäre es mit einem Python3-Einzeiler?
bash-4.4# pwd /home bash-4.4# CUSTOM="ls -altr" bash-4.4# python3 -c "import subprocess; subprocess.call(\"$CUSTOM\", shell=True)" total 8 drwxr-xr-x 2 root root 4096 Mar 4 2019 . drwxr-xr-x 1 root root 4096 Nov 27 16:42 ..
Kommentare
- Die Frage bezieht sich auf BASH, nicht auf Python.
Antwort
Ein weiterer einfacher Trick, um einen in abc
Variable ist:
$ history -s $abc
und drücken Sie UpArrow
oder Ctrl-p
, um es in die Befehlszeile zu bringen. Im Gegensatz zu jeder anderen Methode können Sie sie auf diese Weise bei Bedarf vor der Ausführung bearbeiten.
Mit diesem Befehl wird der Variableninhalt als neuer Eintrag in den Bash-Verlauf angehängt und Sie können ihn über UpArrow
.