Переменная как команда; Эвал против Баш-C

41

Я читал сценарий bash, который кто-то сделал, и заметил, что автор не использует eval для оценки переменной как команды
. Автор использовал

bash -c "$1"

вместо того

eval "$1"

Я предполагаю, что использование eval является предпочтительным методом, и в любом случае он, вероятно, быстрее. Это правда?
Есть ли практическая разница между ними? Каковы заметные различия между этими двумя?

кто я
источник
В некоторых случаях вы можете уйти без них. e='echo foo'; $eработает просто отлично.
Деннис

Ответы:

40

eval "$1"выполняет команду в текущем скрипте. Он может устанавливать и использовать переменные оболочки из текущего скрипта, устанавливать переменные среды для текущего скрипта, устанавливать и использовать функции из текущего скрипта, устанавливать текущий каталог, umask, пределы и другие атрибуты для текущего скрипта и так далее. bash -c "$1"выполняет команду в совершенно отдельном сценарии, который наследует переменные среды, дескрипторы файлов и другую среду процесса (но не передает обратно никаких изменений), но не наследует внутренние параметры оболочки (переменные оболочки, функции, параметры, прерывания и т. д.).

Есть другой способ, (eval "$1")который выполняет команду в подоболочке: он наследует все от вызывающего скрипта, но не передает никаких изменений обратно.

Например, если предположить, что переменная dirне экспортирована и $1есть cd "$foo"; ls, то:

  • cd /starting/directory; foo=/somewhere/else; eval "$1"; pwdперечисляет содержание /somewhere/elseи печатает /somewhere/else.
  • cd /starting/directory; foo=/somewhere/else; (eval "$1"); pwdперечисляет содержание /somewhere/elseи печатает /starting/directory.
  • cd /starting/directory; foo=/somewhere/else; bash -c "$1"; pwdперечисляет содержимое /starting/directory(потому cd ""что не меняет текущий каталог) и печатает /starting/directory.
Жиль "ТАК - перестань быть злым"
источник
Спасибо. Я не знал о (eval "$ 1"), это отличается от источника?
whoami
1
@whoami не (eval "$1")имеет ничего общего с source. Это просто сочетание (…)и eval. source fooпримерно эквивалентно eval "$(cat foo)".
Жиль "ТАК - перестань быть злым"
Должно быть, мы писали наши ответы одновременно ...
mikeserv
@whoami Основное различие между evalи .dotзаключается в том, что он evalработает с аргументами и .dotработает с файлами.
mikeserv
Спасибо вам обоим. Мой предыдущий комментарий кажется немного глупым, когда я снова его читаю ...
whoami
23

Самое важное различие между

bash -c "$1" 

А также

eval "$1"

Это первый работает в подоболочке, а второй нет. Так:

set -- 'var=something' 
bash -c "$1"
echo "$var"

ВЫХОД:

#there doesn't seem to be anything here
set -- 'var=something' 
eval "$1"
echo "$var"

ВЫХОД:

something

Я понятия не имею, почему кто-то когда-либо использовал исполняемый файл bashтаким образом. Если вы должны вызвать его, используйте гарантированно встроенный POSIX sh. Или (subshell eval)если вы хотите защитить свою среду.

Лично я предпочитаю оболочку .dotпревыше всего.

printf 'var=something%d ; echo "$var"\n' `seq 1 5` | . /dev/fd/0

ВЫХОД

something1
something2
something3
something4
something5

НО ЭТО ВАМ НУЖНО?

Единственная причина, по которой можно использовать либо, действительно, в том случае, если ваша переменная фактически назначает или оценивает другую, или разделение слов важно для вывода.

Например:

var='echo this is var' ; $var

ВЫХОД:

this is var

Это работает, но только потому, echoчто не заботится о количестве аргументов.

var='echo "this is var"' ; $var

ВЫХОД:

"this is var"

Видеть? Двойные кавычки приходят, потому что результат расширения оболочки $varне оценивается для quote-removal.

var='printf %s\\n "this is var"' ; $var

ВЫХОД:

"this
is
var"

Но с evalили sh:

    var='echo "this is var"' ; eval "$var" ; sh -c "$var"

ВЫХОД:

this is var
this is var

Когда мы используем evalили, shоболочка берет второй проход в результатах расширений и оценивает их как потенциальную команду, и поэтому кавычки имеют значение. Вы также можете сделать:

. <<VAR /dev/fd/0
    ${var:=echo "this is var"}
#END
VAR

ВЫХОД

this is var
mikeserv
источник
5

Я сделал быстрый тест:

time bash -c 'for i in {1..10000}; do bash -c "/bin/echo hi"; done'
time bash -c 'for i in {1..10000}; eval "/bin/echo hi"; done'

(Да, я знаю, я использовал bash -c для выполнения цикла, но это не должно иметь никакого значения).

Результаты:

eval    : 1.17s
bash -c : 7.15s

Так evalбыстрее. Со страницы руководства eval:

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

bash -cконечно, выполняет команду в оболочке bash. Одно замечание: я использовал, /bin/echoпотому что echoэто встроенная оболочка bash, что означает, что новый процесс запускать не нужно. Замена /bin/echoс echoдля bash -cтестирования, потребовалось 1.28s. Это примерно то же самое. Hovever, evalбыстрее для запуска исполняемых файлов. Ключевым отличием здесь является то, evalчто не запускается новая оболочка (она выполняет команду в текущей), тогда как bash -cзапускается новая оболочка, а затем выполняется команда в новой оболочке. Запуск новой оболочки требует времени, и поэтому bash -cмедленнее, чем eval.

PlasmaPower
источник
Я думаю, что ОП хочет сравнить bash -cс evalне exec.
Джозеф Р.
@JosephR. К сожалению! Я изменю это.
PlasmaPower
1
@JosephR. Это должно быть исправлено сейчас. Кроме того, я переделал тесты немного больше и bash -cне что плохо ...
PlasmaPower
3
Хотя это и правда, в нем отсутствует принципиальное различие в том, что команда выполняется в разных средах. Очевидно, что запуск нового экземпляра bash будет медленнее, это не интересное наблюдение.
Жиль "ТАК - перестань быть злым"