Variable als Befehl; eval vs bash -c

41

Ich habe ein Bash-Skript gelesen, das jemand erstellt hat, und ich habe festgestellt, dass der Autor eval nicht verwendet, um eine Variable als Befehl auszuwerten.
Der Autor verwendete

bash -c "$1"

Anstatt von

eval "$1"

Ich gehe davon aus, dass eval die bevorzugte Methode ist und wahrscheinlich sowieso schneller. Ist das wahr?
Gibt es einen praktischen Unterschied zwischen den beiden? Was sind bemerkenswerte Unterschiede zwischen den beiden?

Wer bin ich
quelle
In einigen Fällen können Sie auch ohne davonkommen. e='echo foo'; $efunktioniert gut
Dennis

Antworten:

40

eval "$1"Führt den Befehl im aktuellen Skript aus. Es kann Shell-Variablen aus dem aktuellen Skript festlegen und verwenden, Umgebungsvariablen für das aktuelle Skript festlegen, Funktionen aus dem aktuellen Skript festlegen und verwenden, das aktuelle Verzeichnis, die Umask, die Grenzwerte und andere Attribute für das aktuelle Skript festlegen usw. bash -c "$1"Führt den Befehl in einem vollständig separaten Skript aus, das Umgebungsvariablen, Dateideskriptoren und andere Prozessumgebungen erbt (aber keine Änderungen zurückgibt), jedoch keine internen Shell-Einstellungen (Shell-Variablen, Funktionen, Optionen, Traps usw.) erbt.

Es gibt einen anderen Weg, (eval "$1")der den Befehl in einer Subshell ausführt: Er erbt alles vom aufrufenden Skript, überträgt aber keine Änderungen zurück.

Angenommen, die Variable dirwird nicht exportiert und $1lautet cd "$foo"; lsdann:

  • cd /starting/directory; foo=/somewhere/else; eval "$1"; pwdlistet den Inhalt von /somewhere/elseund druckt /somewhere/else.
  • cd /starting/directory; foo=/somewhere/else; (eval "$1"); pwdlistet den Inhalt von /somewhere/elseund druckt /starting/directory.
  • cd /starting/directory; foo=/somewhere/else; bash -c "$1"; pwdListet den Inhalt von auf /starting/directory(da cd ""das aktuelle Verzeichnis nicht geändert wird) und druckt /starting/directory.
Gilles 'SO - hör auf böse zu sein'
quelle
Vielen Dank. Ich wusste nichts über (eval "$ 1"), unterscheidet es sich von der Quelle?
Whoami
1
@whoami (eval "$1")hat nichts damit zu tun source. Es ist nur eine Kombination von (…)und eval. source fooentspricht in etwa eval "$(cat foo)".
Gilles 'SO- hör auf böse zu sein'
Wir müssen unsere Antworten zur gleichen Zeit geschrieben haben ...
mikeserv
@whoami Der primäre Unterschied zwischen evalund .dotist , dass die evalArbeiten mit Argumenten und .dotarbeitet mit Dateien.
mikeserv
Danke euch beiden. Mein vorheriger Kommentar scheint ein bisschen dumm zu sein, jetzt wo ich ihn wieder lese ...
whoami
23

Der wichtigste Unterschied zwischen

bash -c "$1" 

Und

eval "$1"

Ist das erstere läuft in einer Unterschale und das letztere nicht. Damit:

set -- 'var=something' 
bash -c "$1"
echo "$var"

AUSGABE:

#there doesn't seem to be anything here
set -- 'var=something' 
eval "$1"
echo "$var"

AUSGABE:

something

Ich habe jedoch keine Ahnung, warum jemand die ausführbare Datei jemals bashauf diese Weise verwenden würde. Wenn Sie es aufrufen müssen, verwenden Sie die POSIX-Garantie sh. Oder (subshell eval)wenn Sie Ihre Umwelt schützen möchten.

