Окружение
В лекции Доступ процессов к файлам и каталогам уже упоминалось, что системный вызов fork() , создавая точную копию родительского процесса, копирует также и окружение. Необходимость в «окружении» происходит вот из какой задачи. Акт передачи данных от этого конкретно родительского процесса дочернему, и, что ещё важнее, системе, должен обладать свойством атомарности. Если использовать для этой цели файл (например, конфигурационный файл запускаемой программы), всегда сохраняется вероятность, что за время между изменением файла и последующим чтением запущенной программой кто-то — например, другой процесс того же пользователя — снова изменит этот файл.
Эта ситуация называется «race condition» («состояние гонки»), и часто встречается в плохо спроектированных системах, где есть хотя бы два параллельных процесса.
Хорошо бы, чтобы изменение данных и их передача делались бы одной операцией. Например, завести для каждого процесса такой «файл», содержимое которого, во-первых, мог бы изменить только этот процесс, и, во-вторых, оно автоматически копировалось бы в аналогичный «файл» дочернего процесса при его порождении.
Эти свойства и реализованы в понятии «окружение». Каждый запускаемый процесс система снабжает неким информационным пространством, которое этот процесс вправе изменять как ему заблагорассудится. Правила пользования этим пространством просты: в нём можно задавать именованные хранилища данных (переменные окружения), в которые записывать какую угодно информацию (присваивать значение переменной окружения), а впоследствии эту информацию считывать (подставлять значение переменной). Дочерний процесс — точная копия родительского, поэтому его окружение — также точная копия родительского. Если про дочерний процесс известно, что он использует значения некоторых переменных из числа передаваемых ему с окружением, родительский может заранее указать, каким из копируемых в окружении переменных нужно изменить значение. При этом, с одной стороны, никто (кроме системы, конечно) не сможет вмешаться в процесс передачи данных, а с другой стороны, одна и та же утилита может быть использована одним и тем же способом, но в изменённом окружении — и выдавать различные результаты:
[methody@localhost methody]$ date Птн Ноя 5 16:20:16 MSK 2004 [methody@localhost methody]$ LC_TIME=C date Fri Nov 5 16:20:23 MSK 2004
Пример 9. Утилита date пользуется переменной окружения LC_TIME
окружение Набор данных, приписанных системой процессу. Процесс может пользоваться информацией из окружения для настройки, изменять и дополнять его. Окружение представлено в виде переменных окружения и их значений. При порождении процесса окружение родительского процесса наследуется дочерним (копируется).
Работа с переменными в shell
В последнем примере Мефодий воспользовался подсмотренным у Гуревича приёмом: присвоил некоторое значение переменной окружения в командной строке перед именем команды. Командный интерпретатор, увидев « = » внутри первого слова командной строки, приходит к выводу, что это — операция присваивания, а не имя команды, и запоминает, как надо изменить окружение команды, которая последует после. Переменная окружения LC_TIME предписывает использовать определённый язык при выводе даты и времени а значение « C » соответствует «стандартному системному» языку (чаще всего — английскому).
Если рассматривать shell в качестве высокоуровневого языка программирования, его переменные — самые обычные строковые переменные. Записать значение в переменную можно с помощью операции присваивания, а прочесть его оттуда — с помощью операции подстановки вида $переменная :
[methody@localhost methody]$ A=dit [methody@localhost methody]$ C=dah [methody@localhost methody]$ echo $A $B $C dit dah [methody@localhost methody]$ B=" " [methody@localhost methody]$ echo $A $B $C dit dah [methody@localhost methody]$ echo "$A $B $C" dit dah [methody@localhost methody]$ echo '$A $B $C' $A $B $C
Пример 10. Подстановка значений переменных
Как видно из примера, значение неопределённой переменной ( B ) в shell считается пустым и при подстановке не выводится никаких предупреждений. Сама подстановка происходит, как и генерация имён, перед разбором командной строки, набранной пользователем. Поэтому вторая команда echo в примере получила, как и первая два параметра (« dit » и « dah »), несмотря на то, что переменная B была к тому времени определена и содержала разделитель-пробел. А вот третья и четвёртая команды echo получили по одному параметру. Здесь сказалось различие между одинарными и двойными кавычками в shell: внутри двойных кавычек действует подстановка значений переменных.
Переменные, которые командный интерпретатор bash определяет после запуска, не принадлежат окружению, и, стало быть, не наследуются дочерними процессами. Чтобы переменная bash попала в окружение, её надо проэкспортировать командой export :
[methody@localhost methody]$ echo "$Qwe -- $LANG" -- ru_RU.KOI8-R [methody@localhost methody]$ Qwe="Rty" LANG=C [methody@localhost methody]$ echo "$Qwe -- $LANG" Rty -- C [methody@localhost methody]$ sh sh-2.05b$ echo "$Qwe -- $LANG" -- C sh-2.05b$ exit [methody@localhost methody]$ echo "$Qwe -- $LANG" Rty -- C [methody@localhost methody]$ export Qwe [methody@localhost methody]$ sh sh-2.05b$ echo "$Qwe -- $LANG" Rty -- C sh-2.05b$ exit
Пример 11. Экспорт переменных shell в окружение
Здесь Мефодий завёл новую переменную Qwe и изменил значение переменной окружения LANG , доставшейся стартовому bash от программы login . В результате запущенный дочерний процесс sh получил изменённое значение LANG и никакой переменной Qwe в окружении. После export Qwe эта переменная была добавлена в окружение и, соответственно, передалась sh .
Переменные окружения, используемые системой и командным интерпретатором
Во время сеанса работы пользователя стартовый командный интерпретатор получает от login довольно богатое окружение, к которому добавляет и собственные настройки. Просмотреть окружение в bash можно с помощью команды set . Большинство заранее определённых переменных используются либо самой командной оболочкой, либо утилитами системы, поэтому их изменение приводит к тому, что оболочка или утилиты начинают работать слегка иначе.
Весьма примечательна переменная окружения PATH . В ней содержится список каталогов, элементы которого разделяются двоеточиями. Если команда в командной строке — не собственная команда shell (вроде cd ) и не представлена в виде пути к запускаемому файлу (как /bin/ls или ./script ), то shell будет искать эту команду среди имён запускаемых файлов во всех каталогах PATH , и только в них. Точно так же будут поступать и другие утилиты, использующие библиотечную функцию execlp() или execvp() (запуск программы).
По этой причине исполняемые файлы невозможно запускать просто по имени, если они лежат в текущем каталоге, и текущий каталог не входит в PATH . Мефодий в таких случаях пользовался кратчайшим из возможных путей, « ./ » (например, вызывая сценарий ./script ).
[methody@localhost methody]$ echo $PATH /home/methody/bin:/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin:/usr/games [methody@localhost methody]$ mkdir /home/methody/bin [methody@localhost methody]$ mv to.sort loop script bin/ [methody@localhost methody]$ script Hello, Methody!
Пример 12. Использование PATH
Пути, указанные в PATH не обязаны существовать на самом деле. Обнаружив в $PATH элемент /home/methody/bin (подкаталог bin домашнего каталога), Мефодий решил, что гораздо правильнее будет складывать исполняемые файлы не куда попало, а именно в этот каталог: во-первых, это упорядочит структуру домашнего каталога, а в во-вторых, позволит запускать эти файлы по имени. Но для начала пришлось этот каталог создать. Порядок, при котором собственные программы лежат в специальном каталоге, куда безопаснее беспорядка, при котором поиск запускаемого файла по имени начинается с текущего каталога. Если в текущем каталоге окажется программа ls , и этот каталог — не /bin , а, допустим, /home/shogun/dontrunme , стоит ожидать подвоха.
Переменных окружения, влияющих на работу разных утилит, довольно много. Например, переменные семейства LC_ (полный их список выдаётся командой locale ), определяющие язык, на котором выводятся диагностические сообщения, стандарты на формат даты, денежных единиц, чисел, способы преобразования строк и т. п. Очень важна переменная TERM , определяющая тип терминала: как известно из лекции Терминал и командная строка разные терминалы имеют различные управляющие последовательности, поэтому программы, желающие эти последовательности использовать, обязательно сверяются с переменной TERM .
В действительности такие программы обычно используют библиотеку curses , оперируя не зависящими от типа терминала понятиями (вроде «очистка экрана» или «позиционирование курсора», а процедуры из curses преобразуют их в управляющие последовательности конкретного терминала, сверившись сначала с $TERM , а затем — с содержимым соответствующего раздела базы данных по терминалам, котороая называется terminfo .
Если какая-то утилита требует редактирования файла, этот файл передаётся программе, путь к которой хранится в переменной EDITOR (обычно это /usr/bin/vi , о котором речь пойдёт в лекции Текстовые редакторы). Наконец, некоторые переменные вроде UID , USER или PWD просто содержат полезную информацию, которую можно было бы добыть и другими способами.
Некоторые переменные окружения предназначены специально для bash : они задают его свойства и особенности поведения. Таковы переменные семейства PS (Prompt String). В этих переменных хранится строка-подсказка, которую командный интерпретатор выводит в разных состояниях. В частности, содержимое PS1 — это подсказка, которую shell показывает, когда вводит командную строку, а PS2 — когда пользователь нажимает Enter , а интерпретатор по какой-то причине считает, что ввод командной строки не завершён (например, не закрыты кавычки). С $PS2 (символом « > ») Мефодий уже сталкивался в лекциях Терминал и командная строка и Работа с текстовыми данными.
[methody@localhost methody]$ cd examples/ [methody@localhost examples]$ echo $PS1 [\u@\h \W]\$ [methody@localhost examples]$ PS1="--> " --> --> PS1="\t \w " 22:11:47 ~ 22:11:48 ~ 22:11:48 ~ PS1="\u@\h:\w \$ " methody@localhost:~/examples $ methody@localhost:~/examples $ methody@localhost:~/examples $ cd methody@localhost:~ $ methody@localhost:~ $
Пример 13. Использование переменной окружения PS1
Мефодий экспериментировал с PS1 , параллельно читая документацию по bash (« (bash.info)Printing a Prompt »). Оказывается, в этом командном интерпретаторе сожержимое PS1 не просто подставляется при выводе, оно ещё и преобразуется, позволяя выводить всякую полезную информацию: имя пользователя (соответствует подстроке « \u », user), имя компьютера (« \h », host), время (« \t », time), путь к текущему каталогу (« \w », work directory) и т. п. В примере Мефодий заменил в конце концов « \W » (показывающую последний элемент пути, то есть собственное имя текущего каталога) на « \w », полный путь, потому что « \w » обладает свойством выделять в полном пути домашний каталог и заменять его на « ~ ». Такое преобразование значений переменных семейства PS1 происходит только когда их использует bash в качестве подсказки, а при обычной подстановке этого не происходит.