Nach dem Lesen der Bash-Manpages und in Bezug auf diesen Beitrag .
Ich habe immer noch Probleme zu verstehen, was genau der eval
Befehl tut und welche typischen Verwendungen dies sind. Zum Beispiel, wenn wir Folgendes tun:
bash$ set -- one two three # sets $1 $2 $3
bash$ echo $1
one
bash$ n=1
bash$ echo ${$n} ## First attempt to echo $1 using brackets fails
bash: ${$n}: bad substitution
bash$ echo $($n) ## Second attempt to echo $1 using parentheses fails
bash: 1: command not found
bash$ eval echo \${$n} ## Third attempt to echo $1 using 'eval' succeeds
one
Was genau passiert hier und wie hängen das Dollarzeichen und der Backslash mit dem Problem zusammen?
$($n)
läuft$n
in einer Unterschale. Es wird versucht, den1
nicht vorhandenen Befehl auszuführen .echo $1
irgendwann zu laufen , nicht1
. Ich glaube nicht, dass dies mit Subshells möglich ist.eval
bewusst sein .Antworten:
eval
Nimmt eine Zeichenfolge als Argument und wertet sie aus, als hätten Sie diese Zeichenfolge in einer Befehlszeile eingegeben. (Wenn Sie mehrere Argumente übergeben, werden diese zuerst mit Leerzeichen zwischen ihnen verbunden.)${$n}
ist ein Syntaxfehler in bash. Innerhalb der geschweiften Klammern können Sie nur einen Variablennamen mit einigen möglichen Präfixen und Suffixen haben, aber Sie können keine willkürliche Bash-Syntax haben und insbesondere können Sie keine Variablenerweiterung verwenden. Es gibt jedoch eine Möglichkeit zu sagen: "Der Wert der Variablen, deren Name in dieser Variablen enthalten ist:"$(…)
führt den in den Klammern angegebenen Befehl in einer Subshell aus (dh in einem separaten Prozess, der alle Einstellungen wie Variablenwerte von der aktuellen Shell erbt) und sammelt seine Ausgabe. Soecho $($n)
läuft$n
als Shell - Befehl und zeigt seinen Ausgang. Da wird$n
ausgewertet1
,$($n)
versucht, den Befehl auszuführen1
, der nicht existiert.eval echo \${$n}
führt die an übergebenen Parameter auseval
. Nach der Erweiterung sind die Parameterecho
und${1}
. Alsoeval echo \${$n}
läuft der Befehlecho ${1}
.Beachten Sie, dass Sie die meiste Zeit doppelte Anführungszeichen für Variablensubstitutionen und Befehlssubstitutionen verwenden müssen (dh immer dann, wenn es eine gibt
$
) :"$foo", "$(foo)"
. Setzen Sie Variablen- und Befehlsersetzungen immer in doppelte Anführungszeichen , es sei denn, Sie wissen, dass Sie sie weglassen müssen. Ohne doppelte Anführungszeichen führt die Shell eine Feldaufteilung durch (dh sie teilt den Wert der Variablen oder die Ausgabe des Befehls in separate Wörter auf) und behandelt dann jedes Wort als Platzhaltermuster. Beispielsweise:eval
wird nicht sehr oft verwendet. In einigen Shells wird am häufigsten der Wert einer Variablen ermittelt, deren Name erst zur Laufzeit bekannt ist. In Bash ist dies dank der nicht notwendig${!VAR}
Syntax .eval
ist immer noch nützlich, wenn Sie einen längeren Befehl erstellen müssen, der Operatoren, reservierte Wörter usw. enthält.quelle
eval
empfängt eine Zeichenfolge (die selbst das Ergebnis von Analyse und Auswertung sein kann) und interpretiert sie als Code-Snippet.eval eval
. Ich kann mich nicht erinnern, jemals das Bedürfnis gespürt zu haben. Wenn Sie wirklich zweieval
Durchgänge benötigen , verwenden Sie eine temporäre Variable. Das Debuggen ist einfacher :eval tmp="\${$i}"; eval x="\${$tmp}"
.eval
Nimmt einfach Code in eine Zeichenfolge und wertet ihn gemäß den üblichen Regeln aus. Technisch gesehen ist es nicht einmal richtig, da es einige Eckfälle gibt, in denen Bash das Parsen so modifiziert, dass die Argumente von eval nicht einmal erweitert werden - aber das ist ein sehr dunkler Leckerbissen, von dem ich bezweifle, dass irgendjemand etwas weiß.Stellen Sie sich eval einfach als "Bewertung Ihres Ausdrucks ein weiteres Mal vor der Ausführung" vor.
eval echo \${$n}
wirdecho $1
nach der ersten Bewertungsrunde. Drei Änderungen zu beachten:\$
wurde$
(Der Backslash wird benötigt, andernfalls versucht es auszuwerten${$n}
, was bedeutet, dass eine Variable namens benannt ist{$n}
, was nicht erlaubt ist)$n
wurde ausgewertet1
eval
verschwandenIn der zweiten Runde kann es grundsätzlich
echo $1
direkt ausgeführt werden.Also
eval <some command>
wird zuerst ausgewertet<some command>
(mit hier meine ich Ersatzvariablen meinen, maskierte Zeichen durch die richtigen ersetzen usw.) und dann den resultierenden Ausdruck erneut ausführen.eval
wird verwendet, wenn Sie dynamisch Variablen erstellen oder Ausgaben von Programmen lesen möchten, die speziell für das Lesen auf diese Weise entwickelt wurden. Beispiele finden Sie unter http://mywiki.wooledge.org/BashFAQ/048 . Der Link enthält auch einige typischeeval
Verwendungsmöglichkeiten und die damit verbundenen Risiken.quelle
${VAR}
Syntax ist erlaubt, und bevorzugt , wenn es eine Zweideutigkeit ist (nicht$VAR == $V
, gefolgt vonAR
oder$VAR == $VA
gefolgt vonR
).${VAR}
ist äquivalent zu$VAR
. Tatsächlich ist der Variablenname$n
nicht zulässig.eval eval echo \\\${\${$i}}
wird eine dreifache Dereferenzierung machen. Ich bin mir nicht sicher, ob es einen einfacheren Weg gibt, das zu tun. Funktioniert auch\${$n}
einwandfrei (Druckeone
) auf meiner Maschine.echo \\\${\${$i}}
druckt\${${n}}
.eval echo \\\${\${$i}}
entsprichtecho \${${n}}`` and prints
$ {1}.
eval eval echo \\\ $ {\ $ {$ i}} `entsprichteval echo ${1}
und drucktone
.` escapes the second one, and the third
`entgeht dem$
danach. So wird es\${${n}}
nach einer Bewertungsrunde\\
einen Backslash ergeben. Dann\$
ergibt sich ein Dollar. Und so weiter.Nach meiner Erfahrung besteht eine "typische" Verwendung von eval darin, Befehle auszuführen, die Shell-Befehle zum Festlegen von Umgebungsvariablen generieren.
Vielleicht haben Sie ein System, das eine Sammlung von Umgebungsvariablen verwendet, und Sie haben ein Skript oder Programm, das festlegt, welche festgelegt werden sollen und welche Werte sie haben. Wenn Sie ein Skript oder Programm ausführen, wird es in einem gegabelten Prozess ausgeführt, sodass alles, was es direkt mit Umgebungsvariablen tut, beim Beenden verloren geht. Dieses Skript oder Programm kann jedoch die Exportbefehle an stdout senden.
Ohne eval müssten Sie stdout in eine temporäre Datei umleiten, die temporäre Datei als Quelle bereitstellen und dann löschen. Mit eval können Sie einfach:
Beachten Sie, dass die Anführungszeichen wichtig sind. Nehmen Sie dieses (erfundene) Beispiel:
quelle
${varname}
. Ich finde es bequem, identische .conf-Dateien auf mehreren verschiedenen Servern zu verwenden, wobei nur wenige Dinge durch Umgebungsvariablen parametrisiert werden. Ich habe / opt / lampp / xampp (das Apache startet) bearbeitet, um diese Art der Auswertung mit einem Skript durchzuführen, das das Systemexport
durchsucht und Bash- Anweisungen ausgibt , um Variablen für die .conf-Dateien zu definieren.eval "$(pyenv init -)"
in Ihre.bash_profile
(oder eine ähnliche) Shell-Konfigurationsdatei ein. Das erstellt ein kleines Shell-Skript und wertet es dann in der aktuellen Shell aus.Die eval-Anweisung weist die Shell an, die Argumente von eval als Befehl zu verwenden und sie über die Befehlszeile auszuführen. Es ist nützlich in einer Situation wie der folgenden:
Wenn Sie in Ihrem Skript einen Befehl in eine Variable definieren und später diesen Befehl verwenden möchten, sollten Sie eval verwenden:
quelle
Update: Einige Leute sagen, man sollte niemals eval verwenden. Ich bin nicht einverstanden. Ich denke, das Risiko entsteht, wenn korrupte Eingaben an weitergegeben werden können
eval
. Es gibt jedoch viele häufige Situationen, in denen dies kein Risiko darstellt, und daher lohnt es sich zu wissen, wie man eval auf jeden Fall verwendet. Diese Stackoverflow-Antwort erklärt die Risiken der Bewertung und Alternativen zur Bewertung. Letztendlich ist es Sache des Benutzers, zu bestimmen, ob / wann die Bewertung sicher und effizient ist.Mit der Bash-
eval
Anweisung können Sie Codezeilen ausführen, die von Ihrem Bash-Skript berechnet oder erfasst wurden.Das vielleicht einfachste Beispiel wäre ein Bash-Programm, das ein anderes Bash-Skript als Textdatei öffnet, jede Textzeile liest und verwendet
eval
, um sie der Reihe nach auszuführen. Dies ist im Wesentlichen das gleiche Verhalten wie bei der Bash-source
Anweisung, die verwendet wird, es sei denn, es ist erforderlich, eine Art Transformation (z. B. Filtern oder Ersetzen) für den Inhalt des importierten Skripts durchzuführen.Ich habe es selten gebraucht
eval
, aber ich fand es nützlich, Variablen zu lesen oder zu schreiben, deren Namen in Zeichenfolgen enthalten waren, die anderen Variablen zugewiesen wurden. Zum Beispiel, um Aktionen für Variablensätze auszuführen, während der Code-Footprint klein gehalten und Redundanz vermieden wird.eval
ist konzeptionell einfach. Die strenge Syntax der Bash-Sprache und die Parsing-Reihenfolge des Bash-Interpreters können jedoch nuanciert werden undeval
kryptisch und schwer zu verwenden oder zu verstehen erscheinen. Hier sind die wesentlichen Punkte:Das übergebene Argument
eval
ist ein Zeichenfolgenausdruck , der zur Laufzeit berechnet wird.eval
führt das endgültige analysierte Ergebnis seines Arguments als tatsächliche Codezeile in Ihrem Skript aus.Syntax und Analysereihenfolge sind streng. Wenn das Ergebnis keine ausführbare Zeile mit Bash-Code ist, stürzt das Programm im Rahmen Ihres Skripts bei der
eval
Anweisung ab, wenn es versucht, Müll auszuführen.Beim Testen können Sie die
eval
Anweisung durch ersetzenecho
und sehen, was angezeigt wird. Wenn es sich im aktuellen Kontext um legitimen Code handelt,eval
funktioniert das Durchlaufen .Die folgenden Beispiele können helfen, die Funktionsweise von eval zu verdeutlichen ...
Beispiel 1:
eval
Anweisung vor 'normalem' Code ist ein NOPIm obigen Beispiel haben die ersten
eval
Aussagen keinen Zweck und können beseitigt werden.eval
ist in der ersten Zeile sinnlos, da der Code keinen dynamischen Aspekt aufweist, dh er wurde bereits in die letzten Zeilen des Bash-Codes analysiert, sodass er mit einer normalen Code-Anweisung im Bash-Skript identisch wäre. Der zweiteeval
ist ebenfalls sinnlos, da es zwar einen Parsing-Schritt gibt, der$a
in sein Literal-String-Äquivalent konvertiert , aber keine Indirektion (z. B. keine Referenzierung über den String-Wert eines tatsächlichen Bash-Substantivs oder einer Bash-gehaltenen Skriptvariablen) erfolgt, sodass er sich identisch verhält als Codezeile ohneeval
Präfix.Beispiel 2:
Führen Sie die Variablenzuweisung mit Variablennamen durch, die als Zeichenfolgenwerte übergeben werden.
Wenn Sie
echo $key=$val
wären, wäre die Ausgabe:Das , das endgültige Ergebnis der String - Parsing ist, ist das, was von eval ausgeführt werden, damit das Ergebnis der Echo - Anweisung am Ende ...
Beispiel 3:
Hinzufügen von mehr Indirektion zu Beispiel 2
Das Obige ist etwas komplizierter als das vorherige Beispiel und stützt sich stärker auf die Parsing-Reihenfolge und die Besonderheiten von Bash. Die
eval
Zeile wird intern grob in der folgenden Reihenfolge analysiert (beachten Sie, dass die folgenden Anweisungen Pseudocode und kein echter Code sind, nur um zu zeigen, wie die Anweisung intern in Schritte unterteilt wird, um zum Endergebnis zu gelangen) .Wenn die angenommene Analysereihenfolge nicht erklärt, was eval genug tut, beschreibt das dritte Beispiel das Parsen möglicherweise detaillierter, um zu klären, was vor sich geht.
Beispiel 4:
Ermitteln Sie, ob Vars, deren Namen in Zeichenfolgen enthalten sind, selbst Zeichenfolgenwerte enthalten.
In der ersten Iteration:
Bash analysiert das Argument
eval
undeval
sieht dies zur Laufzeit buchstäblich:Der folgende Pseudocode versucht zu veranschaulichen, wie bash die obige Zeile des realen Codes interpretiert , um zu dem endgültigen Wert zu gelangen, der von ausgeführt wird
eval
. (Die folgenden Zeilen beschreiben den nicht genauen Bash-Code):Sobald das gesamte Parsen abgeschlossen ist, wird das Ergebnis ausgeführt, und seine Wirkung ist offensichtlich. Dies zeigt, dass an sich nichts besonders Geheimnisvolles
eval
ist und die Komplexität im Parsen des Arguments liegt.Der verbleibende Code im obigen Beispiel testet einfach, ob der $ varval zugewiesene Wert null ist, und fordert den Benutzer in diesem Fall auf, einen Wert anzugeben.
quelle
Ich habe ursprünglich absichtlich nie gelernt, wie man Eval benutzt, weil die meisten Leute empfehlen werden, sich wie die Pest davon fernzuhalten. Allerdings habe ich kürzlich einen Anwendungsfall entdeckt, der mich dazu brachte, ihn nicht früher zu erkennen.
Wenn Sie Cron-Jobs haben, die Sie interaktiv zum Testen ausführen möchten, können Sie den Inhalt der Datei mit cat anzeigen und den Cron-Job kopieren und einfügen, um ihn auszuführen. Leider geht es darum, die Maus zu berühren, was in meinem Buch eine Sünde ist.
Nehmen wir an, Sie haben einen Cron-Job unter /etc/cron.d/repeatme mit dem Inhalt:
*/10 * * * * root program arg1 arg2
Sie können dies nicht als Skript mit dem gesamten Junk davor ausführen, aber wir können cut verwenden, um den gesamten Junk zu entfernen, ihn in eine Subshell zu wickeln und den String mit eval auszuführen
eval $( cut -d ' ' -f 6- /etc/cron.d/repeatme)
Der Befehl cut druckt nur das 6. Feld der Datei aus, das durch Leerzeichen begrenzt ist. Eval führt diesen Befehl dann aus.
Ich habe hier einen Cron-Job als Beispiel verwendet, aber das Konzept besteht darin, Text aus stdout zu formatieren und diesen Text dann auszuwerten.
Die Verwendung von eval ist in diesem Fall nicht unsicher, da wir genau wissen, was wir vorab bewerten werden.
quelle
Ich musste kürzlich
eval
erzwingen, dass mehrere Klammererweiterungen in der von mir benötigten Reihenfolge ausgewertet werden. Bash führt also mehrere Klammererweiterungen von links nach rechts durcherweitert sich auf
aber ich brauchte die zweite Klammererweiterung, die zuerst durchgeführt wurde und nachgab
Das Beste, was ich mir dazu einfallen lassen konnte, war
Dies funktioniert, weil die einfachen Anführungszeichen den ersten Satz von Klammern während des Parsens der
eval
Befehlszeile vor einer Erweiterung schützen und sie durch die von aufgerufene Unterschale erweitert werden könneneval
.Es mag ein listiges Schema geben, das verschachtelte Klammererweiterungen beinhaltet, die dies in einem Schritt ermöglichen, aber wenn es welche gibt, bin ich zu alt und dumm, um es zu sehen.
quelle
Sie haben nach typischen Verwendungszwecken gefragt.
Eine häufige Beschwerde über Shell-Skripte ist, dass Sie (angeblich) nicht als Referenz übergeben können, um Werte aus Funktionen zurückzugewinnen.
Aber eigentlich über "eval" Sie können als Verweis übergeben. Der Angerufene kann eine Liste von Variablenzuweisungen zurückgeben, die vom Anrufer ausgewertet werden sollen. Es wird als Referenz übergeben, da der Aufrufer die Namen der Ergebnisvariablen angeben kann (siehe Beispiel unten). Fehlerergebnisse können Standardnamen wie errno und errstr zurückgegeben werden.
Hier ist ein Beispiel für die Übergabe als Referenz in Bash:
Die Ausgabe sieht folgendermaßen aus:
In dieser Textausgabe ist die Bandbreite nahezu unbegrenzt ! Und es gibt mehr Möglichkeiten, wenn mehrere Ausgabezeilen verwendet werden: z. B. könnte die erste Zeile für variable Zuweisungen verwendet werden, die zweite für einen kontinuierlichen „Gedankenstrom“, aber das würde den Rahmen dieses Beitrags sprengen.
quelle
Ich mag die Antwort "Bewerten Sie Ihren Ausdruck ein weiteres Mal vor der Ausführung" und möchte dies anhand eines anderen Beispiels verdeutlichen.
Die merkwürdigen Ergebnisse in Option 2 sind, dass wir 2 Parameter wie folgt übergeben hätten:
"value
content"
Wie ist das für kontraintuitiv? Das zusätzliche
eval
wird das beheben.Angepasst von https://stackoverflow.com/a/40646371/744133
quelle
In der Frage:
gibt Fehler aus, die behaupten, dass die Dateien a und tty nicht vorhanden sind. Ich habe dies so verstanden, dass tty nicht vor der Ausführung von grep interpretiert wird, sondern dass bash tty als Parameter an grep übergeben hat, der es als Dateinamen interpretiert hat.
Es gibt auch eine Situation verschachtelter Umleitung, die von übereinstimmenden Klammern behandelt werden sollte, die einen untergeordneten Prozess angeben sollten. Bash ist jedoch primitiv ein Worttrennzeichen, das Parameter erstellt, die an ein Programm gesendet werden sollen. Daher werden Klammern nicht zuerst abgeglichen, sondern als interpretiert gesehen.
Ich wurde spezifisch mit grep und gab die Datei als Parameter an, anstatt eine Pipe zu verwenden. Ich habe auch den Basisbefehl vereinfacht und die Ausgabe eines Befehls als Datei übergeben, damit die E / A-Piping nicht verschachtelt wird:
funktioniert gut.
ist nicht wirklich erwünscht, beseitigt aber das verschachtelte Rohr und funktioniert auch gut.
Zusammenfassend scheint Bash kein verschachteltes Pipping zu mögen. Es ist wichtig zu verstehen, dass bash kein rekursiv geschriebenes New-Wave-Programm ist. Stattdessen ist bash ein altes 1,2,3-Programm, an das Funktionen angehängt wurden. Um die Abwärtskompatibilität sicherzustellen, wurde die ursprüngliche Interpretationsweise nie geändert. Wenn bash so umgeschrieben würde, dass es zuerst mit Klammern übereinstimmt, wie viele Fehler würden in wie viele bash-Programme eingeführt? Viele Programmierer lieben es, kryptisch zu sein.
quelle