eval
und exec
beide sind in bash (1) Befehle eingebaut, die Befehle ausführen.
Ich sehe exec
auch ein paar Möglichkeiten, aber ist das der einzige Unterschied? Was passiert mit ihrem Kontext?
eval
und exec
beide sind in bash (1) Befehle eingebaut, die Befehle ausführen.
Ich sehe exec
auch ein paar Möglichkeiten, aber ist das der einzige Unterschied? Was passiert mit ihrem Kontext?
Antworten:
eval
und exec
sind 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 cmd
tut, 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/ls
ruft fork()
ein Kind Prozess zu schaffen, und dann exec()
in das Kind auszuführen /bin/ls
. exec /bin/ls
auf 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/self
die PID ls
der Shell auf, die ausgeführt wurde. Normalerweise sind die Prozess-IDs unterschiedlich, jedoch mit exec
der Shell und ls
der gleichen Prozess-ID. Außerdem wurde der folgende Befehl exec
nicht ausgeführt, da die Shell ersetzt wurde.
Auf der anderen Seite:
$ help eval
eval: eval [arg ...]
Execute arguments as a shell command.
eval
Führt die Argumente als Befehl in der aktuellen Shell aus. Mit anderen Worten eval foo bar
ist 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/ls
wird ein untergeordneter Prozess erstellt, so wie es ein einfacher alter tun /bin/ls
würde.)
Oder wir könnten einen Befehl haben, der Shell-Befehle ausgibt. Beim Ausführen wird ssh-agent
der 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 ssh
Befehle, die Sie ausführen würden). Daher ssh-agent
kann begonnen werden mit:
eval $(ssh-agent)
Und die aktuelle Shell erhält die Variablen, die andere Befehle erben sollen.
Wenn die Variable cmd
zufä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 eval
sicher ist, bevor man sie verwendet.
Oft ist es möglich, das eval
versehentliche Verwechseln von Code und Daten zu vermeiden .
eval
in erster Linie auch auf diese Antwort. Sachen wie indirekt Steuern von Variablen können in vielen Schalen durch getan werden declare
/ typeset
/ nameref
wie und Erweiterungen ${!var}
, also würde ich diejenigen statt verwenden , eval
es sei denn ich hatte wirklich , es zu vermeiden.
exec
erstellt 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 bash
und dann drin schlag ich exec /bin/echo
. Wir können sehen, dass ich später wieder hineingefallen bin, ksh
weil der bash
Prozess durch ersetzt wurde /bin/echo
.
exec
wird verwendet, um den aktuellen Shell-Prozess durch neue zu ersetzen und Stream-Umleitungs- / Dateideskriptoren zu behandeln, wenn kein Befehl angegeben wurde. eval
wird verwendet, um Zeichenfolgen als Befehle auszuwerten. Beide können verwendet werden, um einen Befehl mit zur Laufzeit bekannten Argumenten zu erstellen und auszuführen, exec
ersetzen jedoch zusätzlich zur Ausführung von Befehlen den Prozess der aktuellen Shell.
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 bash
mit PID 1234 ausgeführt wurden und exec top -u root
in dieser Shell ausgeführt werden sollten, hat der top
Befehl 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 exec
ersetzen 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 stdout
nur 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 bash
Version 4.3 das exec
eingebaute in definiert builtins/exec.def
. Es analysiert die empfangenen Befehle und leitet, falls vorhanden, Dinge an shell_execve()
die in der execute_cmd.c
Datei definierte Funktion weiter .
Um es kurz zu machen, es gibt eine Familie von exec
Befehlen 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;
{
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 exec
bei der Simulation von execve()
Funktionen eval
dient 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.def
und übergibt die analysierte Eingabezeichenfolge an evalstring()
function.
eval
Kann unter anderem Variablen zuweisen, die in der aktuellen Shell-Ausführungsumgebung verbleiben, während exec
Folgendes nicht möglich ist:
$ eval x=42
$ echo $x
42
$ exec x=42
bash: exec: x=42: not found
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 eval
ist, 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 exec
ein 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 eval
ein exec
nicht zur aufrufenden Shell zurück, es sei denn, die Shell exec
selbst schlägt fehl, weil sie die ausführbare Datei nicht finden oder laden kann oder weil es zu Problemen bei der Argumenterweiterung kommt.
eval
Grundsä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. exec
macht so etwas nicht.
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 exec
das 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, eval
ist 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
exec
)