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

## Введение

Существует несколько способов передачи данных в программу, которые быстро реализуемы функциями стандартной библиотеки Си/C++ -- это:
* чтение из файла;
* прием данных из клавиатуры;
* сканирование аргументов командной строки.

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

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

В таблице ниже перечислены переменные системы UNIX, часто встречаемые на практике.

| Имя переменной |                     Предназначение                        |
|----------------|-----------------------------------------------------------|
| HOME           | Домашний каталог пользователя                             |
| PWD            | Текущий каталог                                           |
| PATH           | Список каталогов, откуда осуществляется поиск запускаемых по имени программ |
| USER           | Имя пользователя                                          |
| HOSTNAME       | Имя компьютера                                            |
| SHELL          | Полный путь к исполняемому файлу системного интерпретатора|
| TZ             | Часовой пояс                                              |
| LANG           | Текущие язык, настройки локализации и кодировка           |
| EDITOR         | Используемый по умолчанию текстовый редактор              |

Зачем нужны переменные?
Переменные несут информацию от системы к пользователю.
Они подсказывают приложениям, в каком каталоге находился пользователь перед запуском приложения (`PWD`), в каком часовом поясе он находится (`TZ`), какой язык и кодировку использует система (`LANG`) и какой редактор предпочитает пользователь (`EDITOR`).
Переменные также переносят информацию в обратном направлении: от пользователя к системе.
Пользователь с помощью них подсказывает системе где искать программы для запуска (`PATH`) и разделяемые библиотеки (`LD_LIBRARY_PATH`), каким количеством потоков запускать программу (`OMP_NUM_THREADS`).

% Также переменные дают удобный способ скрыть различия в окружении.

% Как переменные передаются программам?

Идея с переменными окружения реализована во всех современных ОС.
Несмотря на простую идею, существуют два небольших отличий, о которых следует знать при разработке кроссплатформенных программ.
* Имена переменных в UNIX чувствительны к регистру используемых символов, а в Windows -- нет.
  Это значит, что в UNIX переменные `PATH` и `Path` -- это две отдельные переменные, а в Windows -- одна переменная.
  Такое различие в требованиях также характерно языкам программирования Си и Fortran применительно к именам программных объектов -- переменным, функциям и процедурам, структурам и классам.
  В отличие от большинства языков программирования, обращение к отсутствующей переменной окружения вернет пустую строку, а не предупредит разработчика об ошибке в инструкции.
  Чтобы не попадаться на такие сложнообнаруживаемые ошибки, придержваются правилу использовать заглавные буквы в имени.
* В переменных, которые содержат список, для разделения элементов в UNIX используют символ `:`, а в Windows -- `;`.
  Из этого следует, что лучше воздержаться от использования этих специальных символов в других смыслах, чтобы не вести в заблуждение остальных разработчиков, пользователей или программ.
  Почему в Windows не используется разделитель `:`?
  Потому что он используется в названии диска (`C:`, `D:`) и таким образом входит в состав путей к файлам и каталогам.

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


## Управление переменными из командной строки

Командная оболочка `bash` дает удобный способ работы с переменными.
Рассмотрим три группы команд:
* просмотр;
* установку;
* удаление.

Команда `printenv` выводит на экран переменные окружения.
Ее вызов без аргументов выведет весь список, где каждая переменная выводится с новой строки в формате `name=value`.
Если передать ей в качестве аргумента имя переменной, то она выведет ее значение.

Вывод переменной чаще осуществляют командой `echo`, которая выводит на экран строки, переданные ей в качестве аргумента.
Чтобы строка с именем переменной превратилась в значение, ее предваряют символом `$`.
Символ `$` указывает оболочке bash интерпретировать последующие символы как имя переменной и заменить их значением этой переменной.

```bash
echo PATH=$PATH
```

:::{note}
Оболочка bash предлагает автоподстановку имен переменных, позволяющий ускорить ввод данных в терминале.
Если имя однозначно определяется по первым набранным символам, то нажав на клавишу `Tab` вы допишете оставшуюся часть имени.
Если набранным символам соответствует несколько имен, то второе нажатие на клавишу `Tab` покажет остальные варианты.
```bash
skt@home:~/MyProjects/sppo$ echo $GNOME_[Tab]
$GNOME_DESKTOP_SESSION_ID  $GNOME_TERMINAL_SCREEN
$GNOME_SHELL_SESSION_MODE  $GNOME_TERMINAL_SERVICE
skt@home:~/MyProjects/sppo$ echo $GNOME_
```
:::

Команда `export var=value` создает новую или перезаписывает старую переменную `var` со значением `value`.
Если значение содержит пробельный символ, то строка обрамляется кавычками: `var="My files"`.

:::{warning}
Обратите внимание, что присваивание не обрамлено пробелами.
В противном случае команда не сработает.
:::

Переменная окружения может хранить не только отдельное значение, но и список.
В списке значения друг от друга разделяются символом `:`.
Разделяющий символ не может быть использован в значениях переменной.
Переменная `PATH` содержит список каталогов с исполняемыми файлами.
Часто приходится добавить в нее новый каталог.
Это возможно сделать командой `export PATH=$PATH:new/path`.
Здесь также показано, что значения существующих переменных можно использовать для формирования новых.

Команда `unset var` удаляет переменную `var`.
Удаление системной переменной не приведет к серьезным проблемам, так как в любой момент их можно вернуть в начальное состояние, перезапустив оболочку.
Это значит, что рассмотренные выше команды, изменяют список переменных только у текущего процесса.
С его завершением изменения в переменных исчезают.

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

