eval
e exec
sono entrambi integrati nei comandi di bash (1) che eseguono i comandi.
Vedo anche exec
alcune opzioni, ma è l'unica differenza? Cosa succede al loro contesto?
eval
e exec
sono entrambi integrati nei comandi di bash (1) che eseguono i comandi.
Vedo anche exec
alcune opzioni, ma è l'unica differenza? Cosa succede al loro contesto?
Risposte:
eval
e exec
sono animali completamente diversi. (A parte il fatto che entrambi eseguiranno i comandi, ma anche tutto ciò che fai in una shell.)
$ help exec
exec: exec [-cl] [-a name] [command [arguments ...]] [redirection ...]
Replace the shell with the given command.
Quello che exec cmd
fa, è esattamente lo stesso che solo in esecuzione cmd
, tranne per il fatto che la shell corrente viene sostituita con il comando, invece di eseguire un processo separato. Internamente, l'esecuzione di say /bin/ls
chiamerà fork()
per creare un processo figlio e quindi exec()
nel bambino eseguirà /bin/ls
. exec /bin/ls
d'altra parte non si biforcherà, ma sostituirà semplicemente la shell.
Confrontare:
$ bash -c 'echo $$ ; ls -l /proc/self ; echo foo'
7218
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7219
foo
con
$ bash -c 'echo $$ ; exec ls -l /proc/self ; echo foo'
7217
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7217
echo $$
stampa il PID della shell che ho avviato e l'elenco /proc/self
ci fornisce il PID della ls
shell eseguita dalla shell. Di solito, gli ID processo sono diversi, ma con exec
la shell e ls
hanno lo stesso ID processo. Inoltre, il comando seguente exec
non è stato eseguito, poiché la shell è stata sostituita.
D'altro canto:
$ help eval
eval: eval [arg ...]
Execute arguments as a shell command.
eval
eseguirà gli argomenti come comando nella shell corrente. In altre parole eval foo bar
è lo stesso di solo foo bar
. Ma le variabili verranno espanse prima dell'esecuzione, quindi possiamo eseguire i comandi salvati nelle variabili shell:
$ unset bar
$ cmd="bar=foo"
$ eval "$cmd"
$ echo "$bar"
foo
Essa non crea un processo figlio, quindi la variabile è impostata nella shell corrente. (Naturalmente eval /bin/ls
creerà un processo figlio, proprio come /bin/ls
farebbe un vecchio normale .)
Oppure potremmo avere un comando che emette comandi di shell. L'esecuzione ssh-agent
avvia l'agente in background e genera una serie di assegnazioni di variabili, che possono essere impostate nella shell corrente e utilizzate dai processi figlio (i ssh
comandi da eseguire). Quindi ssh-agent
può essere avviato con:
eval $(ssh-agent)
E la shell corrente otterrà le variabili da ereditare per altri comandi.
Ovviamente, se la variabile cmd
contenesse qualcosa del genere rm -rf $HOME
, l'esecuzione eval "$cmd"
non sarebbe qualcosa che vorresti fare. Anche cose come le sostituzioni di comandi all'interno della stringa verrebbero elaborate, quindi si dovrebbe davvero essere sicuri che l'input to eval
sia sicuro prima di usarlo.
Spesso è possibile evitare eval
ed evitare anche la miscelazione accidentale di codice e dati nel modo sbagliato.
eval
responsabilità riguardo al non utilizzare in primo luogo anche questa risposta. Cose come la modifica indiretta delle variabili possono essere fatte in molte shell tramite declare
/ typeset
/ nameref
e espansioni come ${!var}
, quindi userei quelle invece che a eval
meno che non dovessi davvero evitarlo.
exec
non crea un nuovo processo. Esso sostituisce il processo corrente con il nuovo comando. Se lo hai fatto sulla riga di comando, la sessione della shell terminerà effettivamente (e forse ti disconnetterai o chiuderai la finestra del terminale!)
per esempio
ksh% bash
bash-4.2$ exec /bin/echo hello
hello
ksh%
Eccomi qui ksh
(il mio guscio normale). Inizio bash
e poi dentro bash exec /bin/echo
. Possiamo vedere che sono stato ricacciato in ksh
seguito perché il bash
processo è stato sostituito da /bin/echo
.
exec
viene utilizzato per sostituire l'attuale processo di shell con nuovi e gestire i reindirizzamenti / descrittori di file di flusso se non è stato specificato alcun comando. eval
viene utilizzato per valutare le stringhe come comandi. Entrambi possono essere utilizzati per creare ed eseguire un comando con argomenti noti in fase di esecuzione, ma exec
sostituisce il processo della shell corrente oltre all'esecuzione dei comandi.
Sintassi:
exec [-cl] [-a name] [command [arguments]]
Secondo il manuale se esiste un comando specificato questo incorporato
... sostituisce la shell. Non viene creato alcun nuovo processo. Gli argomenti diventano gli argomenti da comandare.
In altre parole, se si eseguiva bash
PID 1234 e se si eseguisse exec top -u root
all'interno di quella shell, il top
comando avrà quindi PID 1234 e sostituirà il processo della shell.
Dove è utile? In qualcosa noto come script wrapper. Tali script costruiscono serie di argomenti o prendono determinate decisioni su quali variabili passare nell'ambiente, quindi usano exec
per sostituirsi con qualsiasi comando sia specificato e, naturalmente, forniscono quegli stessi argomenti che lo script wrapper ha costruito lungo la strada.
Ciò che il manuale afferma anche che:
Se il comando non viene specificato, tutti i reindirizzamenti avranno effetto nella shell corrente
Questo ci consente di reindirizzare qualsiasi cosa dagli attuali flussi di output delle shell in un file. Ciò può essere utile per scopi di registrazione o filtro, in cui non si desidera vedere i stdout
comandi ma solo stderr
. Ad esempio, in questo modo:
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
Questo comportamento lo rende utile per accedere agli script della shell , reindirizzare i flussi a file o processi separati e altre cose divertenti con descrittori di file.
A livello di codice sorgente almeno per la bash
versione 4.3, exec
è definito in builtins/exec.def
. Analizza i comandi ricevuti e, se ce ne sono, passa le cose alla shell_execve()
funzione definita nel execute_cmd.c
file.
Per farla breve, esiste una famiglia di exec
comandi nel linguaggio di programmazione C ed shell_execve()
è sostanzialmente una funzione wrapper di execve
:
/* Call execve (), handling interpreting shell scripts, and handling
exec failures. */
int
shell_execve (command, args, env)
char *command;
char **args, **env;
{
Il manuale di bash 4.3 afferma (enfasi aggiunta da me):
Gli arg vengono letti e concatenati insieme in un unico comando. Questo comando viene quindi letto ed eseguito dalla shell e il suo stato di uscita viene restituito come valore di eval.
Si noti che non si verificano sostituzioni di processi. A differenza di exec
dove l'obiettivo è simulare la execve()
funzionalità, l' eval
integrato serve solo a "valutare" gli argomenti, proprio come se l'utente li avesse digitati sulla riga di comando. Pertanto, vengono creati nuovi processi.
Dove potrebbe essere utile? Come ha sottolineato Gilles in questa risposta , "... eval non viene usato molto spesso. In alcune shell, l'uso più comune è quello di ottenere il valore di una variabile il cui nome non è noto fino al runtime". Personalmente, l'ho usato in un paio di script su Ubuntu in cui era necessario eseguire / valutare un comando basato sullo spazio di lavoro specifico che l'utente stava attualmente utilizzando.
A livello di codice sorgente, viene definito builtins/eval.def
e passa la stringa di input analizzata per evalstring()
funzionare.
Tra le altre cose, eval
può assegnare variabili che rimangono nell'attuale ambiente di esecuzione della shell, mentre exec
non possono:
$ eval x=42
$ echo $x
42
$ exec x=42
bash: exec: x=42: not found
creando un nuovo processo figlio, esegui gli argomenti e restituisce lo stato di uscita.
Cosa? Il punto eval
è che non crea in alcun modo un processo figlio. Se lo faccio
eval "cd /tmp"
in una shell, quindi la shell corrente avrà cambiato directory. Né exec
crea un nuovo processo figlio, invece cambia l'eseguibile corrente (ovvero la shell) per quello dato; l'id di processo (e aprire file e altre cose) rimangono gli stessi. Al contrario eval
, un exec
non tornerà alla shell chiamante a meno che lo exec
stesso non fallisca a causa dell'incapacità di trovare o caricare l'eseguibile o morire per problemi di espansione dell'argomento.
eval
fondamentalmente interpreta i suoi argomenti come una stringa dopo la concatenazione, ovvero farà un ulteriore livello di espansione con caratteri jolly e suddivisione degli argomenti. exec
non fa niente del genere.
Valutazione
Questi lavori:
$ echo hi
hi
$ eval "echo hi"
hi
$ exec echo hi
hi
Tuttavia, questi non:
$ exec "echo hi"
bash: exec: echo hi: not found
$ "echo hi"
bash: echo hi: command not found
Sostituzione dell'immagine di processo
Questo esempio dimostra come exec
sostituisce l'immagine del suo processo di chiamata:
# 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
Si noti che è stato exec echo $$
eseguito con il PID della subshell! Inoltre, dopo che è stato completato, siamo tornati nella nostra sh$
shell originale .
D'altra parte, eval
non senza sostituire l'immagine di processo. Piuttosto, esegue il comando dato come si farebbe normalmente all'interno della shell stessa. (Naturalmente, se si esegue un comando che richiede la generazione di un processo ... fa proprio questo!)
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
)