Чому я не можу вказати змінну середовища та повторити її в тому ж командному рядку?


91

Розглянемо цей фрагмент:

$ SOMEVAR=AAA
$ echo zzz $SOMEVAR zzz
zzz AAA zzz

Тут я встановив $SOMEVARдля AAAпершого рядка - і коли я повторюю це для другого рядка, я отримую AAAвміст, як очікувалося.

Але тоді, якщо я спробую вказати змінну в тому ж командному рядку, що і echo:

$ SOMEVAR=BBB echo zzz $SOMEVAR zzz
zzz AAA zzz

... Я отримую не так, BBBяк я очікував - я отримую старе значення ( AAA).

Це так повинно бути? Якщо так, то як так, тоді ви можете вказати такі змінні, як LD_PRELOAD=/... program args ...і зробити це? Чого мені не вистачає?


2
Це працює, коли ви робите присвоєння окремим оператором або під час виклику сценарію із власним середовищем, але не під час попередньої команди в поточному середовищі. Цікаво!
Тодд А. Джейкобс

1
Причина LD_PRELOADробота є те , що змінна встановлена в програмах навколишнього середовища - НЕ в його командному рядку.
Призупинено до подальшого повідомлення.

Відповіді:


103

Те, що ви бачите, це очікувана поведінка. Проблема полягає в тому, що батьківська оболонка виконує оцінку $SOMEVARв командному рядку перед тим, як викликати команду із модифікованим середовищем. Вам потрібно отримати оцінку $SOMEVARвідкладеного до встановлення середовища.

Ваші найближчі варіанти включають:

  1. SOMEVAR=BBB eval echo zzz '$SOMEVAR' zzz.
  2. SOMEVAR=BBB sh -c 'echo zzz $SOMEVAR zzz'.

Обидва вони використовують одинарні лапки, щоб запобігти оцінці батьківської оболонки $SOMEVAR; він обчислюється лише після встановлення в середовищі (тимчасово, на час дії однієї команди).

Інший варіант - використовувати нотацію оболонки (як це також запропонував Маркус Кун у своїй відповіді ):

(SOMEVAR=BBB; echo zzz $SOMEVAR zzz)

Змінна встановлюється лише в допоміжній оболонці


Чудово, @JonathanLeffler - велике спасибі за пояснення; ура!
sdaau

Додаток від @ markus-kuhn важко переоцінити.
Че

37

Проблема, переглянута

Чесно кажучи, посібник з цього приводу бентежить. У посібнику GNU Bash сказано:

Середовище для будь-якої простої команди або функції [зауважте, що це виключає вбудовані файли] може тимчасово доповнюватись, додаючи до нього призначення параметрів, як описано в Параметри оболонки. Ці оператори присвоєння впливають лише на оточення, яке бачить ця команда.

Якщо ви дійсно аналізуєте речення, там йдеться про те, що середовище для команди / функції змінено, але не середовище для батьківського процесу. Отже, це буде працювати:

$ TESTVAR=bbb env | fgrep TESTVAR
TESTVAR=bbb

оскільки середовище для команди env було змінено до її виконання. Однак це не спрацює:

$ set -x; TESTVAR=bbb echo aaa $TESTVAR ccc
+ TESTVAR=bbb
+ echo aaa ccc
aaa ccc

через те, коли розширення параметра виконується оболонкою.

Кроки перекладача

Інша частина проблеми полягає в тому, що Баш визначає ці кроки для свого інтерпретатора:

  1. Зчитує свої вхідні дані з файлу (див. Сценарії оболонки), з рядка, що подається як аргумент до параметра -c виклику (див. Виклик Bash), або з терміналу користувача.
  2. Розбиває введення на слова та оператори, дотримуючись правил цитування, описаних у цитуванні. Ці лексеми розділені метасимволами. Розширення псевдонімів виконується цим кроком (див. Псевдоніми).
  3. Розбирає маркери на прості та складені команди (див. Команди оболонки).
  4. Виконує різні розширення оболонки (див. Розширення оболонки), розбиваючи розгорнуті маркери на списки імен файлів (див. Розширення імен файлів) та команд та аргументів.
  5. Виконує всі необхідні переспрямування (див. Перенаправлення) та видаляє оператори переспрямування та їх операнди зі списку аргументів.
  6. Виконує команду (див. Виконання команд).
  7. За бажанням чекає завершення команди та збирає статус її виходу (див. Стан виходу).

