eval
e exec
são ambos comandos internos do bash (1) que executam comandos.
Também vejo exec
algumas opções, mas essa é a única diferença? O que acontece com o contexto deles?
eval
e exec
são ambos comandos internos do bash (1) que executam comandos.
Também vejo exec
algumas opções, mas essa é a única diferença? O que acontece com o contexto deles?
Respostas:
eval
e exec
sã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 cmd
faz é 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/ls
chamará fork()
para criar um processo filho e, exec()
em seguida, executar o filho /bin/ls
. exec /bin/ls
por 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/self
nos fornece o PID do ls
que foi executado a partir do shell. Geralmente, os IDs de processo são diferentes, mas com exec
o shell e ls
têm o mesmo ID de processo. Além disso, o comando a seguir exec
não foi executado, pois 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 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/ls
que criará um processo filho, da mesma maneira que um velho comum /bin/ls
faria.)
Ou podemos ter um comando que produz comandos shell. A execução 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 pelos processos filhos (os ssh
comandos 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.
Obviamente, se a variável cmd
contiver 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 eval
e evitar a mistura acidental de código e dados da maneira errada.
eval
em 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
/ nameref
e expansões como ${!var}
, então eu os usaria em vez de, a eval
menos que realmente tivesse que evitá-lo.
exec
nã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 bash
e depois dentro do bash exec /bin/echo
. Podemos ver que fui devolvido ksh
depois porque o bash
processo foi substituído por /bin/echo
.
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 exec
substituem o processo do shell atual, além de executar comandos.
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 bash
com o PID 1234 e se executava exec top -u root
nesse shell, o top
comando 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 exec
para 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 stdout
apenas 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 bash
versão 4.3, o exec
interno é 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.c
arquivo.
Para encurtar a história, existe uma família de exec
comandos 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;
{
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 exec
onde o objetivo é simular a execve()
funcionalidade, o eval
built-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.def
e passa a sequência de entrada analisada para evalstring()
funcionar.
Entre outras coisas, eval
pode atribuir variáveis que permanecem no ambiente de execução de shell atual, enquanto exec
não pode:
$ eval x=42
$ echo $x
42
$ exec x=42
bash: exec: x=42: not found
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 exec
cria 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 exec
não retornará ao shell de chamada, a menos que o exec
próprio falhe devido à incapacidade de encontrar ou carregar o executável ou morrer devido a problemas de expansão de argumento.
eval
basicamente 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. exec
não faz nada assim.
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 exec
substitui 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, eval
se 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
exec
)