Лабораторная работа №5

Переменные и конвейер

 

Переменные

До сих пор мы имели дело с параметрами, передававшимися команде в виде аргументов, следующих за именем команды. Команда интерпретирует аргументы исходя из их значений (так, большинство команд считает аргумент, начинающийся с дефиса, ключом) и их позиций (так, команды ср и mv последний операнд считают целевым файлом или каталогом, а предшествующие — источниками), поэтому аргументы называют еще позиционными параметрами.

В открытых системах существует еще один механизм передачи параметров — переменные. В отличие от аргументов, переменные являются именованными параметрами, и их семантика определяется не их позицией и значением, но именем.

Рис. 1

В примере на рис. 1 мы сначала подаём команду ls с несуществующим файлом в качестве аргумента и получаем сообщение об ошибке на русском языке. Затем мы подаём ту же команду, предварив ее конструкцией LC_ALL=C, и получаем сообщение о той же ошибке на английском языке.

Конструкция, состоящая из имени переменной и ее значения, разделенных знаком равенства ("=") без промежутков, и является определением параметра-переменной для вызываемой команды. В данном случае определяется переменная LC_ALL, которой присваивается значение "С". Переменная LC_ALL является одной из стандартных, ее значение определяет язык и другие национально-специфические особенности интерфейса (эту и несколько других переменных локали мы подробнее рассмотрим ниже).

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

Если нам нужно изменить подобным образом (получать сообщения на английском языке) самую оболочку, следует установить значение переменной такой же конструкцией из имени и значения переменной, разделенных знаком равенства, за которыми не следует никакой команды. Присвоение значения переменной само по себе не влияет на поведение вызываемых из оболочки команд, т.е. эта переменная им не передается (рис. 2).

 

Рис.2

Чтобы значение переменной передавалось всем вызываемым командам, ее следует сделать передаваемой (экспортировать ее) командой export с именем переменной в качестве аргумента (см. рис. 3).

Рис. 3

Установленное значение (вне зависимости от того, экспортировано ли оно) сохраняется до конца сеанса работы с оболочкой, до его переустановки или до уничтожения переменной командой unset с именем переменной в качестве аргумента.

Смысл механизма переменных как минимум двояк. Во-первых, крайне удобна возможность единообразного изменения поведения определенной группы команд, команд, поданных в течение определенного сеанса, или команд в сеансах определенного пользователя (механизм ключей для этого, очевидно, слишком громоздок). Во-вторых, язык оболочки — не только язык интерактивного взаимодействия с системой, но и императивный язык программирования.

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

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

Переменная будет раскрыта оболочкой (подобно тому, как оболочка раскрывает значения специальных символов в именах файлов), если указать ее в любом месте любой команды в окружении фигурных скобок "{" и "}", предваренной знаком денежной единицы "$". В большинстве случаев (когда не возникает неоднозначности в интерпретации) фигурные скобки можно опустить (рис. 4).

Рис. 4

Получить список переменных, установленных в данный момент времени, можно командой set без аргументов, а список экспортированных переменных — командой env без аргументов. В типичной системе при запуске оболочки устанавливаются значения нескольких десятков переменных, большинство из которых сразу экспортируются. В примере на рис. 5 эти списки сильно сокращены. С семантикой отдельных элементов мы познакомимся ниже.

 

                                                                                        Рис. 5

 

Стандартом определен ряд переменных, имеющих фиксированную семантику, значения которых используются стандартными командами. Они представлены в таблице на риc. 6. В большинстве случаев пользователю не нужно устанавливать их значения самостоятельно.

Кроме того, стандарт не рекомендует произвольно переопределять еще ряд переменных: ARFLAGS,CC,CDPATH,CFLAGS,CHARSET,DEAD,EDITOR,  ENV,EXINIT,FC,FCEDIT,FFLAGS, GET, GFLAGS, HISTFILE, HISTORY, HISTSIZE,IFS,LDFLAGS,LEX, LFLAGS,LINENO, LISTER, LPDEST,MAIL, MAILCHECK,MAILER,MAILPATH,MAILRC, MAKEFLAGS,MAKESHELL, MANPATH,MBOX,MORE, MSGVERB, PROC, OLDPWD, OPTARG, OPTERR, OPTIND,  PAGER, PPID, PRINTER, PROCLANG, PROJECTDIR, RANDOM,SECONDS, TERMCAP, TERMINFO, USER, VISUAL, YACC, YFLAGS. Некоторые из них используются самой оболочкой, некоторые — стандартными командами, а некоторые — прикладными и инструментальными программами.

Рис. 6

 

 

Локаль, ”и17я” и “л9я”

Локалью (locale — "местонахождение") называется совокупность переменных, управляющих поведением оболочки, команд и других программ в части языковых и национально-культурных особенностей. Локалью также называются и значения, которые принимают эти переменные (кроме NLSPATH).

