В чем разница между eval и exec?


81

evalи execоба встроены в команды bash (1), которые выполняют команды.

Я также вижу, execесть несколько вариантов, но это единственная разница? Что происходит с их контекстом?



Ответы:


124

evalи execсовершенно разные звери. (Помимо того, что оба будут запускать команды, но так же, как и все, что вы делаете в оболочке.)

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

То exec cmdже самое, что и просто запуск cmd, за исключением того, что текущая оболочка заменяется командой, а не отдельным запущенным процессом. Внутри работаю слово /bin/lsбудет вызывать , fork()чтобы создать дочерний процесс, а затем exec()в ребенке выполнить /bin/ls. exec /bin/lsс другой стороны не будет вилка, а просто заменит оболочку.

Для сравнения:

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

с участием

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

echo $$печатает PID оболочки, которую я запустил, а листинг /proc/selfдает нам PID того, lsчто запускалось из оболочки. Обычно идентификаторы процессов разные, но с execоболочкой и lsимеют одинаковые идентификаторы процессов. Кроме того, следующая команда execне выполнялась, поскольку оболочка была заменена.


С другой стороны:

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

evalбудет запускать аргументы как команду в текущей оболочке. Другими словами eval foo barэто то же самое, что и просто foo bar. Но переменные будут расширены перед выполнением, поэтому мы можем выполнять команды, сохраненные в переменных оболочки:

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

Он не создаст дочерний процесс, поэтому переменная установлена ​​в текущей оболочке. (Конечно eval /bin/ls, создаст дочерний процесс, так же, как обычный старый /bin/ls.)

Или у нас может быть команда, которая выводит команды оболочки. Запуск ssh-agentзапускает агент в фоновом режиме и выводит набор переменных, которые могут быть установлены в текущей оболочке и использоваться дочерними процессами ( sshкоманды, которые вы будете запускать). Следовательно, ssh-agentможно начать с:

eval $(ssh-agent)

И текущая оболочка получит переменные для наследования другими командами.


Конечно, если переменная cmdсодержит нечто подобное rm -rf $HOME, то запуск eval "$cmd"не будет чем-то, что вы хотели бы сделать. Даже такие вещи, как подстановка команд внутри строки, будут обработаны, поэтому нужно действительно быть уверенным, что входные данные evalбезопасны перед их использованием.

Часто можно избежать evalи избежать даже случайного смешения кода и данных неправильным образом.


Это отличный ответ, @ilkkachu. Спасибо!
Виллиан Пайшао

Более подробную информацию об использовании eval можно найти здесь: stackoverflow.com/a/46944004/2079103
clearlight

1
@clearlight, ну, это напоминает мне , чтобы добавить обычный отказ не используется evalв первую очередь для этого ответа тоже. Такие вещи, как косвенное изменение переменных, могут выполняться во многих оболочках через declare/ typeset/ namerefи подобные расширения ${!var}, поэтому я бы использовал их вместо тех evalслучаев, когда мне действительно не пришлось этого избегать.
ilkkachu

27

execне создает новый процесс. Он заменяет текущий процесс новой командой. Если вы сделали это в командной строке, то это эффективно завершит сеанс оболочки (и, возможно, выйдет из системы или закроет окно терминала!)

например

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

Вот и я ksh(моя нормальная оболочка). Я начинаю , bashа затем внутри Баш I exec /bin/echo. Мы можем видеть, что меня снова забросили, kshпотому что bashпроцесс был заменен на /bin/echo.


ммм на лице упал обратно в ksh b / c процесс был заменен на эхо не имеет такого большого смысла?

14

TL; DR

execиспользуется для замены текущего процесса оболочки новым и обработки перенаправления потока / файловых дескрипторов, если команда не указана. evalиспользуется для оценки строк как команд. Оба могут использоваться для построения и выполнения команды с аргументами, известными во время выполнения, но execзаменяют процесс текущей оболочки в дополнение к выполнению команд.

встроенный exec

Синтаксис:

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

Согласно инструкции, если есть команда, указанная эта встроенная

... заменяет оболочку. Новый процесс не создан. Аргументы становятся аргументами команды.

Другими словами, если вы работали bashс PID 1234 и если вы должны были работать exec top -u rootв этой оболочке, topкоманда получит PID 1234 и заменит процесс вашей оболочки.

Где это полезно? В чем-то, известном как сценарии оболочки. Такие сценарии создают наборы аргументов или принимают определенные решения о том, какие переменные передавать в среду, а затем используют execдля замены себя любой указанной командой и, конечно, предоставляют те же аргументы, которые создавал сценарий оболочки на этом пути.

В руководстве также говорится, что:

Если команда не указана, любые перенаправления вступают в силу в текущей оболочке

Это позволяет нам перенаправить что-либо из текущих потоков вывода оболочек в файл. Это может быть полезно для целей регистрации или фильтрации, где вы не хотите видеть stdoutкоманды, а только stderr. Например, вот так:

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

