Какие области видимости могут иметь переменные оболочки?


42

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

Я пытался использовать bundle install, это команда Ruby, которая использует значение $GEM_HOMEдля выполнения своей работы. Я установил $GEM_HOME, но команда игнорировала это значение, пока я не использовал export, как в export GEM_HOME=/some/path.

Я читал, что это делает переменную как-то «глобальной» (также известной как переменная среды ), но я не понимаю, что это значит. Я знаю о глобальности в программировании, но не в разных программах.

Кроме того, учитывая, что мои установки таких переменных применимы только к текущему сеансу оболочки, как бы я установил их, скажем, для демонизированного процесса?

Какие области видимости могут иметь переменные оболочки?

Ответы:


33

Процессы организованы в виде дерева: каждый процесс имеет уникального родителя, кроме initкоторого PIDвсегда 1 и не имеет родителя.

Создание нового процесса проходит обычно через пару fork/ execvсистемных вызовов, где среда дочернего процесса является копией родительского процесса.

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

Примите также во внимание, что дочерний процесс может изменить свое окружение, например, может сбросить его до значений по умолчанию, как, например, это возможно, loginнапример.


1
Ах! ОК, посмотрим, пойму ли я это. В оболочке, если я скажу FOO=bar, это устанавливает значение для текущего процесса оболочки. Если я затем запускаю программу наподобие ( bundle install), она создает дочерний процесс, к которому нет доступа FOO. Но если бы я сказал export FOO=bar, дочерний процесс (и его потомки) имели бы к нему доступ. Один из них может, в свою очередь, вызвать export FOO=buzzизменение значения для его потомков или просто FOO=buzzизменить значение только для себя. Это примерно так?
Натан Лонг

2
@NathanLong Это не совсем так: во всех современных оболочках переменная либо экспортируется (и поэтому любое изменение значения отражается в среде потомков), либо не экспортируется (это означает, что переменная не находится в среде). В частности, если переменная уже находится в среде при запуске оболочки, она экспортируется.
Жиль "ТАК - перестань быть злым"

2
Меня немного смутило предложение «если ребенок изменяет значение переменной, измененное значение видимо только ему и всем процессам, созданным после этого изменения». Было бы правильнее сказать «... видимым для него и всех его дочерних процессов, созданных после этого изменения» - другие дочерние элементы родительского процесса, даже те, которые были запущены после дочернего процесса, не затрагиваются.
Яан

26

По крайней мере, под kshи bashпеременные могут иметь три области, а не две, как все остальные ответы в настоящее время говорят.

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

Переменные, объявленные в функциях оболочки с typesetтокеном, видны только внутри функций, в которых они объявлены, и в (под) функциях, вызываемых оттуда.

Это ksh/ bashкод:

# Create a shell script named /tmp/show that displays the scoped variables values.    
echo 'echo [$environment] [$shell] [$local]' > /tmp/show
chmod +x /tmp/show

# Function local variable declaration
function f
{
    typeset local=three
    echo "in function":
    . /tmp/show 
}

# Global variable declaration
export environment=one

# Unexported (i.e. local) variable declaration
shell=two

# Call the function that creates a function local variable and
# display all three variable values from inside the function
f

# Display the three values from outside the function
echo "in shell":
. /tmp/show 

# Display the same values from a subshell
echo "in subshell":
/tmp/show

# Display the same values from a disconnected shell (simulated here by a clean environment start)
echo "in other shell"
env -i /tmp/show 

производит этот вывод:

in function:
[one] [two] [three]
in shell:
[one] [two] []
in subshell:
[one] [] []
in other shell
[] [] []

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

Обратите внимание, что это последнее поведение сильно отличается от Windows, где вы можете использовать системные переменные, которые являются полностью глобальными и общими для всех процессов.


12

Они ограничены процессом

Другие ответчики помогли мне понять, что область видимости переменных оболочки - это процессы и их потомки .

