evalund execbeide sind in bash (1) Befehle eingebaut, die Befehle ausführen.
Ich sehe execauch ein paar Möglichkeiten, aber ist das der einzige Unterschied? Was passiert mit ihrem Kontext?
evalund execbeide sind in bash (1) Befehle eingebaut, die Befehle ausführen.
Ich sehe execauch ein paar Möglichkeiten, aber ist das der einzige Unterschied? Was passiert mit ihrem Kontext?
Antworten:
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 .
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.
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.
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.
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;
{
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
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.
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
exec)