Такое поведение делает его удобным для входа в сценарии оболочки , перенаправления потоков в отдельные файлы или процессы и других забавных вещей с файловыми дескрипторами.

На уровне исходного кода, по крайней мере для bashверсии 4.3, execвстроенный определяется в builtins/exec.def. Он анализирует полученные команды и, если они есть, передает данные в shell_execve()функцию, определенную в execute_cmd.cфайле.

Короче говоря, на execязыке программирования C существует семейство команд, и shell_execve()в основном это функция-обертка execve:

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

встроенный Eval

Руководство bash 4.3 гласит (выделено мной):

Аргументы считываются и объединяются в одну команду. Эта команда затем читается и выполняется оболочкой , и ее состояние выхода возвращается как значение eval.

Обратите внимание, что не происходит замена процесса. В отличие от того, execгде цель состоит в том, чтобы симулировать execve()функциональность, evalвстроенная функция служит только для «оценки» аргументов, как если бы пользователь вводил их в командной строке. Таким образом, новые процессы создаются.

Где это может быть полезно? Как отметил Жиль в этом ответе , «... eval используется не очень часто. В некоторых оболочках наиболее распространенным способом является получение значения переменной, имя которой неизвестно до времени выполнения». Лично я использовал это в нескольких сценариях в Ubuntu, где было необходимо выполнить / оценить команду, основанную на конкретной рабочей области, которую пользователь использовал в настоящее время.

На уровне исходного кода он определяется builtins/eval.defи передает обработанную входную строку в evalstring()функцию.

Помимо прочего, evalможет назначать переменные, которые остаются в текущей среде выполнения оболочки, но execне могут:

$ eval x=42
$ echo $x
42
$ exec x=42
bash: exec: x=42: not found

@ ctrl-alt-delor Я редактировал эту часть, спасибо, хотя, возможно, это порождает новый процесс, PID просто остается таким же, как и в текущей оболочке. В будущем рассмотрите возможность редактирования ответов, вместо того, чтобы оставлять комментарий и понижать голос, особенно если речь идет о такой незначительной вещи, как фраза из 7 слов; редактирование и исправление ответа занимает гораздо меньше времени, и это гораздо более полезно.
Сергей Колодяжный

5
создание нового дочернего процесса, запуск аргументов и возвращение статуса выхода.

А что? Весь смысл в evalтом, что он никоим образом не создает дочерний процесс. Если я сделаю

eval "cd /tmp"

в оболочке, то после этого текущая оболочка сменит каталог. Ни один из них не execсоздает новый дочерний процесс, вместо этого он изменяет текущий исполняемый файл (а именно оболочку) для данного; идентификатор процесса (и открытые файлы и другие вещи) остаются прежними. В противоположность этому eval, an execне будет возвращаться в вызывающую оболочку, если execсам не произойдет сбой из-за невозможности найти или загрузить исполняемый файл или умереть от проблем расширения аргументов.

evalв основном интерпретирует свой аргумент (аргументы) как строку после конкатенации, а именно он будет выполнять дополнительный уровень подстановочных знаков и разделения аргументов. execничего такого не делает.


1
Исходный вопрос гласил: «eval и exec являются встроенными командами Bash (1) и выполняют команды, создавая новый дочерний процесс, запускают аргументы и возвращают состояние выхода»; Я отредактировал ложное предположение.
Чарльз Стюарт

-1

оценка

Эти работы:

$ echo hi
hi

$ eval "echo hi"
hi

$ exec echo hi
hi

Однако это не так:

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

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

Замена образа процесса

Этот пример демонстрирует, как execзаменяет изображение своего вызывающего процесса:

# 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

Обратите внимание, что exec echo $$работает с PID подоболочки! Кроме того, после того, как это было завершено, мы вернулись в нашей первоначальной sh$оболочке.

С другой стороны, evalэто не заменит образа процесса. Скорее, он запускает данную команду, как обычно в самой оболочке. (Конечно, если вы запускаете команду, которая требует порождения процесса ... она делает это!)

sh$ echo $$
1234

sh$ sh

sh-subshell$ echo $$
5678

sh-subshell$ eval echo $$
5678

# We are still in the subshell!
sh-subshell$ echo $$
5678

Я опубликовал этот ответ, потому что другие примеры на самом деле не иллюстрировали это в достаточно понятной форме для слабых умов, таких как я.
Матин Улхак

Плохой выбор слов: замена процесса - это существующая концепция, которая, кажется, не связана с тем, что вы здесь иллюстрируете.
Муру

@muru "Замена процесса" лучше? Я не уверен, какова правильная терминология. Кажется, что справочная страница называет это «заменой образа процесса».
Матин Улхак

Хм, не знаю. (Но когда я слышу «замена образа процесса», я думаю: exec)
Муру
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.