Was ist der Unterschied zwischen eval und exec?

Antworten:

124

evalund execsind ganz andere Tiere. (Abgesehen von der Tatsache, dass beide Befehle ausführen, aber auch alles, was Sie in einer Shell tun.)

$ help exec
exec: exec [-cl] [-a name] [command [arguments ...]] [redirection ...]
    Replace the shell with the given command.

Was exec cmdtut, ist genau dasselbe wie nur ausgeführt cmd, außer dass die aktuelle Shell durch den Befehl ersetzt wird, anstatt dass ein separater Prozess ausgeführt wird. Intern sagen wir laufen /bin/lsruft fork()ein Kind Prozess zu schaffen, und dann exec()in das Kind auszuführen /bin/ls. exec /bin/lsauf der anderen Seite wird nicht gabeln, sondern nur die Schale ersetzt.

Vergleichen Sie:

$ bash -c 'echo $$ ; ls -l /proc/self ; echo foo'
7218
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7219
foo

mit

$ bash -c 'echo $$ ; exec ls -l /proc/self ; echo foo'
7217
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7217

echo $$gibt die PID der Shell aus, die ich gestartet habe, und listet /proc/selfdie PID lsder Shell auf, die ausgeführt wurde. Normalerweise sind die Prozess-IDs unterschiedlich, jedoch mit execder Shell und lsder gleichen Prozess-ID. Außerdem wurde der folgende Befehl execnicht ausgeführt, da die Shell ersetzt wurde.


Auf der anderen Seite:

$ help eval
eval: eval [arg ...]
    Execute arguments as a shell command.

evalFührt die Argumente als Befehl in der aktuellen Shell aus. Mit anderen Worten eval foo barist das gleiche wie nur foo bar. Variablen werden jedoch vor der Ausführung erweitert, sodass wir Befehle ausführen können, die in Shell-Variablen gespeichert sind:

$ unset bar
$ cmd="bar=foo"
$ eval "$cmd"
$ echo "$bar"
foo

Es wird kein untergeordneter Prozess erstellt, daher wird die Variable in der aktuellen Shell festgelegt. (Natürlich eval /bin/lswird ein untergeordneter Prozess erstellt, so wie es ein einfacher alter tun /bin/lswürde.)

Oder wir könnten einen Befehl haben, der Shell-Befehle ausgibt. Beim Ausführen wird ssh-agentder Agent im Hintergrund gestartet und es werden eine Reihe von Variablenzuweisungen ausgegeben, die in der aktuellen Shell festgelegt und von untergeordneten Prozessen verwendet werden können (die sshBefehle, die Sie ausführen würden). Daher ssh-agentkann begonnen werden mit:

eval $(ssh-agent)

Und die aktuelle Shell erhält die Variablen, die andere Befehle erben sollen.


Wenn die Variable cmdzufällig etwas wie enthält rm -rf $HOME, ist das Ausführen eval "$cmd"natürlich nicht das, was Sie tun möchten. Sogar Dinge wie Befehlsersetzungen innerhalb des Strings würden verarbeitet, daher sollte man wirklich sicher sein, dass die Eingabe in evalsicher ist, bevor man sie verwendet.

Oft ist es möglich, das evalversehentliche Verwechseln von Code und Daten zu vermeiden .

ilkkachu
quelle
Das ist eine großartige Antwort, @ilkkachu. Vielen Dank!
Willian Paixao
Weitere Informationen zur Verwendung von eval finden Sie hier: stackoverflow.com/a/46944004/2079103
clearlight
1
@clearlight, na ja, das erinnert sie die übliche Haftungsausschluss hinzufügen etwa nicht mit evalin erster Linie auch auf diese Antwort. Sachen wie indirekt Steuern von Variablen können in vielen Schalen durch getan werden declare/ typeset/ namerefwie und Erweiterungen ${!var}, also würde ich diejenigen statt verwenden , evales sei denn ich hatte wirklich , es zu vermeiden.
ilkkachu
27

execerstellt keinen neuen Prozess. Es ersetzt den aktuellen Prozess durch den neuen Befehl. Wenn Sie dies über die Befehlszeile getan haben, wird Ihre Shell-Sitzung effektiv beendet (und Sie werden möglicherweise abgemeldet oder das Terminalfenster geschlossen!).

z.B

ksh% bash
bash-4.2$ exec /bin/echo hello
hello
ksh% 

Hier bin ich in ksh(meine normale Muschel). Ich fange an bashund dann drin schlag ich exec /bin/echo. Wir können sehen, dass ich später wieder hineingefallen bin, kshweil der bashProzess durch ersetzt wurde /bin/echo.

