Qual é a diferença entre eval e exec?

Respostas:

124

evale execsão bestas completamente diferentes. (Além do fato de que ambos executarão comandos, tudo o que você faz em um shell também).

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

O que exec cmdfaz é exatamente o mesmo que apenas executar cmd, exceto que o shell atual é substituído pelo comando, em vez de um processo separado estar sendo executado. Internamente, executar o say /bin/lschamará fork()para criar um processo filho e, exec()em seguida, executar o filho /bin/ls. exec /bin/lspor outro lado, não bifurca, mas apenas substitui a concha.

Comparar:

$ 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 a listagem /proc/selfnos fornece o PID do lsque foi executado a partir do shell. Geralmente, os IDs de processo são diferentes, mas com execo shell e lstêm o mesmo ID de processo. Além disso, o comando a seguir execnão foi executado, pois o shell foi substituído.


Por outro lado:

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

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

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

Como não criará um processo filho, a variável é definida no shell atual. (É claro eval /bin/lsque criará um processo filho, da mesma maneira que um velho comum /bin/lsfaria.)

Ou podemos ter um comando que produz comandos shell. A execução ssh-agentinicia o agente em segundo plano e gera várias atribuições de variáveis, que podem ser definidas no shell atual e usadas pelos processos filhos (os sshcomandos que você executaria). Portanto, ssh-agentpode ser iniciado com:

eval $(ssh-agent)

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


Obviamente, se a variável cmdcontiver algo como rm -rf $HOME, executar eval "$cmd"não seria algo que você gostaria de fazer. Mesmo coisas como substituições de comando dentro da corda seria processado, por isso deve-se realmente ter certeza de que a entrada para evalé seguro antes de o utilizar.

Muitas vezes, é possível evitar evale evitar a mistura acidental de código e dados da maneira errada.

ilkkachu
fonte
Essa é uma ótima resposta, @ilkkachu. Obrigado!
Willian Paixao
Mais detalhes sobre o uso de eval podem ser encontrados aqui: stackoverflow.com/a/46944004/2079103
clearlight
1
@ claro, bem, isso me lembra de acrescentar o aviso de isenção habitual sobre não usar evalem primeiro lugar a esta resposta também. Coisas como variáveis ​​de modificação indireta podem ser feitas em muitos shells através de declare/ typeset/ namerefe expansões como ${!var}, então eu os usaria em vez de, a evalmenos que realmente tivesse que evitá-lo.
Ilkkachu
27

execnão cria um novo processo. Ele substitui o processo atual pelo novo comando. Se você fez isso na linha de comando, ele efetivamente encerrará sua sessão do shell (e talvez você faça logout ou feche a janela do terminal!)

por exemplo

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

Aqui estou ksh(minha concha normal). Eu começo bashe depois dentro do bash exec /bin/echo. Podemos ver que fui devolvido kshdepois porque o bashprocesso foi substituído por /bin/echo.

Stephen Harris
fonte
hum no rosto caiu de volta no processo ksh b / c foi substituído por eco não faz tanto sentido?
14

TL; DR

execé usado para substituir o processo atual do shell por novos e manipular descritores de redirecionamento / arquivo de fluxo, se nenhum comando foi especificado. evalé usado para avaliar seqüências de caracteres como comandos. Ambos podem ser usados ​​para criar e executar um comando com argumentos conhecidos em tempo de execução, mas execsubstituem 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 um comando especificado, este built-in

... substitui o shell. Nenhum novo processo é criado. Os argumentos se tornam os argumentos a serem comandados.

Em outras palavras, se você estava executando bashcom o PID 1234 e se executava exec top -u rootnesse shell, o topcomando terá o PID 1234 e substituirá o processo do shell.

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

O que o manual também afirma é que:

Se o comando não for especificado, qualquer redirecionamento entrará em vigor no shell atual

Isso nos permite redirecionar qualquer coisa dos fluxos de saída dos shells atuais para um arquivo. Isso pode ser útil para fins de registro ou filtragem, onde você não deseja ver stdoutapenas os comandos 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 o torna útil para fazer logon em scripts de shell , redirecionar fluxos para separar arquivos ou processos e outras coisas divertidas com descritores de arquivo.

