Окружение: переменные, 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/profile | login, для всех пользователей | системные настройки (трогает админ) |
~/.bash_profile | login (личный) | обычно — подключение ~/.bashrc |
~/.profile | login, если нет .bash_profile; читают и не-bash оболочки | export PATH, переменные окружения |
~/.bashrc | каждый интерактивный non-login bash | alias, 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даёт стабильный, не зависящий от языка вывод для скриптов.