Когда вы набираете команду, как lsв командной строке, вы фактически разветвляете процесс для запуска lsпрограммы. Новый процесс имеет вашу оболочку в качестве родителя.

Любой процесс может иметь свои собственные «локальные» переменные, которые не передаются дочерним процессам. Он также может устанавливать переменные окружения, которые есть. Использование exportсоздает переменную среды. В любом случае несвязанные процессы (одноранговые узлы оригинала) не увидят переменную; мы контролируем только то, что видят дочерние процессы.

Предположим, у вас есть оболочка bash, которую мы будем называть A. Вы вводите bash, которая создает дочернюю оболочку bash процесса, которую мы будем называть B. Все, что вы вызываете exportв A, все равно будет установлено в B.

Теперь, в B, вы говорите FOO=b. Произойдет одно из двух:

  • Если B не получил (от A) переменную среды, вызванную FOO, он создаст локальную переменную. Дети Б не получат его (если Б не звонит export).
  • Если B действительно получил (от A) переменную окружения FOO, вызванную , он изменит ее для себя и своих потомков-потомков . Дети B увидят значение, присвоенное B. Однако это никак не повлияет на А.

Вот быстрое демо.

FOO=a      # set "local" environment variable
echo $FOO  # 'a'
bash       # forks a child process for the new shell
echo $FOO  # not set
exit       # return to original shell
echo $FOO  # still 'a'

export FOO # make FOO an environment variable
bash       # fork a new "child" shell
echo $FOO  # outputs 'a'
FOO=b      # modifies environment (not local) variable
bash       # fork "grandchild" shell
echo $FOO  # outputs 'b'
exit       # back to child shell
exit       # back to original shell
echo $FOO  # outputs 'a'

Все это объясняет мою первоначальную проблему: я установил GEM_HOMEв своей оболочке, но когда я позвонил bundle install, это создало дочерний процесс. Поскольку я не использовал export, дочерний процесс не получил оболочки GEM_HOME.

Un-экспорт

Вы можете «отменить экспорт» переменной - предотвратить ее передачу дочерним элементам - используя export -n FOO.

export FOO=a   # Set environment variable
bash           # fork a shell
echo $FOO      # outputs 'a'
export -n FOO  # remove environment var for children
bash           # fork a shell
echo $FOO      # Not set
exit           # back up a level
echo $FOO      # outputs 'a' - still a local variable

1
Когда вы говорите «он изменит его для себя и своих потомков», вы должны уточнить, что только потомки, созданные после модификации, увидят измененное значение.
энзотиб

1
@enzotib - хорошая мысль. Обновлено.
Натан Лонг

3

Лучшее объяснение, которое я могу найти об экспорте, это:

http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_02.html

Переменная, установленная в подоболочке или дочерней оболочке, видна только той подоболочке, в которой она определена. Экспортированная переменная фактически превращается в переменную окружения. Таким образом, чтобы быть ясным, вы bundle installзапускаете свою собственную оболочку, которая не видит, $GEM_HOMEесли только она не превращена в environmentэкспортируемую переменную.

Вы можете взглянуть на документацию для переменной области здесь:

http://www.tldp.org/LDP/abs/html/subshells.html


Ах, я неправильно использовал термин «переменная окружения» FOO=bar; Вы должны использовать, exportчтобы сделать это один. Вопрос исправлен соответственно.
Натан Лонг

Посмотрите на ссылку я добавил в.
Карлсон

3

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

Среда

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

Переменные оболочки

Оболочки имеют свое собственное понятие переменных. Здесь вещи начинают становиться немного запутанными.

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

Вот где exportключевое слово вступает в игру. Он копирует переменную оболочки в среду процесса оболочки, позволяя наследовать дочерние процессы.

Локальные переменные

Локальные переменные - это переменные оболочки, ограниченные областями кода, содержащими их. Вы объявляете локальные переменные с помощью typesetключевого слова (переносимый) или localили declare(Bash). Как и другие переменные оболочки, локальные переменные не наследуются дочерними процессами. Также локальные переменные не могут быть экспортированы.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.