No nível do código-fonte, pelo menos para a bashversão 4.3, o execinterno é definido em builtins/exec.def. Ele analisa os comandos recebidos e, se houver algum, passa as coisas para a shell_execve()função definida no execute_cmd.carquivo.

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

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

avaliação interna

O manual do bash 4.3 afirma (ênfase adicionada por mim):

Os argumentos são lidos e concatenados juntos em um único comando. Este comando é então lido e executado pelo shell e seu status de saída é retornado como o valor de eval.

Observe que não há substituição do processo. Diferentemente de execonde o objetivo é simular a execve()funcionalidade, o evalbuilt-in serve apenas para "avaliar" argumentos, como se o usuário os tivesse digitado na linha de comando. Como tal, novos processos são criados.

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

No nível do código-fonte, ele é definido builtins/eval.defe passa a sequência de entrada analisada para evalstring()funcionar.

Entre outras coisas, evalpode atribuir variáveis que permanecem no ambiente de execução de shell atual, enquanto execnão pode:

$ eval x=42
$ echo $x
42
$ exec x=42
bash: exec: x=42: not found
Sergiy Kolodyazhnyy
fonte
@ ctrl-alt-delor Eu editei essa parte, obrigado, embora, sem dúvida, tenha um novo processo, o PID permanece o mesmo que o shell atual. No futuro, considere editar as respostas, em vez de deixar um comentário e voto negativo, especialmente quando algo menor como a frase de 7 palavras estiver em questão; consome muito menos tempo editar e corrigir uma resposta e é muito mais útil.
Sergiy Kolodyazhnyy
5
criando um novo processo filho, execute os argumentos e retorne o status de saída.

Uh o quê? O ponto principal evalé que ele não cria de forma alguma um processo filho. Se eu fizer

eval "cd /tmp"

em um shell, depois o shell atual terá mudado de diretório. Nem execcria um novo processo filho; em vez disso, altera o executável atual (ou seja, o shell) para o determinado; a identificação do processo (e arquivos abertos e outras coisas) permanecem os mesmos. Ao contrário eval, um execnão retornará ao shell de chamada, a menos que o execpróprio falhe devido à incapacidade de encontrar ou carregar o executável ou morrer devido a problemas de expansão de argumento.

evalbasicamente interpreta seus argumentos como uma sequência após concatenação, ou seja, ele fará uma camada extra de expansão de curinga e divisão de argumentos. execnão faz nada assim.

user180452
fonte
1
A pergunta original lida "eval e exec são comandos internos do Bash (1) e executa comandos, criando um novo processo filho, executando os argumentos e retornando o status de saída"; Eu editei a falsa presunção.
Charles Stewart
-1

Avaliação

Estes trabalhos:

$ echo hi
hi

$ eval "echo hi"
hi

$ exec echo hi
hi

No entanto, estes não:

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

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

Substituição da imagem do processo

Este exemplo demonstra como execsubstitui a imagem do seu processo de chamada:

# 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

Observe que foi exec echo $$executado com o PID do subshell! Além disso, depois de concluído, estávamos de volta ao nosso sh$shell original .

Por outro lado, evalse não substituir a imagem do processo. Em vez disso, ele executa o comando fornecido, como faria normalmente dentro do próprio shell. (Obviamente, se você executar um comando que exige que um processo seja gerado ... isso é exatamente o que acontece!)

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
fonte
Eu postei esta resposta, porque os outros exemplos realmente não ilustrar isso de uma maneira suficientemente compreensível para mentes fracas, como I.
Mateen Ulhaq
Má escolha de palavras: substituição de processo é um conceito existente que parece não ter relação com o que você está ilustrando aqui.
muru 29/07
@muru A "substituição de processo" é melhor? Não sei qual é a terminologia correta. A página de manual parece chamá-lo de "substituir a imagem do processo".
Mateen Ulhaq 29/07
Hmm, não sei. (Mas quando eu ouvi "substituindo a imagem do processo", eu penso: exec)
Muru