Ich persönlich bevorzuge .dotvor allem die Muschel .

printf 'var=something%d ; echo "$var"\n' `seq 1 5` | . /dev/fd/0

AUSGABE

something1
something2
something3
something4
something5

ABER BRAUCHEN SIE ES?

Die einzige Ursache für die Verwendung besteht in dem Fall, dass Ihre Variable tatsächlich eine andere Variable zuweist oder auswertet, oder dass die Wortteilung für die Ausgabe wichtig ist.

Zum Beispiel:

var='echo this is var' ; $var

AUSGABE:

this is var

Das funktioniert, aber nur, weil echodie Anzahl der Argumente keine Rolle spielt.

var='echo "this is var"' ; $var

AUSGABE:

"this is var"

Sehen? Die doppelten Anführungszeichen kommen daher, dass das Ergebnis der Erweiterung der Shell $varnicht ausgewertet wird quote-removal.

var='printf %s\\n "this is var"' ; $var

AUSGABE:

"this
is
var"

Aber mit evaloder sh:

    var='echo "this is var"' ; eval "$var" ; sh -c "$var"

AUSGABE:

this is var
this is var

Wenn wir evaloder shdie Shell verwenden, werden die Ergebnisse der Erweiterungen in einem zweiten Durchgang überprüft und auch als potenzieller Befehl ausgewertet, sodass die Anführungszeichen einen Unterschied ausmachen. Sie können auch tun:

. <<VAR /dev/fd/0
    ${var:=echo "this is var"}
#END
VAR

AUSGABE

this is var
mikeserv
quelle
5

Ich habe einen kurzen Test gemacht:

time bash -c 'for i in {1..10000}; do bash -c "/bin/echo hi"; done'
time bash -c 'for i in {1..10000}; eval "/bin/echo hi"; done'

(Ja, ich weiß, ich habe bash -c verwendet, um die Schleife auszuführen, aber das sollte keinen Unterschied machen).

Die Ergebnisse:

eval    : 1.17s
bash -c : 7.15s

Ist evalalso schneller. Aus der Manpage von eval:

Das Dienstprogramm eval erstellt einen Befehl, indem Argumente miteinander verknüpft und jeweils durch ein Zeichen getrennt werden. Der konstruierte Befehl soll von der Shell gelesen und ausgeführt werden.

bash -cFührt den Befehl natürlich in einer Bash-Shell aus. Ein Hinweis: Ich habe verwendet, /bin/echoweil echoeine Shell mit eingebaut ist bash, was bedeutet, dass kein neuer Prozess gestartet werden muss. Das Ersetzen /bin/echodurch echofür den bash -cTest dauerte 1.28s. Das ist ungefähr das Gleiche. Ist evaljedoch schneller für die Ausführung von ausführbaren Dateien. Der Hauptunterschied besteht darin, dass evalkeine neue Shell gestartet wird (der Befehl wird in der aktuellen bash -cShell ausgeführt ), wohingegen eine neue Shell gestartet und dann der Befehl in der neuen Shell ausgeführt wird. Das Starten einer neuen Shell braucht Zeit und ist deshalb bash -clangsamer als eval.

PlasmaPower
quelle
Ich denke , die OP will vergleichen bash -cmit evalnicht exec.
Joseph R.
@ JosephR. Hoppla! Ich werde das ändern.
PlasmaPower
1
@ JosephR. Es sollte jetzt behoben sein. Auch ich habe die Tests ein bisschen mehr überarbeitet und bash -cist nicht so schlimm ...
PlasmaPower
3
Dies ist zwar der Fall, es fehlt jedoch der grundlegende Unterschied, dass der Befehl in verschiedenen Umgebungen ausgeführt wird. Es ist offensichtlich, dass das Starten einer neuen Bash-Instanz langsamer sein wird. Dies ist keine interessante Beobachtung.
Gilles 'SO- hör auf böse zu sein'