Führen Sie einen in einer Variablen gespeicherten Befehl aus


11

Ich habe einen Befehl in einer Variablen gespeichert. Stellen wir uns vor, die Variable $ihat den Wert:

cat -nT index.php |grep 'someregex'

Wenn ich versuche, die obige Variable durch Eingabe auszuführen $i, schlägt dies fehl, da die Shell versucht, die gesamte Variable als einen Befehl auszuführen. Ich habe auch versucht eval($i), $iBackticks zu verwenden und einzufügen.

Wie kann ich die Shell so ausführen lassen, $ials wäre es ein Befehl? Und warum funktioniert es nicht genauso wie.

$i='echo hi'; $i

Liegt es daran, dass ich die einzelnen Anführungszeichen irgendwie hacken musste? (Weil du sie nicht verschachteln kannst.) Derzeit ist meine Lösung

echo $i > /foo;  . /foo

Aber ich möchte nicht nur dafür eine Datei erstellen, sondern sie später löschen.

Was ich mit "Ich habe in den einfachen Anführungszeichen gehackt" meine, habe ich getan:

$i='cat index.php | grep -P '"'"'MYREGEXHERE'"'"

4
Die Frage ist, warum der Befehl überhaupt in einer Variablen gespeichert wird. Sie sollten den Befehl besser zusammenstellen, wenn Sie ihn ausführen und die Optionsargumente in Variablen oder in einem Array speichern. Können Sie das gesamte Skript anzeigen, das Sie haben?
Slhck

Antworten:


18

Kurze Antwort: siehe BashAQ # 50: Ich versuche, einen Befehl in eine Variable einzufügen, aber die komplexen Fälle schlagen immer fehl! .

Lange Antwort: Es liegt an der Reihenfolge, in der Bash die Befehlszeile analysiert. Insbesondere sucht es nach Dingen wie Pipes und Weiterleitungen, bevor es variable Werte erweitert, und geht nicht zurück und sucht in den erweiterten Werten erneut nach Pipes usw. Im Wesentlichen werden Variablen etwa zur Hälfte des Analyseprozesses ersetzt, sodass der Wert vor der Ausführung nur zur Hälfte analysiert wird.

Um dies zu lösen, müssen Sie die Frage von @ slhck beantworten: Warum wird der Befehl überhaupt in einer Variablen gespeichert? Was ist das eigentliche Problem, das Sie lösen möchten? Abhängig vom tatsächlichen Ziel gibt es eine Reihe möglicher Lösungen:

  • Speichern Sie es nicht in einer Variablen, sondern führen Sie es direkt aus. Das Speichern von Befehlen für die spätere Verwendung ist schwierig, und wenn Sie dies nicht wirklich benötigen, tun Sie es einfach nicht.

  • Verwenden Sie eine Funktion anstelle einer Variablen. Dafür sind sie schließlich da:

    i() { cat -nT index.php |grep 'someregex'; }
    i

    Der Hauptnachteil davon ist, dass Sie die Funktion nicht dynamisch erstellen können - Sie können Code nicht bedingt ein- oder ausschließen (obwohl die Funktion selbst bedingte Elemente enthalten kann, die bei der Ausführung ausgewählt werden).

  • Verwenden Sie eval. Dies sollte als letzter Ausweg betrachtet werden, da es leicht zu unerwartetem Verhalten kommt. Der Befehl wird im Wesentlichen durch einen weiteren vollständigen Parsing-Durchlauf ausgeführt, sodass alle Pipes usw. ihre volle Bedeutung erhalten. Dies bedeutet jedoch auch, dass Teile des Befehls, von denen Sie dachten, dass es sich nur um Daten handelt, ebenfalls analysiert und möglicherweise ausgeführt werden. Dateinamen, die Shell-Metazeichen (Pipe, Semikolon, Anführungszeichen / Apostroph usw.) enthalten, können seltsame und manchmal gefährliche Auswirkungen haben. Wenn Sie evaldie Zeichenfolge verwenden, zitieren Sie sie mindestens doppelt, andernfalls wird ihr Inhalt im Wesentlichen eineinhalb Mal analysiert, mit noch seltsameren Ergebnissen.

    i="cat -nT index.php |grep 'someregex'"
    eval "$i"

BEARBEITEN: Ein anderer Standardansatz zum Speichern eines Befehls in einer Variablen besteht darin, ein Array anstelle einer einfachen Variablen zu verwenden. Auf diese Weise können Sie einen Befehl mit komplexen Argumenten (z. B. Leerzeichen) problemlos speichern, jedoch keine Elemente wie Pipes und Weiterleitungen. Daher ist der Array-Ansatz in diesem speziellen Fall nicht hilfreich.


+1 für die evalMethode. Dies hat mich davon abgehalten, die gleiche Frage zu stellen :). Ich habe so etwas sudo rsync -aAHXi -n --delete-excluded --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/lost+found","/mnt/DATA/*","/var/log/*","/var/swap","/var/cache/apt/archives/*.deb"} -e ssh root@piac_wireless:/ /home/mrx/Docs/RPi/backup/piac/piac_usb-rootnicht richtig die Ausschlüsse ausgeführt.
Dentex

@dentex Ich würde dringend davon abraten, evales zu verwenden - es gibt zu viele Möglichkeiten, um etwas falsch zu machen. Ein Array wäre ein besserer Weg, dies zu tun. Beispiele finden Sie hier , hier und hier .
Gordon Davisson

Danke für den Vorschlag. Ich werde versuchen, die Lösung mit dem Array zu implementieren, um die Ausschlussliste an rsync zu übergeben.
Dentex

4

Das sollte einfach funktionieren:

a="cat -nT index.php |grep 'someregex'"
eval "$a"

Beachten Sie, dass dies evalpotenzielle Sicherheitslücken und unerwartete Verhaltenssituationen mit sich bringt und daher, wenn überhaupt, mit besonderer Sorgfalt verwendet werden muss.


Wenn Sie verwenden eval, müssen Sie wirklich doppelte Anführungszeichen ( eval "$i") verwenden, um besonders seltsame Effekte zu vermeiden. Versuchen Sie dies beispielsweise mit a="cat -nT index.php |grep ' .* '"(dh der reguläre Ausdruck sollte zwei Leerzeichen mit einer beliebigen Zeichenfolge zwischen ihnen übereinstimmen) und sehen Sie, was tatsächlich passiert. Erklären Sie für zusätzliche Gutschriften, warum dies geschieht.
Gordon Davisson

@GordonDavisson doppelte Anführungszeichen hinzugefügt.
Jlliagre

2

Wenn Sie die Variable deklarieren, sollten Sie das Dollarzeichen nicht als Präfix verwenden.

Versuche dies:

i='echo hi'; $i

1
Das ist richtig, aber es ist wirklich keine Antwort auf die Frage des OP.
Slhck

Es ist für mich WIRKLICH: P
nwgat
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.