Вопрос или проблема
Я использую Fedora, где все предустановленные оболочки, по-видимому, поддерживают bashism: bash --posix
работает, и даже sh
. Тем не менее, когда я использовал эту функцию,
pathprepend () {
if [[ ":$PATH:" != *":$1:"* ]]; then
PATH="$1${PATH:+":$PATH"}"
fi
}
чтобы добавить каталог в PATH
из ~/.profile
, Git его не увидел. Обратите внимание, что echo $PATH
в bash-сессии, из которой я использовал Git, показал мой каталог, я заметил это просто потому, что использую diff-highlight
, связанный символической ссылкой в этом каталоге, и после того как я начал использовать эту функцию pathprepend
, Git жаловался, что diff-highlight
отсутствует. После восстановления POSIX-соответствующей настройки PATH
всё вернулось к обычному функционированию.
Это заставило меня задуматься, откуда программы берут свою среду. У каждой из них есть собственный механизм для этого? Они оставляют эту задачу на откуп системной оболочке или библиотеке?
Редактирование
@Bodo, вот что я использовал раньше: в ~/.profile
,
pathprepend () {
if [[ ":$PATH:" != *":$1:"* ]]; then
PATH="$1${PATH:+":$PATH"}"
fi
}
pathprepend "$HOME/.local/bin"
Под “POSIX-соответствующий” я имею в виду без bashism. Вот что у меня сейчас в ~/.profile
:
pathprepend () {
case ":$PATH:" in
*":$1:"*)
:;;
*)
PATH="$1${PATH:+":$PATH"}";;
esac
}
pathprepend "$HOME/.local/bin"
В обоих случаях ~/.profile
считывается из ~/.bash_profile
, который содержит
# Загружаем конфигурацию для логин-сессий любых оболочек
if [[ -f "$HOME/.profile" ]]; then
source "$HOME/.profile"
else
echo >&2 "$HOME/.bash_profile: $HOME/.profile не найден"
fi
# Загружаем конфигурацию для интерактивных нелогиновых сессий Bash
case "$-" in *i*)
if [[ -f "$HOME/.bashrc" ]]; then
source "$HOME/.bashrc"
else
echo >&2 "$HOME/.bash_profile: $HOME/.bashrc не найден"
fi;;
esac
Я не использовал export
для PATH
, потому что Гордон Дэвисон объясняет в ответе, из которого я скопировал функцию, что “PATH
уже должен быть маркирован как экспортированный, поэтому его повторная экспортировка не нужна.” Фактически, я до сих пор не использую export
, но с версией case
функции pathprepend
всё работает нормально. Кстати, какой родительский процесс у процесса Git, является ли это Bash-процесс, из которого я использую git
?
программы получают свою среду из третьего аргумента системного вызова execve(program_path, argv, envp)
, который их выполняет.
envp
, как и argv
, представляет собой массив строк, за исключением того, что для envp
по соглашению строки имеют формат var=value
.
Что программы делают с этими строками, решают они сами, но в общем случае они берут эти строки var=value
и интерпретируют то, что слева от первого =
, как имя переменной среды, а то, что справа, как её значение.
Обычно они сохраняют этот список и когда они, в этом или дочерних процессах, выполняют другие команды, они передают этот же список в третьем аргументе соответствующему системному вызову execve()
.
В библиотеке C есть вспомогательные функции для этого. Этот сохраненный список — переменная environ
, и функции putenv()
/setenv()
/unsetenv()
можно использовать для добавления/изменения/удаления переменных там, а функции типа execlp()
являются обёртками для системного вызова execve()
, которые передают этот environ
автоматически как envp
. Идея заключается в том, что среда предназначена для наследования автоматическими путями через выполнение¹.
Большинство оболочек сопоставляют переменные среды с переменными оболочки.
В оболочках, подобных POSIX, переменные из envp
, которые имеют имя, совместимое с именем переменной оболочки, преобразуются в переменную оболочки, которая маркируется атрибутом export
. Также специальная встроенная утилита export
может использоваться для повышения статуса переменной оболочки до переменной среды, чтобы она передавалась в envp
каждой последующей выполняемой команде.
~/.profile
— это файл инициализации сессии, который интерпретируется большинством оболочек, подобных Bourne, когда они вызваны как логиновые оболочки (что login
делает, добавляя -
к argv[0]
). Эквивалентом для оболочек, подобных csh, будет ~/.login
, также смотрите ~/.zprofile
/~/.zlogin
для zsh.
На сегодняшний день это обычно считывается при входе через ssh
(при использовании только ssh host
, а не ssh host 'shell code'
), в виртуальном терминале или sudo -i
, редко терминальными эмуляторами, запущенными в графической сессии входа в систему.
Некоторые графические среды предпринимают попытки запустить логиновую оболочку пользователя как нелогиновую интерактивную оболочку, которая интерпретирует те файлы среды сессии при входе в систему, чтобы получить те же переменные среды, но не все. Иногда для определения переменных среды используются другие механизмы.
Если при входе в систему ваша логиновая оболочка была запущена как логиновая оболочка и она подобна Bourne (и не zsh), она интерпретировала код в ~/.profile
.
При интерпретации PATH="~/.local/bin${PATH:+":$PATH"}"
было обновлено $PATH
переменная оболочки. Поскольку эта переменная будет найдена в envp
, переданной этой оболочке, эта переменная будет отмечена для экспорта
, и переменная с её изменённым значением² будет включена в envp
, переданный каждой команде, выполняемой впоследствии, включая git
. Если git
выполнит getenv("PATH")
, он должен получить это изменённое значение.
Но для этого ~/.profile
должен быть интерпретирован оболочкой в иерархии процессов, которая выполняет git
.
¹ Не путать с отношениями родитель/ребёнок. Дочерний процесс наследует всё от родительского, но выполнение очищает всю память, и здесь вам нужен такой механизм как среда, чтобы передать информацию.
² За исключением оболочки Bourne, где изменение переменной оболочки не отражается на переменной среды, если не вызвать export
. Но вы не найдёте оболочку Bourne в Fedora или в любой современной системе.
.
Ответ или решение
Вопрос, который мы рассматриваем, касается того, как программы получают доступ к своему окружению, и почему иногда изменения в переменных окружения не отражаются, как ожидалось. Данная задача относится к технологиям и операционным системам, особенно в контексте использования оболочки Bash и системных вызовов в операционной системе Fedora. Попробуем разобраться в этом вопросе более детально.
Теория
В Unix-подобных системах, таких как Fedora, процессы наследуют свое окружение от родительского процесса. Информация об окружении передаётся процессам через системный вызов execve
. Этот вызов принимает три параметра: путь к исполняемому файлу, массив аргументов командной строки и массив переменных окружения (envp
). Каждая запись в массиве envp
представляет собой строку формата var=value
, где var
— это имя переменной, а value
— ее значение.
Программы могут работать с этой коллекцией окружения по-разному. Обычно они распознают строки var=value
и интерпретируют var
как имя переменной окружения, а value
как ее значение. При запуске дочерних процессов программы обычно передают список окружения следующему процессу с использованием того же вызова execve()
.
Пример
В контексте вопроса, проблема возникает при добавлении нового пути в переменную окружения PATH
через пользовательскую функцию pathprepend
в файле ~/.profile
. Однако Git, который запускается из этой Bash-сессии, не видит изменений в PATH
. Это может быть связано с тем, как и когда интерпретируются стартовые файлы окружения.
Для именования нескольких критически важных моментов:
-
Инициализация окружения:
~/.profile
обычно выполняется при входе в систему для настроек сессии в интерактивной оболочке (например, при входе через SSH). Тем не менее, терминальные эмуляторы, используемые в графической среде, такие как GNOME Terminal, могут не инициировать оболочку входа (login shell), что означает, что~/.profile
может быть не выполнен. -
Оболочка и экспорт переменных: При вызове в
~/.bash_profile
и~/.profile
, переменнаяPATH
устанавливается и наследуется процессами, запуск которых инициирован из этой же оболочки. ЕслиPATH
экспортирован, то изменения должны быть видны всем дочерним процессам этой сессии. -
Влияние POSIX-соответствия: Использование POSIX-соответствующего стиля в скриптах может влиять на то, как интерпретируются команды и как производится экспорт переменных. Проблемы возникают, если используются команды или функции, не соответствующие POSIX в строго POSIX-режиме.
Применение
Для гарантии, что изменения в PATH
видны всем процессам, рекомендовано следующее:
-
Убедитесь, что оболочка работает в режиме login shell при запуске терминала, который вы используете. Это может требовать настройки терминального эмулятора для запуска оболочки как login shell. В GNOME Terminal, например, это можно настроить в параметрах.
-
Всегда проверяйте, удовлетворяют ли ваши скрипты и команды POSIX-стандарту, если вы хотите избежать проблем с совместимостью в других средах или профессионально настроенных системах.
-
Для графической среды рекомендуется использовать файлы
~/.bashrc
или системы управления сессиями, такие как environment.d от systemd, для задания переменных окружения, которые будут применяться во всех случаях, включая неинтерактивные и интерактивные сессии.
Чтобы вся система функционировала как положено, важно понимать, каким образом Unix-подобные системы управляют информацией об окружении и как различные обертки и утилиты, такие как оболочки и скрипты инициализации, взаимодействуют друг с другом в этом процессе. Эти знания помогут в эффективной настройке и устранении возникающих сбоев в поведении программ.