Когда переменные bash экспортируются в подоболочки и/или доступны сценариям?

Я смущен тем, экспортируются ли переменные bash в подоболочки и когда они доступны сценариям. Мой опыт до сих пор привел меня к мысли, что переменные bash автоматически доступны для подоболочек. Например.:

> FOO=bar
> echo $FOO
bar
> (echo $FOO)
bar

Вышеприведенное показывает, что переменные bash доступны в подоболочках.

Учитывая этот скрипт:

#! /usr/bin/bash
# c.sh

func()
{
  echo before
  echo ${FOO}
  echo after
}

func

Я понимаю, что вызов скрипта в текущем контексте оболочки дает ему доступ к текущим переменным оболочки:

> . ./c.sh 
before
bar
after

Если бы я вызывал скрипт без прецедента "точка-пробел"...

> ./c.sh 
before

after

... разве это не тот случай, когда скрипт вызывается в подоболочке? Если это так, а также верно то, что переменные текущей оболочки доступны для подоболочек (как я понял из самого первого блока кода), почему $FOO недоступна для c.sh при таком запуске?

Точно так же, почему $FOO также недоступен, когда c.sh запускается в круглых скобках, что, как я понял, означает выполнение выражения в подоболочке:

> (./c.sh)
before

after

(Если это не загрязняет этот пост слишком большим количеством вопросов: если «./c.sh» и «(./c.sh)» оба запускают скрипт в подоболочке текущей оболочки, в чем разница между двумя способами вызова?)


person StoneThrow    schedule 17.08.2018    source источник
comment
Подоболочка является ответвлением родительского процесса, поэтому переменную не нужно необходимо экспортировать, чтобы она была видна в ней: дочерние процессы всегда наследуют 100% состояния своего родительского процесса (за исключением самого PID, и дескрипторы файлов, которые были явно открыты с флагами, указывающими ОС не копировать их при разветвлении).   -  person Charles Duffy    schedule 18.08.2018
comment
Таким образом, ./foo не запускает foo в подоболочке: это совершенно не связанный дочерний процесс, находящийся не только за границей fork(), но и за границей execve().   -  person Charles Duffy    schedule 18.08.2018
comment
... тогда как (./c.sh) разветвляет подоболочку, а затем запускает дочерний процесс внутри нее, поэтому дочерний процесс является внуком, а не прямым дочерним элементом исходной оболочки, и у вас есть граница execv между дочерним элементом и внуком ( хотя между родителем и ребенком нет).   -  person Charles Duffy    schedule 18.08.2018
comment
Вы отметили shell, поэтому я хотел бы отметить, что не все оболочки обрабатывают вложенные оболочки так же, как bash. Оболочка Korn, например, избегает создания дочернего процесса для вложенной оболочки.   -  person cdarke    schedule 18.08.2018
comment
@cdarke, ... Я бы скорее сказал, что ksh реализует семантику отдельной среды (...) без использования подоболочек, насколько это возможно (когда невозможно соблюдать семантику POSIX без создания подоболочки, создается подоболочка; неверно подразумевать, что (...) вообще их не использует). Чтение вышеизложенного как просьба отредактировать мой ответ, чтобы больше не указывать, что (...) запрашивает подоболочку (по сравнению с запросом независимой среды, наиболее легко реализуемой с помощью подоболочки), является справедливым.   -  person Charles Duffy    schedule 19.08.2018
comment
@CharlesDuffy: я не согласен с вашим первым предложением, но, возможно, это зависит от вашего определения подоболочки. Для меня подоболочка - это отдельная среда, вы подразумеваете, что подоболочка всегда является дочерней. Это, конечно, может быть просто семантикой. Я должен был сказать, что ksh пытается избежать создания дочернего процесса.   -  person cdarke    schedule 19.08.2018
comment
Перечитывая стандарт, я определенно вижу некоторую поддержку вашего определения, хотя и не настолько двусмысленного, чтобы заставить изменить мой собственный выбор терминов. В любом случае, я уверен, что совместное чтение наших комментариев приведет читателя к полезному пониманию. :)   -  person Charles Duffy    schedule 20.08.2018


