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
а потім всередині баш я 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;
{
Базовий 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
, 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
)