Тут відбувається те, що вбудовані не отримують власного середовища виконання, тому вони ніколи не бачать модифікованого середовища. Крім того, прості команди (наприклад , / bin / відлуння) дійсно отримати модифіковану ennvironment (тому приклад окр працював) , але розширення оболонки відбувається в поточній середовищі на етапі # 4.

Іншими словами, ви не передаєте 'aaa $ TESTVAR ccc' в / bin / echo; ви передаєте інтерпольований рядок (розширений у поточному середовищі) до / bin / echo. У цьому випадку, оскільки в поточному середовищі немає TESTVAR , ви просто передаєте команду 'aaa ccc'.

Резюме

Документація може бути набагато чіткішою. Добре, що є переповнення стека!

Дивіться також

http://www.gnu.org/software/bash/manual/bashref.html#Command-Execution-Environment


Я вже проголосував за це - але я щойно повернувся до цього питання, і цей пост містить саме ті вказівки, які мені потрібні; велике спасибі, @CodeGnome!
sdaau

Я не знаю, чи змінився Bash у цій області з моменту розміщення цієї відповіді, але префіксовані призначення змінних зараз працюють із вбудованими. Наприклад, FOO=foo eval 'echo $FOO'друкує, fooяк очікувалося. Це означає, що ви можете робити такі речі IFS="..." read ....
Уіл Вусден,

Я думаю, що відбувається в тому, що Bash насправді тимчасово модифікує власне середовище і відновлює його після завершення команди, що може мати дивні побічні ефекти.
Уіл Вусден,

22

Щоб досягти бажаного, використовуйте

( SOMEVAR=BBB; echo zzz $SOMEVAR zzz )

Причина:

  • Ви повинні відокремити присвоєння крапкою з комою або новим рядком від наступної команди, інакше воно не виконується до розширення параметра для наступної команди (ехо).

  • Вам потрібно зробити призначення всередині середовища підшелупки , щоб переконатися, що воно не зберігається за поточним рядком.

Це рішення коротше, акуратніше та ефективніше, ніж пропонували інші, зокрема воно не створює нового процесу.


3
Для майбутніх гуглерів, які завітають сюди: це, мабуть, найкраща відповідь на це питання. Щоб ще більше ускладнити це, якщо вам потрібно, щоб призначення було доступне в середовищі команди, вам потрібно експортувати його. Під оболонка все ще перешкоджає збереженню призначення. (export SOMEVAR=BBB; python -c "from os import getenv; print getenv('SOMEVAR')")
eaj

@eaj Щоб експортувати змінну оболонки в один зовнішній виклик програми, як у вашому прикладі, просто використовуйтеSOMEVAR=BBB python -c "from os import getenv; print getenv('SOMEVAR')"
Markus Kuhn

10

Причина полягає в тому, що це встановлює змінну середовища для одного рядка. Але, echoне робить розширення, bashробить. Отже, ваша змінна насправді розширюється до виконання команди, хоча SOME_VARвона BBBзнаходиться в контексті команди echo.

Щоб побачити ефект, ви можете зробити щось на зразок:

$ SOME_VAR=BBB bash -c 'echo $SOME_VAR'
BBB

Тут змінна не розширюється, доки дочірній процес не виконується, тому ви бачите оновлене значення. якщо ви SOME_VARIABLEзнову перевірите батьківську оболонку, це все одно AAA, як і слід було очікувати.


1
+1 для правильного пояснення, чому це не працює, як написано, та для життєздатного обхідного шляху.
Джонатан Леффлер,

1
SOMEVAR=BBB; echo zzz $SOMEVAR zzz

Використовуйте a; відокремити твердження, що знаходяться в одному рядку.


1
Це працює, але справа не зовсім у цьому. Ідея полягає в тому, щоб встановити середовище лише для однієї команди, а не постійно, як це робить ваше рішення.
Джонатан Леффлер,

Дякую за це @Kyros; не знаю, як це я до цього часу пропустив :) Все ще блукаючи, як LD_PRELOADі такі можуть працювати перед виконуваним файлом без крапки з комою, правда ... Ще раз велике спасибі - ура
sdaau

@JonathanLeffler - справді, це була ідея; Я не підозрював, що крапка з комою робить зміну постійною - дякую, що зазначили!
sdaau

1

Ось одна альтернатива:

SOMEVAR=BBB && echo zzz $SOMEVAR zzz

Незалежно від того, використовуєте ви &&або ;для розділення команд, призначення зберігається, що не є бажаною поведінкою OP. Маркус Кун має правильну версію цієї відповіді.
eaj
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.