Окружение: переменные, PATH, профили

Почему одна и та же команда работает в одном терминале и «не найдена» в другом — и где это всё настраивается.

Переменная окружения — именованное значение, которое оболочка передаёт по наследству каждому запускаемому процессу; PATH среди них главная: по ней система ищет, какой именно файл запустить, когда вы набираете имя команды.

Когда вы пишете git status, оболочка не обыскивает весь диск. Она перебирает каталоги из переменной PATH слева направо и запускает первый найденный исполняемый файл git. Понимание этого механизма (и того, в каком файле он настраивается) экономит часы: именно из-за окружения «работает у меня, не работает на сервере», из-за него sudo не видит вашу команду, а свежеустановленная утилита оказывается «не найдена», пока вы не перезайдёте.

Переменные окружения и export

Текущее окружение целиком печатает printenv (или env). Одну переменную — echo с символом $ перед именем:

printenv | sort | head
echo "$HOME"
echo "$USER"
echo "$PATH"

Ключевое различие — между обычной переменной оболочки и экспортированной. Простое присваивание видно только в текущей оболочке; дочерние процессы его не получают. Команда export помечает переменную как наследуемую:

GREETING=hello          # видна только этой оболочке
export EDITOR=vim       # унаследуют все дочерние процессы
export API_URL="https://example.com"

# проверка: запустим дочернюю оболочку и спросим переменную
bash -c 'echo "$EDITOR — $GREETING"'

Вывод:

vim — 

Дочерний bash увидел EDITOR (экспортирована), но не GREETING. Именно поэтому переменные для приложений — токены, пути, флаги — почти всегда экспортируют. Передать переменную только одной команде, не засоряя окружение, можно префиксом:

LANG=C date          # эта одна команда видит LANG=C
DEBUG=1 ./myapp      # флаг живёт только на время запуска myapp

Снять переменную — unset ИМЯ.

Как команда находится по PATH

PATH — это список каталогов через двоеточие. Узнать, какой именно файл выполнится, помогает type и which:

type python3          # покажет путь, alias или встроенную команду
type -a python3       # ВСЕ совпадения по PATH, в порядке поиска
which -a git
command -v ls

Вывод:

python3 is /usr/bin/python3
python3 is /usr/bin/python3
/usr/bin/git

Порядок важен: если в начале PATH стоит ~/.local/bin, то ваш личный python3 перекроет системный. Так работают venv, pyenv, nvm — они просто подставляют свой каталог в начало PATH. Добавить каталог обычно нужно именно в начало (приоритет) или в конец:

export PATH="$HOME/.local/bin:$PATH"   # свой каталог приоритетнее
export PATH="$PATH:/opt/tools/bin"     # запасной, в конце

Частая причина «command not found» после установки утилиты — её положили в каталог, которого нет в PATH (например, /usr/local/bin или ~/.local/bin), а оболочка ещё не перечитала профиль. Решение — открыть новый терминал или явно перечитать конфиг.

Профили: .bashrc vs .profile vs .bash_profile

Здесь живёт большинство недоразумений. Bash читает разные файлы в зависимости от того, как он запущен.

Login vs non-login, interactive vs non-interactive

Login-оболочка — та, что стартует при входе в систему: вход по SSH, вход в текстовую консоль, su -, bash --login. Она читает /etc/profile, затем первый из ~/.bash_profile, ~/.bash_login, ~/.profile.

Non-login interactive-оболочка — обычное окно терминала внутри уже запущенной графической сессии, новая вкладка, bash без аргументов. Она читает ~/.bashrc/etc/bash.bashrc).

ФайлКогда читаетсяЧто класть
/etc/profilelogin, для всех пользователейсистемные настройки (трогает админ)
~/.bash_profilelogin (личный)обычно — подключение ~/.bashrc
~/.profilelogin, если нет .bash_profile; читают и не-bash оболочкиexport PATH, переменные окружения
~/.bashrcкаждый интерактивный non-login bashalias, prompt, функции, автодополнение

Практическое правило: экспорт переменных и PATH кладут туда, что читается при логине (~/.profile или ~/.bash_profile), а alias, цвет приглашения, функции — в ~/.bashrc. Чтобы login-оболочка тоже получила alias из .bashrc, в ~/.bash_profile традиционно дописывают подключение:

cat ~/.bash_profile
# типичное содержимое:
if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

Точка (.) или source — это «выполнить файл в текущей оболочке». После правки конфига не обязательно перезаходить — достаточно перечитать его:

source ~/.bashrc      # применить изменения сразу
. ~/.profile          # то же самое короче

Alias — короткие имена для команд

alias создаёт сокращение, которое раскрывается до начала разбора команды. Удобно для часто используемых длинных вызовов и для безопасных значений по умолчанию:

