Опубликован: 22.06.2005 | Уровень: для всех | Доступ: свободно | ВУЗ: Компания IBM
Лекция 8:

Возможности командной оболочки

< Лекция 7 || Лекция 8: 123456 || Лекция 9 >

Окружение

В лекции 5 уже упоминалось, что системный вызов fork(), создавая точную копию родительского процесса, копирует также и окружение. Необходимость в " окружении " обусловлена вот какой задачей. Акт передачи данных от родительского процесса дочернему, и, что еще важнее, системе, должен обладать свойством атомарности. Если использовать для этой цели файл (например, конфигурационный файл запускаемой программы), всегда сохраняется вероятность, что за время между изменением файла и последующим чтением запущенной программой кто-то - например, другой процесс того же пользователя - снова изменит этот файл7Эта ситуация называется "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
Пример 8.9. Утилита date пользуется переменной окружения LC_TIME

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

Работа с переменными в 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
Пример 8.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
Пример 8.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!
Пример 8.12. Использование PATH

Пути, указанные в PATH, могут и не существовать на самом деле. Обнаружив в $PATH элемент /home/methody/bin (подкаталог bin домашнего каталога), Мефодий решил, что гораздо правильнее будет складывать исполняемые файлы не куда попало, а именно в этот каталог: во-первых, это упорядочит структуру домашнего каталога, а во-вторых, позволит запускать эти файлы по имени. Но для начала пришлось такой каталог создать. Порядок, при котором собственные программы лежат в специальном каталоге, куда безопаснее беспорядка, при котором поиск запускаемого файла по имени начинается с текущего каталога. Если в текущем каталоге окажется программа ls и этот каталог - не /bin, а, допустим, /home/shogun/dontrunme, тогда следует ожидать подвоха...

Переменных окружения, влияющих на работу разных утилит, довольно много. Например, переменные семейства LC_ (полный их список выдается командой locale ), определяющие язык, на котором выводятся диагностические сообщения, стандарты на формат даты, денежных единиц, чисел, способы преобразования строк и т. п. Очень важна переменная TERM, определяющая тип терминала: как известно из лекции 2, разные терминалы имеют различные управляющие последовательности, поэтому программы, желающие эти последовательности использовать, обязательно сверяются с переменной TERM 8В действительности такие программы обычно используют библиотеку curses, оперируя независящими от типа терминала понятиями (вроде "очистка экрана" или "позиционирование курсора"), а процедуры из curses преобразуют их в управляющие последовательности конкретного терминала, сверившись сначала с $TERM, а затем с содержимым соответствующего раздела базы данных по терминалам, которая называется terminfo .. Если какая-то утилита требует редактирования файла, этот файл передается программе, путь к которой хранится в переменной EDITOR (обычно это /usr/bin/vi, о котором речь пойдет в лекции 9). Наконец, некоторые переменные вроде UID, USER или PWD просто содержат полезную информацию, которую можно было бы добыть и другими способами.

Некоторые переменные окружения предназначены специально для bash: они задают его свойства и особенности поведения. Таковы переменные семейства PS (Prompt String). В этих переменных хранится строка-подсказка, которую командный интерпретатор выводит в разных состояниях. В частности, содержимое PS1 - это подсказка, которую shell показывает, когда вводит командную строку, а PS2 - когда пользователь нажимает Enter, а интерпретатор по какой-то причине считает, что ввод командной строки не завершен (например, не закрыты кавычки). С $PS2 (символом " > ") Мефодий уже сталкивался в лекциях 2 и 7:

[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:~ $
Пример 8.13. Использование переменной окружения PS1

Мефодий экспериментировал с PS1, параллельно читая документацию по bash (" (bash.info)Printing a Prompt "). Оказывается, в этом командном интерпретаторе содержимое PS1 не просто подставляется при выводе - оно еще и преобразуется, позволяя выводить всякую полезную информацию: имя пользователя (соответствует подстроке " \u ", user ), имя компьютера (" \h ", host ), время (" \t ", time ), путь к текущему каталогу (" \w ", work directory) и т. п. В примере Мефодий заменил " \W " (показывающую последний элемент пути, то есть собственное имя текущего каталога) на " \w ", полный путь, потому что " \w " обладает свойством выделять в полном пути домашний каталог и заменять его на " ~ ". Такое преобразование значений переменных семейства PS1 выполняется, только когда их использует bash в качестве подсказки, а при обычной подстановке этого не происходит.

< Лекция 7 || Лекция 8: 123456 || Лекция 9 >
Аягоз Имансакипова
Аягоз Имансакипова
Тимур Булатов
Тимур Булатов

С момента выхода курса прошло достаточно много времени, и хотелось бы понимать, насколько курс является актуальным на сегодняшний день.