Установленная переменная окружения в терминале будет там находится до следующего перезапуска или явного удаления.
В последующем она будет передана всем запускаемым программам, засоряя таким образом пространство и, возможно, приведет к побочным эффектам.
Переменную можно передать явно только одной программе, и она исчезнет после этого, если добавить инициализацию переменной перед именем программы в той же строке: `var=name program`.


## Инициализация переменных системой

Переменные окружения система берет из конфигурационных файлов.
Система добавляет свои переменные в файл `/etc/environment`.
Системно значимые программы (интерпретатор `bash`, окружение рабочего стола `unity`, оконная система `X Server` и др.) добавляют в систему свои переменные, которые прописаны в их файлах настроек.
Для `bash` -- это `/etc/profile` и `/etc/bashrc`.
Пользователь может определить собственные переменные окружения.
Эти переменные прописываются в файлах настроек в домашнем каталоге.
Интерпретатор `bash` использует для этого файл `~/.bashrc`.

*Добавление переменных*.
Если пользователю нужны специальные настройки для запускаемых им программ, он может их записать как переменные окружения в файлах сценариев bash.
При запуске bash, они будут добавлены к списку доступных переменных.

<!--
Те переменные, которые относятся к системе и доступны всем пользователям, называются стандартными. Пользовательские переменные.

Переменные могут быть установлены ОС или пользователем и в зависимости от этого они бывают системными и пользовательскими. 
Системные переменные несут информацию об окружении, а пользовательские – информацию о пользователе и его предпочтения.
-->


## Примеры использования переменных окружения

В параллельных программах следует воздержаться от фиксации в исходном коде количества используемых потоков.
Вместо этого число следует вынести в интерфейс и предоставить пользователю самостоятельно влиять на степень загрузки системы.
В программах, распараллеленных технологией `OpenMP`, количество потоков в параллельных секциях берется из переменной окружения `OMP_NUM_THREADS`.
Запуск программы `program` на четырех потоках осуществляется командой `OMP_NUM_THREADS=4 ./program`.

Переменная `HOME` указывает путь к домашнему каталогу, а `USER` – имя пользователя.
Это позволяет персонализировать программу.
Она часто используется в сценариях для указания каталога установки `--prefix=$HOME`.
При установке файлы собранной программы скопируются в домашний каталог, не повлияв на других пользователей.
При развертывании программ на компьютере установщик предлагает добавить каталог с программой к значению системной переменной окружения `PATH`.
Это позволяет запускать программу из командной строки по имени, не указывая полный путь к исполняемому файлу.

Отладка программы на стороне пользователя представляет серьезную проблему для разработчика в связи с тем, что не доступна среда разработки, отладочные символы и не получится изучить внутреннее состояние программы.
Один из приемов увидеть состояние – это воспользоваться отладочным выводом, активируемым, например, переменной `DEBUG_OUTPUT=ON`.
Программа, заметив, что установлена переменная заранее оговоренным именем, сбрасывает в файл свое промежуточное состояние.
Действие переменной на программу можно ограничить во времени, если инициализировать ее меткой – временем действия, как, например, `DEBUG_OUTPUT=2022_11_30`.
Это позволит защититься от случая, когда разработчик забудет сбросить переменную на пользовательской машине, что повлияет на производительность программы.

Утилита `/bin/pwd` выводит на экран текущий каталог.
Запущенная из разных каталогов, она выведет разный результат.
Из какого источника утилита узнает путь к текущему каталогу?
Текущий каталог программы хранится в переменной PWD, которую меняет команда `cd`.
Утилита `pwd`, запускаясь оболочкой, наследует все ее переменные вместе с `PWD`.
А как команда `cd` влияет на переменную PWD оболочки, если запущенный процесс не имеет доступа к переменным родительского процесса?
Команда cd не может этого делать, если была бы внешней утилитой.
Поэтому она является встроенной в bash командой -- реализована в виде функции, которая меняет переменную окружения у своей программы.

<!-- EDITOR=/usr/bin/vim mc-->

## Вопросы для контроля

1. Что такое переменная окружения?
1. Какие переменные окружения вам известны?
1. Откуда система берет переменные окружения?


## Упражнения

1. Установите для текущей сессии новую переменную окружения `ABC_ENVIRONMENT` в значение `local`, отобразите ее на экране и удалите затем.
1. Как передать программе переменную окружения `DEBUG_OUTPUT=ON` так, чтобы она была доступна только этой программе, но не следующим запускаемым.
1. Добавьте переменную окружения `LOCAL_SDK`, доступную вашему пользователю при входе в систему.
   В какое значение?
1. Добавьте к переменной `PATH` каталог `/home/student/bin`.
1. Измените текущий каталог терминала, переопределив переменную `PWD`.
1. Удалите переменную `PATH`.
   Попробуйте выполнить команду `ls`.
   Почему так происходит?
   Обратите изменение.
   Почему команда `exit` сработала?
1. Установите переменной `MYLIST` список из значений `value1`, `value2` и `value3`.
   Затем удалите из списка значение `value2`.
1. Поменяйте цвет каталогов в выводе команды `ls` с темно-синего на более контрастное.
   Для этого обратите на переменную окружения `LS_COLORS`.
   Воспользуйтесь следующими подсказками: каталог на английский переводится как "directory"; дублирующий элемент в списке переопределяет предыдущий; если в аргументе есть символы, интерпретируемые как специальные, то такой аргумент следует обрамить кавычками.

<!-- Удаление и восстановление PATH в рамках одной сессии. -->