alias ll='ls -lah'
alias gs='git status'
alias rm='rm -i'              # спросить подтверждение на удаление
alias grep='grep --color=auto'

alias                        # показать все текущие alias
unalias ll                   # убрать один
\rm file                     # обратный слэш — запустить БЕЗ alias (настоящий rm)

Чтобы alias жил между сессиями, его записывают в ~/.bashrc. Важно: alias работает только в интерактивной оболочке и не виден скриптам — для скриптов используют функции или полный путь.

Локали: LANG и LC_*

Локаль определяет язык сообщений, формат даты, разделитель дробей, правила сортировки и кодировку. Управляют ею LANG (значение по умолчанию для всех категорий) и более точные LC_*:

locale                       # текущие настройки локали
locale -a | grep -i utf      # какие локали установлены в системе

export LANG=en_US.UTF-8       # язык и кодировка по умолчанию
export LC_TIME=ru_RU.UTF-8    # только формат времени — русский

Зачем это DevOps: на «голом» сервере локаль часто C/POSIX, из-за чего perl, psql и пакетные менеджеры сыплют предупреждениями вроде «setlocale: LC_ALL: cannot change locale». Особый случай — LC_ALL: он перебивает все категории и LANG разом, поэтому в скриптах для предсказуемого, не зависящего от языка вывода ставят LC_ALL=C — тогда числа, даты и сортировка будут стабильными независимо от настроек машины.

Как это работает под капотом

Окружение — не магия оболочки, а свойство процесса в ядре. У каждого процесса есть массив строк вида ИМЯ=значение, который ядро копирует потомку при системном вызове execve. Поэтому изменения наследуются строго вниз по дереву процессов и никогда не поднимаются вверх: скрипт не может поменять окружение запустившей его оболочки — он меняет лишь свою копию. Прочитать окружение любого живого процесса можно прямо из псевдо-файловой системы:

cat /proc/self/environ | tr '\0' '\n' | head   # окружение текущего процесса
tr '\0' '\n' < /proc/$$/environ | grep PATH    # PATH вашей оболочки ($$ — её PID)

Здесь видно, что переменные хранятся как строки, разделённые нулевым байтом. А source отличается от обычного запуска скрипта именно тем, что выполняет команды в текущем процессе, а не в потомке — поэтому только через source файл может изменить ваш PATH или объявить alias в действующей сессии.

Частые ошибки

  • Положили export PATH=... в ~/.bashrc и удивляетесь, что при входе по SSH (login-оболочка) переменная не подхватилась. Переменные окружения — в профиль логина.
  • Забыли export: переменная видна в оболочке, но дочерний процесс/приложение её не получает.
  • Перезаписали PATH целиком (export PATH="/my/bin" вместо "$PATH:/my/bin") — и потеряли ls, cat и прочее: система перестала находить базовые команды.
  • Ждёте, что alias из .bashrc сработает в скрипте — не сработает, alias живёт только в интерактивной оболочке.
  • Странный вывод дат и сортировки в скрипте на сервере — виновата непредсказуемая локаль; зафиксируйте LC_ALL=C.

Итоги

  • export делает переменную наследуемой дочерними процессами; окружение течёт только вниз по дереву процессов.
  • Команда ищется по каталогам PATH слева направо; type -a и which -a показывают, что именно запустится.
  • Login-оболочка читает ~/.profile/~/.bash_profile, обычное окно терминала — ~/.bashrc; переменные — в первый, alias и prompt — во второй.
  • source (он же .) применяет конфиг к текущей сессии без перезахода.
  • Локаль (LANG, LC_*) задаёт язык, форматы и кодировку; LC_ALL=C даёт стабильный, не зависящий от языка вывод для скриптов.
Проверьте себя
1. Вы добавили строку `export PATH="$HOME/bin:$PATH"` в ~/.bashrc, но при входе на сервер по SSH ваша утилита из ~/bin не находится. Почему?
ASSH-сессия — это login-оболочка, которая читает ~/.profile или ~/.bash_profile, а не ~/.bashrc
BВ PATH нельзя использовать переменную $HOME
Cexport работает только для встроенных команд оболочки
DНужно было написать PATH без кавычек
2. Чем `source ~/.bashrc` (или `. ~/.bashrc`) отличается от запуска `bash ~/.bashrc`?
AНичем, это синонимы
Bsource выполняет файл в ТЕКУЩЕЙ оболочке (изменения PATH и alias применяются к ней), а `bash файл` запускает дочерний процесс, чьи изменения вашей сессии не достаются
Csource работает только с файлами в домашнем каталоге
D`bash файл` быстрее, потому что не читает переменные окружения