Wie können wir einen in einer Variablen gespeicherten Befehl ausführen?

$ 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

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, eine eval zu verwenden, hilft es. Es ist ‚ auch sehr nützlich, um den Befehl durch ähnliche Dinge wie ssh 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.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.