Почему я не могу указать переменную среды и повторить ее в той же командной строке?

Рассмотрим этот фрагмент:

$ 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 ..., и заставить их работать? Что мне не хватает?


person sdaau    schedule 07.06.2012    source источник


Ответы (9)


То, что вы видите, является ожидаемым поведением. Проблема в том, что родительская оболочка оценивает $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)

Переменная устанавливается только в подоболочке

person Jonathan Leffler    schedule 07.06.2012

Проблема, новый взгляд

Откровенно говоря, руководство в этом вопросе сбивает с толку. В руководстве по 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

из-за того, когда расширение параметра выполняется оболочкой.

Шаги интерпретатора

Другая часть проблемы заключается в том, что Bash определяет эти шаги для своего интерпретатора:

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

Здесь происходит то, что встроенные функции не имеют собственной среды выполнения, поэтому они никогда не видят модифицированную среду. Кроме того, простые команды (например, /bin/echo) делают получают измененную среду (поэтому пример среды работает), но расширение оболочки происходит в текущей среда на шаге №4.

Другими словами, вы не передаете 'aaa $TESTVAR ccc' в /bin/echo; вы передаете интерполированную строку (расширенную в текущей среде) в /bin/echo. В этом случае, поскольку в текущей среде нет TESTVAR, вы просто передаете команде «aaa ccc».

Резюме

Документация могла бы быть намного понятнее. Хорошо, что есть переполнение стека!

Смотрите также

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

person Todd A. Jacobs    schedule 07.06.2012
comment
Я уже проголосовал за это, но я только что вернулся к этому вопросу, и этот пост содержит именно те указатели, которые мне нужны; большое спасибо, @CodeGnome! - person sdaau; 23.04.2013
comment
Я не знаю, изменился ли Bash в этой области с тех пор, как был опубликован этот ответ, но назначения переменных с префиксом do теперь работают со встроенными функциями. Например, FOO=foo eval 'echo $FOO' печатает foo как и ожидалось. Это означает, что вы можете делать такие вещи, как IFS="..." read .... - person Will Vousden; 19.08.2016
comment
Я думаю, что происходит то, что Bash фактически временно изменяет свою собственную среду и восстанавливает ее после завершения команды, что может иметь странные побочные эффекты. - person Will Vousden; 19.08.2016

Чтобы добиться желаемого, используйте

( SOMEVAR=BBB; echo zzz $SOMEVAR zzz )

Причина:

  • Вы должны отделить назначение точкой с запятой или новой строкой от следующей команды, иначе оно не будет выполнено до того, как произойдет расширение параметра для следующей команды (эхо).

  • Вам нужно выполнить назначение внутри среды подоболочки, чтобы убедиться, что оно не сохраняется за пределами текущей строки.

Это решение короче, аккуратнее и эффективнее, чем некоторые другие предложенные, в частности, оно не создает новый процесс.

person Markus Kuhn    schedule 18.01.2013
comment
Для будущих гуглов, которые окажутся здесь: это, вероятно, лучший ответ на этот вопрос. Чтобы еще больше усложнить, если вам нужно, чтобы назначение было доступно в среде команды, вам нужно экспортировать его. Подоболочка по-прежнему предотвращает сохранение назначения. (export SOMEVAR=BBB; python -c "from os import getenv; print getenv('SOMEVAR')") - person eaj; 26.02.2016
comment
@eaj Чтобы экспортировать переменную оболочки в один вызов внешней программы, как в вашем примере, просто используйте SOMEVAR=BBB python -c "from os import getenv; print getenv('SOMEVAR')" - person Markus Kuhn; 07.12.2019

Причина в том, что это устанавливает переменную среды для одной строки. Но echo не выполняет расширение, а bash делает. Следовательно, ваша переменная фактически расширяется до выполнения команды, хотя SOME_VAR равно BBB в контексте команды echo.

Чтобы увидеть эффект, вы можете сделать что-то вроде:

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

Здесь переменная не раскрывается до тех пор, пока не выполнится дочерний процесс, поэтому вы видите обновленное значение. если вы снова проверите SOME_VARIABLE в родительской оболочке, это все еще будет AAA, как и ожидалось.

person FatalError    schedule 07.06.2012

Чтобы понять, почему это ведет себя не только в bash, но и в любой совместимой оболочке:


2.10.2, Правила грамматики оболочки

Из правила 7(b), охватывающего случаи, когда присваивание предшествует простой команде:

Если все символы, предшествующие '=', образуют допустимое имя (см. том «Базовые определения» стандарта IEEE Std 1003.1-2001, раздел 3.230, «Имя»), должен быть возвращен маркер ASSIGNMENT_WORD. (Символы в кавычках не могут участвовать в формировании допустимого имени.)