В любой стандартной ОС определены (совпадающие) локали "POSIX" и "С". Эта локаль называется "системной", и во всех известных нам системах она соответствует соглашениям, принятым в США (Строго говоря, стандарт не говорит об этом прямо, но ограничивает набор символов стандартной локали семибитной латиницей, так что, кроме американского или британского английского, полностью реализовать стандартную локаль, не прибегая к транслитерации, можно разве что на латыни).

В ОС может быть также определено произвольное количество локалей, именуемых следующим образом: двухбуквенное ИСО-сокращение названия страны, за которым слитно следует знак подчеркивания "_" и — слитно же — двухбуквенное ИСО-сокращение названия языка. Далее могут следовать (также слитно) точка и наименование кодовой таблицы. Для русского языка и российских культурных особенностей значением локали будет "ru_RU.KOI8-R" или "ru_RU.ISO8859-5".

Текущую локаль можно узнать, подав команду locale. Обычно всем переменным локали (кроме NLSPATH), перечисленным на рис. 7, присваивается одно и то же значение (это можно сделать, установив значение всего лишь одной переменной, LC_ALL ). Однако бывают и другие случаи: например, иностранный сотрудник или студент может предпочесть сообщения и диалоги на родном языке, а остальные национально-культурные параметры — соответствующими стране пребывания.

Рис. 7

 

Интернационализованной называется программа (включая стандартные команды), корректно изменяющая свое поведение в соответствии с переменными локали.

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

Для длинных слов "localization" и "internationalization" иногда даже в формальных документах используются сокращения "l10n" и "il8n" (цифры между первой и последней буквами образуют число пропущенных букв).

Стандартный набор команд (и многие дополнительные программы), входящие в состав популярных дистрибутивов открытых систем, в массе своей интернационализованы, но с русской локализацией ситуация весьма неоднозначная. Вы столкнетесь с ситуациями, когда перевода того или иного ресурса (например, сообщений об ошибках и диагностики) для определенной программы не окажется. В этом случае корректно интернационализованная программа "откатится" к системной локали "С".

Вы также можете столкнуться и с некорректно интернационализованными программами (такое случается с прикладным кодом, перенесенным с альтернативных платформ, или разработанным без оглядки на локаль), которые игнорируют локаль, ведут себя странно или отказываются работать при локали, отличной от "С".

К сожалению, при применении наиболее популярного сегодня "IBM PC-совместимого" оборудования (т.е. компьютеров на основе процессоров архитектуры IA-32) реальная локализация аппаратно-программной системы не сводится к установке локали. Для возможности ввода нелатинских символов необходимо назначить код переключения систем письменности какой-либо клавише. Кроме того, чтобы терминал отображал нелатинские символы в текстовом режиме, в видеоадаптер должен быть подгружен соответствующий шрифт (при применении эмулятора терминала в графическом режиме этой проблемы нет). Стандартного способа осуществления этих действий нет, каждая ОС решает эти задачи по-своему.

Еще одна локализационная сложность связана с ограничением восьмибитной кодировки: размер одного набора символов ограничен 256, и, соответственно, за вычетом управляющих символов и символов псевдографики места в нем хватает только на обычную и расширенную латиницу, обычную латиницу и русскую кириллицу или обычную латиницу и украинскую кириллицу.

Работать с текстами, включающими одновременно большее количество символов, можно, используя кодовые таблицы Юникод (стандарт ИСО/МЭК 10646) и кодировку UTF8. Однако не все ОС поддерживают корректную работу текстовых утилит при переменной длине символов. Кроме того, применять при этом вшитый знакогенератор видеоадаптера уже не представляется возможным, и для терминального режима нужен встроенный в драйвер программный знакогенератор. Проблемы, связанные с UTF8-локалью, должны быть решены в современных ОС в течение ближайших лет.

 

Переменная $PATH и команда как файл

Обратите внимание на переменную $РАТН (у нас ее значение оказалось равным /home/user/bin:/bin:/usr/bin:/usr/local/bin:/usr/XI1R6/bin:/usr/games). Значение переменной $РАТН интерпретируется оболочкой как список имен каталогов, разделенных двоеточиями. Когда оператор вводит команду, оболочка просматривает эти каталоги в поисках исполняемого файла с именем, совпадающим с именем введенной команды.

Большинство стандартных команд ОС реализовано в виде отдельных программ (исключение составляют так называемые встроенные команды). Чтобы оболочка нашла и запустила соответствующую программу, путь к ней (т.е. имя каталога, в котором содержится эта программа) должен содержаться в списке, составляющем переменную $РАТН. Текущий каталог не исключение — чтобы поиск программы осуществлялся и в нем, он должен в явном виде присутствовать в этом списке.

