it-swarm.com.de

eval Befehl in Bash und seine typischen Verwendungen

Nach dem Lesen der bash-Manpages und in Bezug auf diese post .

Ich habe immer noch Probleme damit, zu verstehen, was genau der Befehl eval tut und welche seine typischen Verwendungen sind. Zum Beispiel, wenn wir 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 knüpfen das Dollarzeichen und der Backslash an das Problem an?

137
kstratis

eval nimmt eine Zeichenfolge als Argument und wertet sie so aus, als hätten Sie diese Zeichenfolge in eine Befehlszeile eingegeben. (Wenn Sie mehrere Argumente übergeben, werden diese zuerst mit Leerzeichen verbunden.)

${$n} ist ein Syntaxfehler in der Bash. Innerhalb der geschweiften Klammern können Sie nur einen Variablennamen mit einigen möglichen Präfixen und Suffixen verwenden, aber Sie können keine beliebige Bash-Syntax verwenden. Insbesondere können Sie keine Variablenerweiterung verwenden. Es gibt jedoch eine Möglichkeit, "den Wert der Variablen, deren Name in dieser Variablen enthalten ist" zu sagen:

echo ${!n}
one

$(…) führt den in Klammern angegebenen Befehl in einer Subshell aus (d. h. in einem separaten Prozess, bei dem alle Einstellungen, wie z. B. Variablenwerte, von der aktuellen Shell übernommen werden) und deren Ausgabe gesammelt wird. echo $($n) führt $n als Shell-Befehl aus und zeigt seine Ausgabe an. Da $n zu 1 ausgewertet wird, versucht $($n) den Befehl 1 auszuführen, der nicht vorhanden ist.

eval echo \${$n} führt die an eval übergebenen Parameter aus. Nach der Erweiterung sind die Parameter echo und ${1}. eval echo \${$n} führt also den Befehl echo ${1} aus.

Beachten Sie, dass Sie in den meisten Fällen doppelte Anführungszeichen für Variablensubstitutionen und den Befehl susbtitutions verwenden müssen (d. H. Immer wenn es einen $ gibt): "$foo", "$(foo)". Setzen Sie immer doppelte Anführungszeichen in Variablen- und Befehlssubstitutionen , es sei denn, Sie wissen, dass Sie sie weglassen müssen. Ohne Anführungszeichen führt die Shell eine Feldaufteilung durch (d. H. Sie teilt den Wert der Variablen oder die Ausgabe des Befehls in separate Wörter auf) und behandelt dann jedes Wort als Platzhaltermuster. Zum Beispiel:

$ ls
file1 file2 otherfile
$ set -- 'f* *'
$ echo "$1"
f* *
$ echo $1
file1 file2 file1 file2 otherfile
$ n=1
$ eval echo \${$n}
file1 file2 file1 file2 otherfile
$eval echo \"\${$n}\"
f* *
$ echo "${!n}"
f* *

eval wird nicht sehr oft verwendet. In einigen Shells wird am häufigsten der Wert einer Variablen abgerufen, deren Name erst zur Laufzeit bekannt ist. In bash ist dies dank der ${!VAR}-Syntax nicht erforderlich. eval ist immer noch nützlich, wenn Sie einen längeren Befehl mit Operatoren, reservierten Wörtern usw. erstellen müssen.

175
Gilles

Stellen Sie sich eval einfach so vor: "Bewerten Sie Ihren Ausdruck noch einmal vor der Ausführung".

eval echo \${$n} wird nach der ersten Bewertungsrunde zu echo $1. Drei zu beachtende Änderungen:

  • \$ wurde $ (der umgekehrte Schrägstrich wird benötigt, andernfalls versucht er, ${$n} auszuwerten, dh eine Variable mit dem Namen {$n}, die nicht zulässig ist.)
  • $n wurde ausgewertet zu 1
  • Die eval ist verschwunden

In der zweiten Runde kann echo $1 direkt ausgeführt werden.