Ответы (1)


(...) запускает ... в отдельной среде, чего легче всего добиться (и реализовать в bash, dash и большинстве других оболочек POSIX-y) с использованием подоболочки, то есть дочерней структуры, созданной fork()ингом старой оболочки, но без вызова какой-либо функции семейства execv. Таким образом, дублируется все состояние родителя в памяти, включая неэкспортированные переменные оболочки.


./other-script, напротив, запускает other-script как совершенно отдельный исполняемый файл; он не сохраняет неэкспортированные переменные после вызова дочерней оболочки (которая не является подоболочкой!). Это работает следующим образом:

  • Оболочка вызывает fork() для создания дочернего элемента. На данный момент у дочернего элемента все еще есть скопированное даже неэкспортированное состояние переменной.
  • Дочерний элемент учитывает любые перенаправления (если это ./other-script >>log.out, дочерний элемент будет open("log.out", O_APPEND), а затем fdup() дескриптор будет заменен на 1, перезаписывая стандартный вывод).
  • Дочерний элемент вызывает execv("./other-script", {"./other-script", NULL}), инструктируя операционную систему заменить его новым экземпляром other-script. После успешного выполнения этого вызова процесс, работающий под дочерним PID, является совершенно новой программой, и выживают только переменные exported.
person Community    schedule 17.08.2018
comment
Это увлекательно — я никогда не рассматривал fork() и exec() в контексте bash. Правильно ли я понимаю: когда я вызываю (./c.sh), разветвляется подоболочка, поэтому $FOO виден в подоболочке. Но эта подоболочка затем fork()s и exec()s ./c.sh, поэтому в контексте c.sh (который является своего рода дочерним процессом оболочки, где я набрал (./c.sh)), $FOO больше не отображается? - person StoneThrow; 18.08.2018
comment
Второе предложение должно читаться как родитель, не так ли? Не совсем уверен, следовательно, не просто редактирую;) - person Benjamin W.; 18.08.2018
comment
Правильно ли я понимаю это: похоже, что ./c.sh является подмножеством (./c.sh) в том смысле, что первый будет разветвлять и выполнять фактический сценарий c.sh из текущего контекста оболочки, где последний будет (1) разветвляться, но не выполняться (т. е. новый bash процесс создается с тем же состоянием, что и его родитель), а затем (2) разветвить и выполнить фактический c.sh скрипт _ из вновь созданного bash дочернего процесса? - person StoneThrow; 18.08.2018
comment
@StoneThrow, ... да, вы правильно поняли. - person Charles Duffy; 18.08.2018
comment
@BenjaminW., действительно, это то, что я имел в виду, спасибо. - person Charles Duffy; 18.08.2018
comment
Кроме того, если вы используете exec ./other-script (который запускает exec() без предварительного разветвления), другой сценарий наследует экспортированные переменные, но не неэкспортированные переменные оболочки. ./other-script в основном эквивалентен (exec ./other-script), в котором ( ) разветвляет подоболочку (сохраняя неэкспортированные переменные), а затем exec эффективно выходит из текущей оболочки (уничтожая неэкспортируемые переменные) и запускает новую оболочку в том же процессе. - person Gordon Davisson; 18.08.2018
comment
Обратите внимание на особенность bash, $$ в подоболочке дает PID родителя, а не текущего процесса подоболочки. - person cdarke; 18.08.2018
comment
@cdarke: это не бахизм; он определен Posix: [$$] Заменяется на десятичный идентификатор процесса вызванной оболочки. В подоболочке (см. Среда выполнения оболочки) '$' должен расширяться до того же значения, что и в текущей оболочке. - person rici; 18.08.2018
comment
@rici: спасибо, я не знал, что это было в POSIX, но это объясняет, почему bash делает это (хотя вы можете поспорить о том, что означает текущая оболочка). - person cdarke; 18.08.2018