Stephen Harris
quelle
umm auf dem Gesicht fiel zurück in ksh b / c Prozess wurde durch Echo ersetzt macht das nicht viel Sinn?
14

TL; DR

execwird verwendet, um den aktuellen Shell-Prozess durch neue zu ersetzen und Stream-Umleitungs- / Dateideskriptoren zu behandeln, wenn kein Befehl angegeben wurde. evalwird verwendet, um Zeichenfolgen als Befehle auszuwerten. Beide können verwendet werden, um einen Befehl mit zur Laufzeit bekannten Argumenten zu erstellen und auszuführen, execersetzen jedoch zusätzlich zur Ausführung von Befehlen den Prozess der aktuellen Shell.

Exec Buil-In

Syntax:

exec [-cl] [-a name] [command [arguments]]

Laut Handbuch, wenn dort ein Befehl angegeben ist, ist dieser eingebaut

... ersetzt die Schale. Es wird kein neuer Prozess erstellt. Die Argumente werden zu den zu befehlenden Argumenten.

Mit anderen Worten, wenn Sie bashmit PID 1234 ausgeführt wurden und exec top -u rootin dieser Shell ausgeführt werden sollten, hat der topBefehl PID 1234 und ersetzt Ihren Shell-Prozess.

Wo ist das nützlich? In so genannten Wrapper-Skripten. Solche Skripte bauen eine Reihe von Argumenten auf oder treffen bestimmte Entscheidungen darüber, welche Variablen an die Umgebung übergeben werden sollen, und execersetzen sich dann durch den angegebenen Befehl. Dabei werden natürlich dieselben Argumente bereitgestellt, die das Wrapper-Skript auf diesem Weg aufgebaut hat.

Im Handbuch heißt es außerdem:

Wenn der Befehl nicht angegeben wird, werden alle Umleitungen in der aktuellen Shell wirksam

Dies ermöglicht es uns, alles von aktuellen Shells-Ausgabestreams in eine Datei umzuleiten. Dies kann für Protokollierungs- oder Filterzwecke nützlich sein, bei denen Sie nicht stdoutnur Befehle sehen möchten stderr. Zum Beispiel so:

bash-4.3$ exec 3>&1
bash-4.3$ exec > test_redirect.txt
bash-4.3$ date
bash-4.3$ echo "HELLO WORLD"
bash-4.3$ exec >&3
bash-4.3$ cat test_redirect.txt 
2017 05 20 星期六 05:01:51 MDT
HELLO WORLD

Dieses Verhalten macht es praktisch, wenn Sie sich in Shell-Skripten anmelden, Streams umleiten, um Dateien oder Prozesse zu trennen , und andere unterhaltsame Dinge mit Dateideskriptoren.

Auf der Quellcode-Ebene ist mindestens für bashVersion 4.3 das execeingebaute in definiert builtins/exec.def. Es analysiert die empfangenen Befehle und leitet, falls vorhanden, Dinge an shell_execve()die in der execute_cmd.cDatei definierte Funktion weiter .

Um es kurz zu machen, es gibt eine Familie von execBefehlen in der Programmiersprache C, die shell_execve()im Grunde genommen eine Wrapper-Funktion von execve:

/* Call execve (), handling interpreting shell scripts, and handling
   exec failures. */