Also wird eval <some command> zuerst <some command> auswerten (durch Auswerten meine ich hier Ersatzvariablen, ersetzte Zeichen mit Escapezeichen durch die richtigen 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 dafür ausgelegt sind, auf diese Weise gelesen zu werden. Siehe http://mywiki.wooledge.org/BashFAQ/048 für Beispiele. Der Link enthält auch einige typische Verwendungsmöglichkeiten von eval und die damit verbundenen Risiken.

32
Hari Menon

Nach meiner Erfahrung ist es eine "typische" Verwendung von eval, Befehle auszuführen, die Shell-Befehle generieren, um Umgebungsvariablen festzulegen.

Möglicherweise verfügen Sie über ein System, das eine Sammlung von Umgebungsvariablen verwendet, und Sie verfügen über ein Skript oder ein Programm, das bestimmt, welche festgelegt werden sollen und welche Werte diese Werte haben sollen. Immer, wenn Sie ein Skript oder ein Programm ausführen, wird es in einem gegabelten Prozess ausgeführt. Wenn Sie das Programm dann direkt mit Umgebungsvariablen ausführen, gehen diese verloren. 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 verwenden und sie dann löschen. Mit eval können Sie einfach:

eval "$(script-or-program)"

Beachten Sie, dass die Zitate wichtig sind. Nehmen Sie dieses (erfundene) Beispiel:

# activate.sh
echo 'I got activated!'

# test.py
print("export foo=bar/baz/womp")
print(". activate.sh")

$ eval $(python test.py)
bash: export: `.': not a valid identifier
bash: export: `activate.sh': not a valid identifier
$ eval "$(python test.py)"
I got activated!
21
sootsnoot

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 unten:

Wenn Sie in Ihrem Skript einen Befehl in einer Variablen definieren und später diesen Befehl verwenden möchten, sollten Sie eval verwenden:

/home/user1 > a="ls | more"
/home/user1 > $a
bash: command not found: ls | more
/home/user1 > # Above command didn't work as ls tried to list file with name pipe (|) and more. But these files are not there
/home/user1 > eval $a
file.txt
mailids
remote_cmd.sh
sample.txt
tmp
/home/user1 >
7
Nikhil Gupta

Update: Einige Leute sagen, man sollte eval niemals verwenden. Ich stimme dir nicht zu. Ich denke, das Risiko entsteht, wenn korrupte Eingaben an eval übergeben werden können. Es gibt jedoch viele Situationen, in denen dies kein Risiko darstellt, und es ist daher wichtig zu wissen, wie eval in jedem Fall verwendet wird. Diese Stackoverflow-Antwort erläutert die Risiken der Bewertung und der Alternativen zur Bewertung. Letztendlich liegt es am Benutzer, zu bestimmen, ob und wann eval sicher und effizient ist.


Mit der Anweisung bash eval können Sie Codezeilen ausführen, die von Ihrem bash-Skript berechnet oder abgerufen werden.

Das einfachste Beispiel wäre ein Bash-Programm, das ein anderes Bash-Skript als Textdatei öffnet, jede Textzeile liest und eval verwendet, um sie der Reihe nach auszuführen. Das ist im Wesentlichen dasselbe Verhalten wie die bash-Anweisung source, die man verwenden würde, es sei denn, es war notwendig, eine Art Transformation (z. B. Filtern oder Ersetzen) des Inhalts des importierten Skripts durchzuführen.

Ich habe selten eval gebraucht, aber ich habe es als nützlich erachtet, Variablen zu lesen oder zu schreiben, deren names in Zeichenfolgen enthalten waren, die anderen Variablen zugewiesen wurden. Beispielsweise zum Ausführen von Aktionen für Variablensätze, während der Code-Footprint klein bleibt und Redundanz vermieden wird.

eval ist konzeptionell einfach. Allerdings können die strikte Syntax der bash-Sprache und die Parsing-Reihenfolge des bash-Interpreters nuanciert werden und eval als kryptisch erscheinen und schwer zu verwenden oder zu verstehen sein. Hier sind das Wesentliche:

  1. Das an eval übergebene Argument ist ein Zeichenfolgenausdruck, der zur Laufzeit berechnet wird. eval führt das endgültige geparste Ergebnis seines Arguments als actual Codezeile in Ihrem Skript aus.

  2. Syntax und Parsing-Reihenfolge sind streng. Wenn das Ergebnis keine ausführbare Zeile mit Bash-Code ist, stürzt das Programm im Gültigkeitsbereich Ihres Skripts mit der Anweisung eval ab, wenn es versucht, Müll auszuführen.

  3. Beim Testen können Sie die eval-Anweisung durch echo ersetzen und sehen, was angezeigt wird. Wenn es sich im aktuellen Kontext um legitimen Code handelt, funktioniert die Ausführung über eval.


Die folgenden Beispiele können helfen zu klären, wie eval funktioniert ...

Beispiel 1: 

eval-Anweisung vor "normalem" Code ist ein NOP

$ eval a=b
$ eval echo $a
b

Im obigen Beispiel haben die ersten eval-Anweisungen keinen Zweck und können eliminiert werden. eval ist in der ersten Zeile sinnlos, da der Code keinen dynamischen Aspekt hat, d. h. er wurde bereits in die letzten Zeilen des Bash-Codes geparst und wäre daher als normale Code-Anweisung im Bash-Skript identisch. Die 2. Variable eval ist auch sinnlos, da es zwar einen Parsing-Schritt gibt, der $a in sein literales Zeichenkettenäquivalent konvertiert, jedoch keine Dereferenzierung vorliegt (z. B. keine Referenzierung über den Zeichenfolgenwert eines actual bash-Substantivs oder einer von Bash gehaltenen Skriptvariablen). Es würde sich also wie eine Codezeile ohne das Präfix eval verhalten.



Beispiel 2: 

Führen Sie die Var-Zuweisung mit Var-Namen durch, die als Zeichenfolgenwerte übergeben werden.

$ key="mykey"
$ val="myval"
$ eval $key=$val
$ echo $mykey
myval

Wenn Sie echo $key=$val wären, würde die Ausgabe lauten:

mykey=myval

Das ist das Endergebnis der String-Analyse und wird von eval ausgeführt, daher das Ergebnis der Echo-Anweisung am Ende ...



Beispiel 3:

Hinzufügen von Indirektion zu Beispiel 2

$ keyA="keyB"
$ valA="valB"
$ keyB="that"
$ valB="amazing"
$ eval eval \$$keyA=\$$valA
$ echo $that
amazing

Das obige ist etwas komplizierter als das vorige Beispiel und verlässt sich stärker auf die Parsing-Reihenfolge und die Besonderheiten von bash. Die eval-Zeile würde grob intern in der folgenden Reihenfolge analysiert: (Beachten Sie, dass die folgenden Anweisungen Pseudocode und nicht echten Code sind, nur um zu zeigen, wie die Anweisung intern in Schritte zerlegt wird, um zum Endergebnis zu gelangen.) .

 eval eval \$$keyA=\$$valA  # substitution of $keyA and $valA by interpreter
 eval eval \$keyB=\$valB    # convert '$' + name-strings to real vars by eval
 eval $keyB=$valB           # substitution of $keyB and $valB by interpreter
 eval that=amazing          # execute string literal 'that=amazing' by eval

Wenn die angenommene Parsing-Reihenfolge nicht erklärt, was eval genug tut, kann das dritte Beispiel das Parsing genauer beschreiben, um zu klären, was los ist.



Beispiel 4: 

Ermitteln Sie, ob Variablen, deren names in Zeichenfolgen enthalten sind, selbst Zeichenfolgenwerte enthalten.

a="User-provided"
b="Another user-provided optional value"
c=""

myvarname_a="a"
myvarname_b="b"
myvarname_c="c"

for varname in "myvarname_a" "myvarname_b" "myvarname_c"; do
    eval varval=\$$varname
    if [ -z "$varval" ]; then
        read -p "$varname? " $varname
    fi
done

In der ersten Iteration:

varname="myvarname_a"

Bash parst das Argument in eval und eval sieht zur Laufzeit buchstäblich Folgendes:

eval varval=\$$myvarname_a

Der folgende Pseudocode versucht zu veranschaulichen, wie bash die obige Zeile von real Code interpretiert, um den endgültigen Wert zu erreichen, der von eval ausgeführt wird. (die folgenden Zeilen beschreiben den Code, nicht den genauen Code):

1. eval varval="\$" + "$varname"      # This substitution resolved in eval statement
2. .................. "$myvarname_a"  # $myvarname_a previously resolved by for-loop
3. .................. "a"             # ... to this value
4. eval "varval=$a"                   # This requires one more parsing step
5. eval varval="User-provided"        # Final result of parsing (eval executes this)

Sobald das Parsing abgeschlossen ist, wird das Ergebnis ausgeführt und die Wirkung ist offensichtlich, was zeigt, dass an eval selbst nichts besonders Rätselhaftes ist, und die Komplexität liegt im Parsing seines Arguments.

varval="User-provided"

Der verbleibende Code im obigen Beispiel testet lediglich, ob der Wert, der $ varval zugewiesen wurde, null ist, und fordert den Benutzer in diesem Fall dazu auf, einen Wert anzugeben.

2
clearlight

Ich habe ursprünglich absichtlich nie gelernt, wie man mit eval umgeht, 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 zum Testen interaktiv 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 den folgenden Inhalten:

*/10 * * * * root program arg1 arg2

Sie können dies nicht als Skript mit dem gesamten Junk-Inhalt ausführen, aber wir können cut verwenden, um den gesamten Junk-Inhalt zu entfernen, ihn in eine Subshell zu packen und den String mit eval auszuführen

eval $( cut -d ' ' -f 6- /etc/cron.d/repeatme)

Der Befehl cut gibt nur das 6. Feld der Datei aus, das durch Leerzeichen begrenzt ist. Eval führt dann diesen Befehl 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 auswerten werden.

2
Luke Pafford

Sie haben nach typischen Verwendungszwecken gefragt.

Eine häufige Beschwerde über Shell-Scripting ist, dass Sie (angeblich) nicht als Referenz übergeben werden dürfen, um Werte aus den Funktionen herauszuholen.

Über "eval" übergeben Sie tatsächlich can als Referenz. Der Angerufene kann eine Liste von Variablenzuweisungen zurückgeben, die vom Anrufer ausgewertet werden sollen. Es ist Referenz für Referenz, weil der Aufrufer die Namen der Ergebnisvariablen angeben darf (siehe Beispiel unten). Fehlerergebnisse können Standardnamen wie errno und errstr zurückgegeben werden. 

Hier ein Beispiel für die Referenzübergabe in bash:

#!/bin/bash
isint()
{
    re='^[-]?[0-9]+$'
    [[ $1 =~ $re ]]
}

#args 1: name of result variable, 2: first addend, 3: second addend 
iadd()
{
    if isint ${2} && isint ${3} ; then
        echo "$1=$((${2}+${3}));errno=0"
        return 0
    else
        echo "errstr=\"Error: non-integer argument to iadd $*\" ; errno=329"
        return 1
    fi
}

var=1
echo "[1] var=$var"

eval $(iadd var A B)
if [[ $errno -ne 0 ]]; then
    echo "errstr=$errstr"
    echo "errno=$errno"
fi
echo "[2] var=$var (unchanged after error)"

eval $(iadd var $var 1)
if [[ $errno -ne 0 ]]; then
    echo "errstr=$errstr"
    echo "errno=$errno"
fi  
echo "[3] var=$var (successfully changed)"

Die Ausgabe sieht folgendermaßen aus:

[1] var=1
errstr=Error: non-integer argument to iadd var A B
errno=329
[2] var=1 (unchanged after error)
[3] var=2 (successfully changed)

In dieser Textausgabe ist fast unbegrenzt Bandbreite vorhanden! Es gibt noch mehr Möglichkeiten, wenn mehrere Ausgabezeilen verwendet werden: Zum Beispiel könnte die erste Zeile für variable Zuweisungen verwendet werden, die zweite für einen kontinuierlichen Gedankenfluss, aber das würde den Rahmen dieses Beitrags sprengen.

0
Craig Hicks

Ich musste vor kurzem eval verwenden, um zu veranlassen, dass mehrere Ausdehnungen in der von mir benötigten Reihenfolge ausgewertet werden. Bash führt also mehrere Klammererweiterungen von links nach rechts durch

xargs -I_ cat _/{11..15}/{8..5}.jpg

erweitert zu

xargs -I_ cat _/11/8.jpg _/11/7.jpg _/11/6.jpg _/11/5.jpg _/12/8.jpg _/12/7.jpg _/12/6.jpg _/12/5.jpg _/13/8.jpg _/13/7.jpg _/13/6.jpg _/13/5.jpg _/14/8.jpg _/14/7.jpg _/14/6.jpg _/14/5.jpg _/15/8.jpg _/15/7.jpg _/15/6.jpg _/15/5.jpg

aber ich brauchte die zweite Spangexpansion, die zuerst gemacht wurde, und gab nach

xargs -I_ cat _/11/8.jpg _/12/8.jpg _/13/8.jpg _/14/8.jpg _/15/8.jpg _/11/7.jpg _/12/7.jpg _/13/7.jpg _/14/7.jpg _/15/7.jpg _/11/6.jpg _/12/6.jpg _/13/6.jpg _/14/6.jpg _/15/6.jpg _/11/5.jpg _/12/5.jpg _/13/5.jpg _/14/5.jpg _/15/5.jpg

Das Beste, was ich dazu machen konnte, war

xargs -I_ cat $(eval echo _/'{11..15}'/{8..5}.jpg)

Dies funktioniert, weil die einfachen Anführungszeichen den ersten Satz von geschweiften Klammern während der Analyse der eval-Befehlszeile vor der Erweiterung schützen, sodass sie durch die von eval aufgerufene Subshell erweitert werden.

Es kann ein listiges Schema mit verschachtelten Klammererweiterungen geben, das dies in einem Schritt ermöglicht, aber wenn es da ist, bin ich zu alt und dumm, um es zu sehen.

0
flabdablet

Ich mag die Antwort "Einen Ausdruck noch einmal vor der Ausführung auswerten" und möchte mit einem anderen Beispiel verdeutlichen.

var="\"par1 par2\""
echo $var # prints nicely "par1 par2"

function cntpars() {
  echo "  > Count: $#"
  echo "  > Pars : $*"
  echo "  > par1 : $1"
  echo "  > par2 : $2"

  if [[ $# = 1 && $1 = "par1 par2" ]]; then
    echo "  > PASS"
  else
    echo "  > FAIL"
    return 1
  fi
}

# Option 1: Will Pass
echo "eval \"cntpars \$var\""
eval "cntpars $var"

# Option 2: Will Fail, with curious results
echo "cntpars \$var"
cntpars $var

Die kuriosen Ergebnisse in Option 2 sind, dass wir zwei Parameter wie folgt übergeben hätten:

  • Erster Parameter: "value
  • Zweiter Parameter: content"

Wie ist das für Counter intuitiv? Die zusätzliche Variable eval wird das beheben.

Angepasst von https://stackoverflow.com/a/40646371/744133

0
YoYo

In der frage:

who | grep $(tty | sed s:/dev/::)

gibt Fehler aus, die besagen, dass die Dateien a und tty nicht vorhanden sind. Ich verstand das so, 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.

Es gibt auch eine Situation verschachtelter Umleitung, die durch übereinstimmende Klammern behandelt werden sollte, die einen untergeordneten Prozess angeben sollten. Bash ist jedoch primitiv ein Word-Separator, der Parameter erstellt, die an ein Programm gesendet werden. Daher werden Klammern nicht als übereinstimmend betrachtet gesehen.

Ich habe spezifisch mit grep gearbeitet und die Datei als Parameter angegeben, anstatt eine Pipe zu verwenden. Ich habe auch den Basisbefehl vereinfacht und die Ausgabe eines Befehls als Datei übergeben, sodass das E/A-Piping nicht verschachtelt wäre:

grep $(tty | sed s:/dev/::) <(who)

funktioniert gut.

who | grep $(echo pts/3)

ist nicht wirklich erwünscht, beseitigt aber die verschachtelte Pipe und funktioniert auch gut.

Zusammengefasst scheint Bash kein verschachteltes Pipping zu sein. Es ist wichtig zu verstehen, dass bash kein rekursives Programm für neue Wellen ist. Stattdessen ist bash ein altes 1,2,3-Programm, dem Funktionen hinzugefügt wurden. Um die Rückwärtskompatibilität zu gewährleisten, wurde die ursprüngliche Interpretationsweise nie geändert. Wenn bash neu geschrieben wurde, um zuerst Klammern zu finden, wie viele Fehler würden in wie viele bash-Programme eingefügt? Viele Programmierer lieben es, kryptisch zu sein.

0
Carmin