Рис.8

В приведенном на рис. 8 примере мы проверяем значение $РАТН, затем добавляем в начало списка текущий каталог и убеждаемся, что значение переменной приняло искомую форму. (Этот пример демонстрирует особенности экранирования, выполняемого двойными кавычками. Они экранируют пробелы, но, в отличие от апострофов, не мешают раскрытию специальных символов и имен переменных.)

Хотя стандартом не определены каталоги, в которых содержатся исполняемые файлы команд, в большинстве современных ОС основным таким каталогом является /usr/bin/.

Добавление в PATH текущего каталога считается весьма легкомысленным; по крайней мере суперпользователь никогда не должен этого делать. Удобным является присутствие в PATH каталога /bin/ (в нашем примере /home/user/bin/). В него пользователь может помещать собственные сценарии и другие программы.

Команду, соответствующую программе, чей код размещен вне путей, перечисленных в PATH, можно издать, указав полное или относительное имя файла (например, команда./моя_программа запустит программу, содержащуюся в файле моя_программа в текущем каталоге), Файл должен быть исполняемым.

 

Конвейер

Помимо использования символов завершения ("&", ";". "&&" и "||") и скобок "(" и ")", в открытых ОС имеется еще один механизм объединения простых команд в группу — конвейер.

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

В следующем примере нам по каким-то причинам нужно получить пронумерованный список файлов в одном из каталогов. У команды ls нет соответствующего ключа, хотя есть ключ -1 (единица), позволяющий осуществить вывод списка в "коротком" формате по одному файлу на строку. Но в системе имеется стандартная команда nl, выводящая строки ввода, предваренные их номерами.

Мы можем перенаправить вывод команды ls -1 во временный файл, затем перенаправить ввод команды nl из того же файла и, наконец, удалить его (рис.9).

 

                                                       Рис. 9

Это достаточно громоздко; кроме того, в вывод попало и имя самого временного файла, что в наши планы не входило.

Конвейер — это соединение двух или более команд символом "|" ("вертикальная черта", "пайп"). При связывании команд конвейером вывод указанной слева становится вводом указанной справа, без каких-либо временных файлов (рис. 10).

Рис. 10

В конвейер могут быть связаны и более двух команд. В примере на рис. 11 мы передаем, как и ранее, вывод команды ls команде nl, а вывод nl передает команде sort -r, которая (с этим ключом) выводит свой ввод, отсортированный в обратном порядке.

Рис. 11

Ввод (но не вывод) первой команды в конвейере может быть перенаправлен из файла посредством символа "<", а вывод (но не ввод) последней — перенаправлен в файл или в конец файла символами ">.", ">>". Перенаправлять стандартный ввод или вывод команд, окруженных символами конвейера с обеих сторон, бессмысленно, хотя можно (и бывает полезно) перенаправить в файл вывод их ошибок ("2>" или "2>>").

В некоторых случаях бывает все-таки необходимо вывести "сечение" конвейера в определенной точке. Для этого служит команда tee, копирующая ввод в вывод и параллельно записывающая его в файл, имя которого указано в качестве ее аргумента. При необходимости осуществить вывод на терминал необходимо в явном виде указать его имя (/dev/tty) (рис. 12).

Рис. 12

Приведенный пример повторяет уже встречавшуюся цепочку из трех команд, но итоговый вывод перенаправлен в файл "временный", а между командами nl и sort вставлена команда tee /dev/tty, копирующая поток конвейера на текущий терминал. На терминал, соответственно, выводится последовательность строк после их нумерации, но до ее обратной сортировки.

Вне зависимости от включения конвейера в скобки все запускаемые при этом процессы входят в одну группу процессов (одно задание).

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

В "доюниксовых" системах пришлось бы предвидеть возможность того, что нам понадобится нумерация строк и обратная сортировка списка файлов в каталоге, и включать соответствующие ключи в реализацию команды ls. Что еще хуже, их реализацию пришлось бы включать в каждую программу, построчно обрабатывающую тексты (или убедить нас в том, что "на самом деле такая возможность не нужна").

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

Наличие таких механизмов и "дешевизна" (в терминах потребления компьютерных ресурсов) их применения обусловили складывание вокруг открытых систем культуры разработки (не только системной, но и прикладной), частью которой являются принципы 1) минимизации функциональности отдельных программ, 2) простоты форматов ввода-вывода и 3) реализации программ в виде фильтров (то есть преобразующих стандартный ввод в стандартный вывод) везде, где это возможно.

Наиболее очевидным примером реализации этих принципов является набор текстовых утилит, входящих в стандарт, некоторые из которых (cat, nl, sort) были уже бегло рассмотрены нами.

 

 

[Курс] [Помощь]