eval
и exec
оба встроены в команды bash (1), которые выполняют команды.
Я также вижу, exec
есть несколько вариантов, но это единственная разница? Что происходит с их контекстом?
eval
и exec
оба встроены в команды bash (1), которые выполняют команды.
Я также вижу, exec
есть несколько вариантов, но это единственная разница? Что происходит с их контекстом?
Ответы:
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
и избежать даже случайного смешения кода и данных неправильным образом.
eval
в первую очередь для этого ответа тоже. Такие вещи, как косвенное изменение переменных, могут выполняться во многих оболочках через declare
/ typeset
/ nameref
и подобные расширения ${!var}
, поэтому я бы использовал их вместо тех eval
случаев, когда мне действительно не пришлось этого избегать.
exec
не создает новый процесс. Он заменяет текущий процесс новой командой. Если вы сделали это в командной строке, то это эффективно завершит сеанс оболочки (и, возможно, выйдет из системы или закроет окно терминала!)
например
ksh% bash
bash-4.2$ exec /bin/echo hello
hello
ksh%
Вот и я ksh
(моя нормальная оболочка). Я начинаю , bash
а затем внутри Баш I exec /bin/echo
. Мы можем видеть, что меня снова забросили, ksh
потому что bash
процесс был заменен на /bin/echo
.
exec
используется для замены текущего процесса оболочки новым и обработки перенаправления потока / файловых дескрипторов, если команда не указана. eval
используется для оценки строк как команд. Оба могут использоваться для построения и выполнения команды с аргументами, известными во время выполнения, но 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;
{
Руководство 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
создание нового дочернего процесса, запуск аргументов и возвращение статуса выхода.
А что? Весь смысл в eval
том, что он никоим образом не создает дочерний процесс. Если я сделаю
eval "cd /tmp"
в оболочке, то после этого текущая оболочка сменит каталог. Ни один из них не exec
создает новый дочерний процесс, вместо этого он изменяет текущий исполняемый файл (а именно оболочку) для данного; идентификатор процесса (и открытые файлы и другие вещи) остаются прежними. В противоположность этому eval
, an exec
не будет возвращаться в вызывающую оболочку, если exec
сам не произойдет сбой из-за невозможности найти или загрузить исполняемый файл или умереть от проблем расширения аргументов.
eval
в основном интерпретирует свой аргумент (аргументы) как строку после конкатенации, а именно он будет выполнять дополнительный уровень подстановочных знаков и разделения аргументов. exec
ничего такого не делает.
оценка
Эти работы:
$ 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
exec
)