[...]

Присвоение NAME должно происходить, как указано в Простых командах.

Таким образом, синтаксический анализ этого назначения требуется для POSIX-совместимой оболочки.


2.9.1, Простые команды

  1. Перенаправления должны выполняться, как описано в Перенаправлении.

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

[...]

Если имя команды не получено, присвоение переменных должно повлиять на текущую среду выполнения. В противном случае назначения переменных должны быть экспортированы для среды выполнения команды и не должны влиять на текущую среду выполнения (за исключением специальных встроенных функций). Если какое-либо из назначений переменных попытается присвоить значение к переменной только для чтения, должна произойти ошибка назначения переменной. См. Последствия ошибок оболочки, чтобы узнать о последствиях этих ошибок.

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


2.12, Среда выполнения оболочки

Утилиты, отличные от специальных встроенных (см. Специальные встроенные утилиты), должны вызываться в отдельной среде, состоящей из следующего. Начальное значение этих объектов должно быть таким же, как и для родительской оболочки, за исключением указанного ниже.

[...]

Переменные с атрибутом экспорта, вместе с теми, которые явно экспортируются на время выполнения команды, должны быть переданы в переменные среды утилиты.


Таким образом: эти переменные расширяются подоболочкой после fork и перед выполнением вызываемой команды и должны - по спецификации - влиять только на дочернюю среду.


Теперь о другом поведении:

SOMEVAR=BBB sh -c 'echo "$SOMEVAR"'

... выигрывает от того, что экземпляр sh создает переменные оболочки из своих переменных среды (как требуется в разделе 2.5.3 спецификации POSIX) при запуске.


Кстати, стоит отметить, что синтаксис, о котором вы спрашиваете, предназначен для присваивания внутри простой команды, а не присваивания внутри вложенной оболочки. Вы можете управлять назначением в подоболочке, участвующей в конвейере, следующим образом:

{ SOMEVAR=BBB; echo "$SOMEVAR"; } | somecommand ...

... который помещает назначение в подоболочку, выполняющую первый компонент конвейера (если ваша оболочка действительно запускает этот компонент в подоболочке, что является неопределенным поведением в отношении POSIX; из спецификации: как расширение , однако любая или все команды в конвейере могут выполняться в текущей среде).

person Charles Duffy    schedule 22.06.2015

Проще говоря, $SOMEVAR оценивается перед вызовом команды, в то время как добавление SOMEVAR=BBB перед командой изменяет среду выполняемой вами команды.

Как сказал Чарльз Даффи, вы можете добавить промежуточный процесс sh, который будет оценивать переменную с похожим синтаксисом, но вы, вероятно, захотите сделать что-то более сложное, и было бы полезно знать, что делать, если у вас все еще есть проблемы с этим.

person Adrien Nader    schedule 22.06.2015

SOMEVAR=BBB; echo zzz $SOMEVAR zzz

Использовать ; для разделения операторов, находящихся на одной строке.

person Kyros    schedule 07.06.2012
comment
Это работает, но это не совсем так. Идея состоит в том, чтобы настроить среду только для одной команды, а не на постоянной основе, как это делает ваше решение. - person Jonathan Leffler; 07.06.2012
comment
Спасибо за это @Kyros; не знаю, как так получилось, что я пропустил это до сих пор :) Все еще блуждаю, как LD_PRELOAD и тому подобное могут работать перед исполняемым файлом без точки с запятой, хотя... Еще раз большое спасибо - ура! - person sdaau; 07.06.2012
comment
@JonathanLeffler - действительно, это была идея; Я не знал, что точка с запятой делает изменение постоянным - спасибо, что заметили это! - person sdaau; 07.06.2012

Вот один из вариантов:

SOMEVAR=BBB && echo zzz $SOMEVAR zzz
person brian    schedule 28.10.2014
comment
Независимо от того, используете ли вы && или ; для разделения команд, назначение сохраняется, что не является желаемым поведением OP. У Маркуса Куна есть правильная версия этого ответа. - person eaj; 26.02.2016

SOMEVAR=BBB echo zzz $SOMEVAR zzz

добавит SOMEVAR=BBB в переменные среды, а затем выполнит echo zzz $SOMEVAR zzz. $SOMEVAR относится к переменной оболочки SOMEVAR, для которой вы заранее установили значение AAA.

Добавление точки с запятой SOMEVAR=BBB; echo zzz $SOMEVAR zzz устанавливает для переменной оболочки значение BBB, а затем выполняет команду после точки с запятой, то есть echo zzz $SOMEVAR zzz, и выдает zzz BBB zzz.

Попробуйте эту команду:

SOMEVAR=BBB env | less

и посмотрите на окружение.

person X Tian    schedule 22.06.2015