Определение переменной с или без экспорта
Что такое export
за?
В чем разница между:
export name=value
а также
name=value
15 ответов
export
делает переменную доступной для подпроцессов.
То есть,
export name=value
означает, что имя переменной доступно любому процессу, который вы запускаете из этого процесса оболочки. Если вы хотите, чтобы процесс использовал эту переменную, используйте export
и запустить процесс из этой оболочки.
name=value
означает, что область видимости переменной ограничена оболочкой и недоступна для любого другого процесса. Вы бы использовали это для (скажем) переменных цикла, временных переменных и т. Д.
Важно отметить, что экспорт переменной не делает ее доступной для родительских процессов. То есть, указание и экспорт переменной в порожденном процессе не делает ее доступной в процессе, который ее запустил.
Чтобы проиллюстрировать, что говорят другие ответы:
$ foo="Hello, World"
$ echo $foo
Hello, World
$ bar="Goodbye"
$ export foo
$ bash
bash-3.2$ echo $foo
Hello, World
bash-3.2$ echo $bar
bash-3.2$
Говорят, что нет необходимости экспортировать в bash при порождении подоболочек, в то время как другие говорят прямо противоположное. Важно отметить разницу между подоболочками (теми, которые созданы ()
, ``
, $()
или циклы) и подпроцессы (процессы, которые вызываются по имени, например, литерал bash
появляется в вашем сценарии).
- Подоболочки будут иметь доступ ко всем переменным от родителя, независимо от их экспортированного состояния.
- Подпроцессы будут видеть только экспортированные переменные.
Общим в этих двух конструкциях является то, что ни один из них не может передавать переменные в родительскую оболочку.
$ noexport=noexport; export export=export; (echo subshell: $noexport $export; subshell=subshell); bash -c 'echo subprocess: $noexport $export; subprocess=subprocess'; echo parent: $subshell $subprocess
subshell: noexport export
subprocess: export
parent:
Есть еще один источник путаницы: некоторые думают, что "разветвленные" подпроцессы - это те, которые не видят неэкспортированные переменные. Обычно за fork() сразу следуют exec(), и поэтому может показаться, что fork() - это то, что нужно искать, хотя на самом деле это exec(). Вы можете запускать команды без fork() сначала с помощью exec
команда, и процессы, запущенные этим методом, также не будут иметь доступа к неэкспортированным переменным:
$ noexport=noexport; export export=export; exec bash -c 'echo execd process: $noexport $export; execd=execd'; echo parent: $execd
execd process: export
Обратите внимание, что мы не видим parent:
на этот раз, потому что мы заменили родительскую оболочку на exec
команда, так что ничего не осталось для выполнения этой команды.
Другие ответили, что экспорт делает переменную доступной для подоболочек, и это правильно, но это просто побочный эффект. Когда вы экспортируете переменную, она помещает эту переменную в среду текущей оболочки (то есть оболочка вызывает putenv(3) или setenv(3)). Среда процесса наследуется через exec, делая переменную видимой в подоболочках.
Изменить (с перспективой на 5 лет): это глупый ответ. Цель "экспорта" состоит в том, чтобы сделать переменные "находящимися в среде последовательно выполняемых команд", независимо от того, являются ли эти команды подоболочками или подпроцессами. Наивной реализацией было бы просто поместить переменную в среду оболочки, но это сделало бы невозможной реализацию export -p
,
export NAME=value
для настроек и переменных, которые имеют значение для подпроцесса.
NAME=value
для временных или петлевых переменных, приватных для текущего процесса оболочки.
Более подробно, export
помечает имя переменной в среде, которая копирует подпроцессы и их подпроцессы при создании. Ни одно имя или значение никогда не копируются из подпроцесса.
Распространенной ошибкой является размещение пробела вокруг знака равенства:
$ export FOO = "bar" bash: export: `=': not a valid identifier
Только экспортированная переменная (
B
) видно из подпроцесса:$ A="Alice"; export B="Bob"; echo "echo A is \$A. B is \$B" | bash A is . B is Bob
Изменения в подпроцессе не изменяют основную оболочку:
$ export B="Bob"; echo 'B="Banana"' | bash; echo $B Bob
Переменные, помеченные для экспорта, имеют значения, скопированные при создании подпроцесса:
$ export B="Bob"; echo '(sleep 30; echo "Subprocess 1 has B=$B")' | bash & [1] 3306 $ B="Banana"; echo '(sleep 30; echo "Subprocess 2 has B=$B")' | bash Subprocess 1 has B=Bob Subprocess 2 has B=Banana [1]+ Done echo '(sleep 30; echo "Subprocess 1 has B=$B")' | bash
Только экспортированные переменные становятся частью среды (
man environ
):$ ALICE="Alice"; export BOB="Bob"; env | grep "ALICE\|BOB" BOB=Bob
Итак, теперь это должно быть так же ясно, как летнее солнце! Спасибо Брэйну Агнью, Александру и Уильяму Пруселлу.
Следует отметить, что вы можете экспортировать переменную, а затем изменить значение. Измененное значение переменной будет доступно дочерним процессам. Как только экспорт был установлен для переменной, вы должны сделать export -n <var>
удалить имущество.
$ K=1
$ export K
$ K=2
$ bash -c 'echo ${K-unset}'
2
$ export -n K
$ bash -c 'echo ${K-unset}'
unset
export
сделает переменную доступной для всех оболочек, разветвленных из текущей оболочки.
Как вы, возможно, уже знаете, UNIX позволяет процессам иметь набор переменных среды, которые представляют собой пары ключ / значение, причем ключ и значение являются строками. Операционная система отвечает за хранение этих пар для каждого процесса в отдельности.
Программа может получить доступ к своим переменным среды через этот UNIX API:
char *getenv(const char *name);
int setenv(const char *name, const char *value, int override);
int unsetenv(const char *name);
Процессы также наследуют переменные среды от родительских процессов. Операционная система отвечает за создание копии всех "envars" в момент создания дочернего процесса.
Bash, помимо других оболочек, способен устанавливать переменные окружения по запросу пользователя. Это то, что export
существует для.
export
команда Bash для установки переменной окружения для Bash Все переменные, установленные с помощью этой команды, будут наследоваться всеми процессами, которые создаст этот Bash.
Больше на Окружающей среде в Баше
Другой тип переменной в Bash - это внутренняя переменная. Поскольку Bash - это не просто интерактивная оболочка, это фактически интерпретатор сценариев, как и любой другой интерпретатор (например, Python), он может хранить свой собственный набор переменных. Следует отметить, что Bash (в отличие от Python) поддерживает только строковые переменные.
Обозначение для определения переменных Bash: name=value
, Эти переменные остаются внутри Bash и не имеют ничего общего с переменными среды, которые хранятся в операционной системе.
Подробнее о параметрах оболочки (включая переменные)
Также стоит отметить, что, согласно справочнику Bash:
Среду для любой простой команды или функции можно временно дополнить, добавив к ней префиксы с помощью параметров, как описано в разделе Параметры оболочки. Эти операторы присваивания влияют только на среду, видимую этой командой.
Подводя итог:
export
используется для установки переменной среды в операционной системе. Эта переменная будет доступна всем дочерним процессам, созданным текущим процессом Bash.- Обозначение переменных Bash (name = value) используется для установки локальных переменных, доступных только текущему процессу bash
- Обозначение переменной Bash с префиксом другой команды создает переменную среды только для области действия этой команды.
Принятый ответ подразумевает это, но я хотел бы сделать явное соединение со встроенными командами оболочки:
Как уже упоминалось, export
сделает переменную доступной как для оболочки, так и для детей. Если export
не используется, переменная будет доступна только в оболочке, и только встроенные оболочки могут получить к ней доступ.
То есть,
tango=3
env | grep tango # prints nothing, since env is a child process
set | grep tango # prints tango=3 - "type set" shows `set` is a shell builtin
Два из создателей UNIX, Брайан Керниган и Роб Пайк, объясняют это в своей книге "Среда программирования UNIX". Google для названия, и вы легко найдете PDF-версию.
Они обращаются к переменным оболочки в разделе 3.6 и фокусируются на использовании export
команда в конце этого раздела:
Если вы хотите сделать значение переменной доступным в подоболочках, следует использовать команду экспорта оболочки. (Вы можете подумать, почему нет способа экспортировать значение переменной из вложенной оболочки в ее родительский объект).
Вот еще один пример:
VARTEST="value of VARTEST"
#export VARTEST="value of VARTEST"
sudo env | grep -i vartest
sudo echo ${SUDO_USER} ${SUDO_UID}:${SUDO_GID} "${VARTEST}"
sudo bash -c 'echo ${SUDO_USER} ${SUDO_UID}:${SUDO_GID} "${VARTEST}"'
Только с помощью export VARTEST значение VARTEST доступно в sudo bash -c '...'!
Для дальнейших примеров см.:
bash-hackers.org/wiki/doku.php/scripting/processtree
Просто чтобы показать разницу между экспортированной переменной, находящейся в среде (env), и неэкспортированной переменной, не находящейся в среде:
Если я сделаю это:
$ MYNAME=Fred
$ export OURNAME=Jim
тогда только $OURNAME появляется в env. Переменная $MYNAME отсутствует в env.
$ env | grep NAME
OURNAME=Jim
но переменная $MYNAME существует в оболочке
$ echo $MYNAME
Fred
По умолчанию переменные, созданные в скрипте, доступны только текущей оболочке; дочерние процессы (вложенные оболочки) не будут иметь доступа к значениям, которые были установлены или изменены. Чтобы дочерние процессы могли видеть значения, необходимо использовать команду экспорта.
В качестве еще одного следствия существующих здесь ответов давайте перефразируем постановку задачи.
Ответ на «следует ли» идентичен ответу на вопрос «Выполняет ли ваш последующий код команду, которая неявно обращается к этой переменной?»
Для надлежащим образом задокументированной стандартной утилиты ответ на этот вопрос можно найти в разделе ENVIRONMENT на справочной странице утилиты. Так, например, на странице руководства упоминается, чтоGIT_PAGER
определяет, какая утилита используется для просмотра многостраничного вывода из файлов . Следовательно,
# XXX FIXME: buggy
branch="main"
GIT_PAGER="less"
git log -n 25 --oneline "$branch"
git log "$branch"
не будет работать корректно, потому что вы неexport GIT_PAGER
. (Конечно, если ваша система уже объявила переменную как экспортированную где-то еще, ошибка не может быть воспроизведена.)
Мы явно ссылаемся на переменную$branch
, а код программы не ссылается на системную переменнуюbranch
в любом месте (на что также указывает тот факт, что его имя написано строчными буквами; но многие новички также ошибочно используют верхний регистр для своих частных переменных! См. Обсуждение в разделе Правильное использование заглавных букв в сценариях Bash и shell ), поэтому нет причинexport branch
.
Правильный код будет выглядеть
branch="main"
export GIT_PAGER="less"
git log -n 25 --oneline "$branch"
git log -p "$branch"
(или, что то же самое, вы можете явно добавлять префикс к каждому вызовуgit
с временным заданием
branch="main"
GIT_PAGER="less" git log -n 25 --oneline "$branch"
GIT_PAGER="less" git log -p "$branch"
Если это не очевидно, синтаксис сценария оболочки
var=value command arguments
временно устанавливаетvar
кvalue
на время исполнения
command arguments
и экспортирует его вcommand
subprocess, а затем возвращает его обратно к предыдущему значению, которое может быть неопределенным или определенным с другим — возможно, пустым — значением и неэкспортируемым, если оно было раньше.)
Для внутренних, специальных или иным образом плохо документированных инструментов вам просто нужно знать, проверяют ли они молча свою среду. На практике это редко бывает важным, за исключением нескольких конкретных случаев использования, таких как передача пароля или токена аутентификации или другой секретной информации процессу, работающему в каком-либо контейнере или изолированной среде.
Если вам действительно нужно знать и иметь доступ к исходному коду, ищите код, который используетgetenv
системный вызов (или в Windows, с моими соболезнованиями, вариации типаgetenv_s
,w_getenv
, и т. д). Для некоторых языков сценариев (таких как Perl или Ruby) ищитеENV
. Для Python ищите (но обратите внимание также, что, например,from os import environ as foo
Значит этоfoo
теперь псевдоним дляos.environ
). В узле найдитеprocess.env
. Для C и родственных языков ищитеenvp
(но это всего лишь соглашение о том, что вызывать необязательный третий аргумент дляmain
, послеargc
иargv
; язык позволяет называть их как угодно). Для сценариев оболочки (как кратко упомянуто выше), возможно, ищите переменные с именами в верхнем или иногда смешанном регистре или используйте утилитуenv
. Многие неформальные сценарии имеют недокументированные, но обнаруживаемые назначения, обычно в начале сценария; в частности, искать?=
расширение параметра назначения по умолчанию .
Для краткой демонстрации вот скрипт оболочки, который вызывает скрипт Python, который ищет$NICKNAME
, и возвращается к значению по умолчанию, если оно не установлено.
#!/bin/sh
NICKNAME="Handsome Guy"
demo () {
python3 <<\____
from os import environ as env
print("Hello, %s" % env.get("NICKNAME", "Anonymous Coward"))
____
}
demo
# prints "Hello, Anonymous Coward"
# Fix: forgot export
export NICKNAME
demo
# prints "Hello, Handsome Guy"
В качестве еще одного второстепенного замечания позвольте мне повторить, что вам всегда нужноexport
переменная один раз. Многие начинающие карго-культовые коды, такие как
# XXX FIXME: redundant exports
export PATH="$HOME/bin:$PATH"
export PATH="/opt/acme/bin:$PATH"
но, как правило, ваша операционная система уже объявлена как экспортируемая, так что это лучше написать
PATH="$HOME/bin:$PATH"
PATH="/opt/acme/bin:$PATH"
или, возможно, рефакторинг к чему-то вроде
for p in "$HOME/bin" "/opt/acme/bin"
do
case :$PATH: in
*:"$p":*) ;;
*) PATH="$p:$PATH";;
esac
done
# Avoid polluting the variable namespace of your interactive shell
unset p
что позволяет избежать добавления повторяющихся записей в вашPATH
.
Хотя это явно не упомянуто в обсуждении, НЕ обязательно использовать экспорт при порождении подоболочки изнутри bash, поскольку все переменные копируются в дочерний процесс.