Змінна як команда; eval vs bash -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

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. Це приблизно те саме. Наведення курсора evalшвидше для запуску виконуваних файлів. Ключова відмінність тут полягає в тому, evalщо не запускається нова оболонка (вона виконує команду в поточній), тоді як bash -cзапускається нова оболонка, а потім виконується команда в новій оболонці. Запуск нової оболонки потребує часу, і тому bash -cце повільніше, ніж eval.


Я думаю, що ОП хоче порівняти bash -cз evalне exec.
Джозеф Р.

@JosephR. На жаль! Я це зміню.
PlasmaPower

1
@JosephR. Це слід виправити зараз. Крім того, я переробив тести трохи більше і bash -cНЕ що погано ...
PlasmaPower

3
Хоча це правда, вона не вистачає принципової різниці в тому, що команда виконується в різних середовищах. Очевидно, що запуск нового екземпляра bash пройде повільніше, це не цікаве спостереження.
Жиль "ТАК - перестань бути злим"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.