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

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 ...и они работают? Что мне не хватает?

sdaau
источник
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 07
Дополнение от @ markus-kuhn сложно переоценить.
Alex Che
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

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

Шаги переводчика

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

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

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

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

Резюме

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

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

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

Тодд А. Джейкобс
источник
Я уже проголосовал за это, но я только что вернулся к этому вопросу, и этот пост содержит именно те указатели, которые мне нужны; большое спасибо, @CodeGnome!
sdaau
Я не знаю, изменился ли Bash в этой области с момента публикации этого ответа, но назначение переменных с префиксом теперь работает со встроенными командами. Например, FOO=foo eval 'echo $FOO'печатает fooкак положено. Это означает, что вы можете делать такие вещи, как IFS="..." read ....
Will Vousden
Я думаю, что происходит то, что Bash на самом деле временно изменяет свою собственную среду и восстанавливает ее после завершения команды, что может иметь странные побочные эффекты.
Will Vousden
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')"
Маркус Кун
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

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

Кирос
источник
1
Это работает, но не совсем в этом суть. Идея состоит в том, чтобы настроить среду только для одной команды, а не постоянно, как ваше решение.
Джонатан Леффлер,
Спасибо за это @Kyros; не знаю, почему я пропустил это к настоящему времени :) Все еще блуждаю, как LD_PRELOADи что-то может работать перед исполняемым файлом без точки с запятой, хотя ... Еще раз большое спасибо - ура!
sdaau 07
@JonathanLeffler - действительно, это была идея; Я не понимал, что точка с запятой делает изменение постоянным - спасибо, что заметили это!
sdaau 07
1

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

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