int
shell_execve (command, args, env)
     char *command;
     char **args, **env;
{

eval eingebaut

Das Handbuch zu Bash 4.3 besagt (Hervorhebung von mir hinzugefügt):

Die Argumente werden gelesen und zu einem einzigen Befehl zusammengefasst. Dieser Befehl wird dann von der Shell gelesen und ausgeführt , und sein Beendigungsstatus wird als Wert von eval zurückgegeben.

Beachten Sie, dass kein Prozessaustausch stattfindet. Anders als execbei der Simulation von execve()Funktionen evaldient die integrierte Funktion nur zum "Auswerten" von Argumenten, als hätte der Benutzer sie in die Befehlszeile eingegeben. Dadurch entstehen neue Prozesse.

Wo könnte das nützlich sein? In dieser Antwort wies Gilles darauf hin , dass "... eval nicht sehr oft verwendet wird. In einigen Shells wird am häufigsten der Wert einer Variablen abgerufen, deren Name erst zur Laufzeit bekannt ist". Persönlich habe ich es in einigen Skripten unter Ubuntu verwendet, in denen es notwendig war, einen Befehl basierend auf dem spezifischen Arbeitsbereich auszuführen / auszuwerten, den der Benutzer aktuell verwendete.

Auf der Quellcode-Ebene wird sie in definiert builtins/eval.defund übergibt die analysierte Eingabezeichenfolge an evalstring()function.

evalKann unter anderem Variablen zuweisen, die in der aktuellen Shell-Ausführungsumgebung verbleiben, während execFolgendes nicht möglich ist:

$ eval x=42
$ echo $x
42
$ exec x=42
bash: exec: x=42: not found
Sergiy Kolodyazhnyy
quelle
@ ctrl-alt-delor Ich habe diesen Teil bearbeitet, danke, obwohl es wohl einen neuen Prozess hervorbringt, bleibt die PID nur die gleiche wie in der aktuellen Shell. Erwägen Sie in Zukunft, Antworten zu bearbeiten, anstatt einen Kommentar und eine Gegenstimme zu hinterlassen, insbesondere dann, wenn eine Kleinigkeit wie eine 7-Wort-Phrase in Frage kommt. Das Bearbeiten und Korrigieren einer Antwort ist weitaus weniger zeitaufwendig und weitaus hilfreicher.
Sergiy Kolodyazhnyy
5
Erstellen Sie einen neuen untergeordneten Prozess, führen Sie die Argumente aus und geben Sie den Beendigungsstatus zurück.

UH, was? Der springende Punkt dabei evalist, dass dadurch in keiner Weise ein untergeordneter Prozess erstellt wird. Wenn ich mache

eval "cd /tmp"

In einer Shell hat danach die aktuelle Shell das Verzeichnis geändert. Weder wird execein neuer untergeordneter Prozess erstellt, noch wird die aktuelle ausführbare Datei (dh die Shell) für den angegebenen Prozess geändert. Die Prozess-ID (und geöffnete Dateien und andere Informationen) bleiben unverändert. Im Gegensatz dazu kehrt evalein execnicht zur aufrufenden Shell zurück, es sei denn, die Shell execselbst schlägt fehl, weil sie die ausführbare Datei nicht finden oder laden kann oder weil es zu Problemen bei der Argumenterweiterung kommt.

evalGrundsätzlich interpretiert es seine Argumente nach der Verkettung als Zeichenfolge, dh es wird eine zusätzliche Ebene für die Platzhaltererweiterung und die Argumentaufteilung erstellt. execmacht so etwas nicht.

user180452
quelle
1
Die ursprüngliche Frage lautete "eval und exec sind beide in Bash (1) integrierte Befehle und führen Befehle aus, die einen neuen untergeordneten Prozess erstellen, die Argumente ausführen und den Beendigungsstatus zurückgeben". Ich habe die falsche Vermutung herausgearbeitet.
Charles Stewart
-1

Auswertung

Diese Arbeiten:

$ echo hi
hi

$ eval "echo hi"
hi

$ exec echo hi
hi

Diese tun jedoch nicht:

$ exec "echo hi"
bash: exec: echo hi: not found

$ "echo hi"
bash: echo hi: command not found

Prozessabbild ersetzen

Dieses Beispiel zeigt, wie execdas Image des aufrufenden Prozesses ersetzt wird:

# Get PID of current shell
sh$ echo $$
1234

# Enter a subshell with PID 5678
sh$ sh

# Check PID of subshell
sh-subshell$ echo $$
5678

# Run exec
sh-subshell$ exec echo $$
5678

# We are back in our original shell!
sh$ echo $$
1234

Beachten Sie, dass exec echo $$mit der PID der Unterschale lief! Darüber hinaus waren wir nach Fertigstellung wieder in unserer ursprünglichen sh$Hülle.

Auf der anderen Seite, evalist nicht ersetzt das Prozessabbild. Stattdessen wird der angegebene Befehl so ausgeführt, wie Sie es normalerweise in der Shell selbst tun würden. (Natürlich, wenn Sie einen Befehl ausführen, für den ein Prozess erzeugt werden muss ... genau das tut er!)

sh$ echo $$
1234

sh$ sh

sh-subshell$ echo $$
5678

sh-subshell$ eval echo $$
5678

# We are still in the subshell!
sh-subshell$ echo $$
5678
Mateen Ulhaq
quelle
Ich habe diese Antwort gepostet, weil die anderen Beispiele dies für schwache Köpfe wie I.
Mateen Ulhaq
Schlechte Wortwahl: Die Prozessersetzung ist ein bestehendes Konzept , das nichts mit dem zu tun hat, was Sie hier veranschaulichen.
muru
@muru Ist "Prozessersatz" besser? Ich bin nicht sicher, wie die korrekte Terminologie lautet. Die Manpage scheint es "Prozessabbild ersetzen" zu nennen.
Mateen Ulhaq
Hmm, keine Ahnung. (Aber wenn ich „ersetzt das Prozessabbild“ hören, denke ich: exec)
muru