Переменная как команда; eval vs bash -c

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

bash -c "$1" 

вместо

 eval "$1" 

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

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

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

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

  • cd /starting/directory; foo=/somewhere/else; eval "$1"; pwd cd /starting/directory; foo=/somewhere/else; eval "$1"; pwd перечисляет содержимое /somewhere/else и prints /somewhere/else .
  • cd /starting/directory; foo=/somewhere/else; (eval "$1"); pwd cd /starting/directory; foo=/somewhere/else; (eval "$1"); pwd отображает содержимое /somewhere/else и prints /starting/directory .
  • cd /starting/directory; foo=/somewhere/else; bash -c "$1"; pwd cd /starting/directory; foo=/somewhere/else; bash -c "$1"; pwd перечисляет содержимое /starting/directory (потому что cd "" не меняет текущий каталог) и prints /starting/directory .

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

 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) если вы хотите защитить свою среду.

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

 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 

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

 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 быстрее. На странице man eval :

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

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