Qual é a diferença entre eval e exec?

62

eval e exec são ambos criados em comandos do bash (1) que executam comandos.

Eu também vejo exec tem algumas opções, mas essa é a única diferença? O que acontece com o contexto deles?

    
por Willian Paixao 19.07.2016 / 14:36

4 respostas

102

eval e exec são animais completamente diferentes. (Além do fato de que ambos irão executar comandos, mas o mesmo acontece com tudo que você faz em um shell.)

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

O que exec cmd faz é exatamente o mesmo que executar cmd , exceto que o shell atual é substituído pelo comando, em vez de um processo separado sendo executado. Internamente, em execução, diga /bin/ls chamará fork() para criar um processo filho e, em seguida, exec() no filho para executar /bin/ls . exec /bin/ls , por outro lado, não será bifurcado, mas apenas substitui o shell.

Compare:

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

com

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

echo $$ imprime o PID do shell que iniciei, e listar /proc/self nos fornece o PID do ls que foi executado a partir do shell. Geralmente, os IDs do processo são diferentes, mas com exec o shell e ls têm o mesmo ID do processo. Além disso, o comando seguinte exec não foi executado, já que o shell foi substituído.

Por outro lado:

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

eval executará os argumentos como um comando no shell atual. Em outras palavras, eval foo bar é o mesmo que foo bar . Mas as variáveis serão expandidas antes de serem executadas, para que possamos executar os comandos salvos nas variáveis do shell:

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

Ele não criará um processo filho, portanto, a variável é definida no shell atual. (É claro que eval /bin/ls criará um processo filho, da mesma forma que um antigo /bin/ls faria.)

Ou podemos ter um comando que produza comandos do shell. Executar ssh-agent inicia o agente em segundo plano e gera várias atribuições de variáveis, que podem ser definidas no shell atual e usadas por processos filhos (os comandos ssh que você executaria). Portanto, ssh-agent pode ser iniciado com:

eval $(ssh-agent)

E o shell atual obterá as variáveis para outros comandos herdarem.

É claro que, se a variável cmd contivesse algo como rm -rf $HOME , a execução de eval "$cmd" não seria algo que você gostaria de fazer. Mesmo coisas como as substituições de comandos dentro da string seriam processadas, então deve-se realmente ter certeza de que a entrada para eval é segura antes de usá-la.

Geralmente, é possível evitar eval e evitar misturar código e dados acidentalmente de maneira errada.

    
por 19.07.2016 / 15:30
21

exec não cria um novo processo. Ele substitui o processo atual pelo novo comando. Se você fez isso na linha de comando, ele terminará efetivamente a sessão do shell (e talvez você desconecte ou feche a janela do terminal!)

por exemplo,

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

Aqui estou em ksh (meu shell normal). Eu começo bash e, em seguida, dentro de bash eu exec /bin/echo . Podemos ver que fui devolvido para ksh porque o processo bash foi substituído por /bin/echo .

    
por 19.07.2016 / 14:50
7

TL; DR

exec é usado para gerar novos processos e manipular redirecionamentos de fluxo / descritores de arquivos. eval é usado para avaliar strings como comandos. Ambos podem ser usados para criar e executar um comando com argumentos conhecidos em tempo de execução, mas exec substitui o processo do shell atual além de executar comandos.

exec buil-in

Sintaxe:

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

De acordo com o manual, se houver comando especificado, este

...replaces the shell. No new process is created. The arguments become the arguments to command.

Em outras palavras, se você estivesse executando bash com o PID 1234 e executasse exec top -u root dentro desse shell, o comando top terá PID 1234 e substituirá o processo do shell.

Onde isso é útil? Em algo conhecido como scripts de wrapper. Esses scripts criam conjuntos de argumentos ou tomam certas decisões sobre quais variáveis passar para o ambiente e, em seguida, usam exec para substituir a si mesmo com qualquer comando especificado e, é claro, fornecendo os mesmos argumentos que o script wrapper criou ao longo do maneira.

O que o manual também afirma é que:

If command is not specified, any redirections take effect in the current shell

Isso nos permite redirecionar qualquer coisa dos fluxos de saída das shells atuais para um arquivo. Isso pode ser útil para fins de registro ou filtragem, em que você não deseja ver stdout de comandos, mas apenas stderr . Por exemplo, assim:

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

Esse comportamento torna-se útil para efetuar login em scripts de shell , redirecionando fluxos para arquivos separados ou processos e outras coisas divertidas com descritores de arquivos.

No nível do código-fonte, pelo menos para bash versão 4.3, o exec incorporado é definido em builtins/exec.def . Ele analisa os comandos recebidos e, se houver, passa as coisas para shell_execve() function definida no arquivo execute_cmd.c .

Para encurtar a história, existe uma família de comandos exec na linguagem de programação C e shell_execve() é basicamente uma função de invólucro de execve :

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

embutido no eval

Os estados do manual do bash 4.3 (ênfase adicionada por mim):

The args are read and concatenated together into a single command. This command is then read and executed by the shell, and its exit status is returned as the value of eval.

Observe que não há substituição de processo. Ao contrário de exec , em que o objetivo é simular a funcionalidade execve() , o eval incorporado serve apenas para "avaliar" argumentos, como se o usuário os tivesse digitado na linha de comando. Assim, novos processos são criados.

Onde isso pode ser útil? Como Gilles apontou nesta resposta , "... eval não é usado com muita freqüência. Em alguns shells, o mais comum use é obter o valor de uma variável cujo nome não é conhecido até o tempo de execução ". Pessoalmente, eu usei isso em alguns scripts no Ubuntu, onde era necessário executar / avaliar um comando baseado no espaço de trabalho específico que o usuário estava usando atualmente.

No nível do código-fonte, ele é definido em builtins/eval.def e passa a string de entrada analisada para a função evalstring() .

Entre outras coisas, eval pode atribuir variáveis que permanecem no ambiente de execução atual do shell, enquanto exec não pode:

$ eval x=42
$ echo $x
42
$ exec x=42
bash: exec: x=42: not found
    
por 20.05.2017 / 13:22
5
creating a new child process, run the arguments and returning the exit status.

Uh o que? O ponto inteiro de eval é que isso não cria, de maneira alguma, um processo filho. Se eu fizer

eval "cd /tmp"

em um shell, depois o shell atual terá o diretório alterado. O exec não cria um novo processo filho, em vez disso, ele altera o executável atual (ou seja, o shell) para o dado; o ID do processo (e arquivos abertos e outras coisas) permanece o mesmo. Ao contrário de eval , um exec não retornará ao shell de chamada, a menos que o exec falhe por não conseguir localizar ou carregar o executável ou morrer para problemas de expansão de argumento.

eval interpreta basicamente seu (s) argumento (s) como uma string após a concatenação, ou seja, ele fará uma camada extra de expansão de curingas e divisão de argumentos. exec não faz nada disso.

    
por 19.07.2016 / 19:51