1.8 Конвейер
1.8 Конвейер
Помимо использования символов завершения («&», «;», «&&» и «||») и скобок «(» и «)», в открытых ОС имеется еще один механизм объединения простых команд в группу — конвейер[54].
В отличие от групп команд, объединенных упомянутыми символами завершения и независимых друг от друга, команды, входящие в конвейер, связаны передачей данных.
В следующем примере Алисе по каким-то причинам нужно получить пронумерованный список файлов в одном из каталогов. У команды «ls» нет соответствующего ключа, хотя есть ключ «-1» (единица), позволяющий осуществить вывод списка в «коротком» формате по одному файлу на строку. Но в системе имеется стандартная команда «nl», выводящая строки ввода, предваренные их номерами.
Алиса может перенаправить вывод команды «ls -1» во временный файл, затем перенаправить ввод команды «nl» из того же файла и, наконец, удалить его (Рис. 1-67).
Рис. 1-67
Это достаточно громоздко; кроме того, в вывод попало и имя самого временного файла, что в планы Алисы не входило[55].
Конвейер — это соединение двух или более команд символом «|» («вертикальная черта», «пайп»). При связывании команд конвейером, вывод указанной слева становится вводом указанной справа, без каких-либо временных файлов (Рис. 1-68).
Рис. 1-68
В конвейер могут быть связаны и более двух команд. В примере на Рис. 1-69 Алиса передает, как и ранее, вывод команды «ls» команде «nl», а вывод «nl» передает команде «sort -r», которая (с этим ключом) выводит свой ввод, отсортированный в обратном порядке.
Рис. 1-69
Ввод (но не вывод) первой команды в конвейере может быть перенаправлен из файла посредством символа «<», а вывод (но не ввод) последней — перенаправлен в файл или в конец файла символами «>», «>>». Перенаправлять стандартный ввод или вывод команд, окруженных символами конвейера с обеих сторон, бессмысленно[56], хотя можно (и бывает полезно) перенаправить в файл вывод их ошибок («2>» или «2>>»).
В некоторых случаях бывает все-таки необходимо вывести «сечение» конвейера в определенной точке. Для этого служит команда «tee», копирующая ввод в вывод и параллельно записывающая его в файл, имя которого указано в качестве ее аргумента. При необходимости осуществить вывод на терминал, необходимо в явном виде указать его имя (/dev/tty) (см. Рис. 1-70).
Рис. 1-70
Приведенный пример повторяет уже встречавшуюся цепочку из трех команд, но итоговый вывод перенаправлен в файл «временный», а между командами «nl» и «sort» вставлена команда «tee /dev/tty», копирующая поток конвейера на текущий терминал. На терминал, соответственно, выводится последовательность строк после их нумерации, но до ее обратной сортировки.
Вне зависимости от включения конвейера в скобки, все запускаемые при этом процессы входят в одну группу процессов (одно задание).
Введение механизма конвейера и его эффективная реализация в ранних версиях ОС «Юникс» революционализировали практику программирования, в том числе, системного. Именно благодаря наличию этого механизма на уровне ОС стала возможна аккуратная декомпозиция реальных задач на относительно простые фрагменты-утилиты.
В «доюниксовых» системах пришлось бы предвидеть возможность того, что какой-нибудь Алисе понадобится нумерация строк и обратная сортировка списка файлов в каталоге, и включать соответствующие ключи в реализацию команды «ls». Что еще хуже, их реализацию пришлось бы включать в каждую программу, построчно обрабатывающую тексты (или убедить Алису в том, что «на самом деле такая возможность не нужна»)[57].
Декомпозицией задач и предоставлением «тезауруса» отдельных команд вкупе с механизмами их связывания и объясняется простота и элегантность открытых ОС. Поскольку большинство команд вводят и/или выводят текст, существует практически неограниченная возможность их комбинации, отвечающей как предвиденным, так и непредвиденным разработчиками ситуациям.
Наличие таких механизмов и «дешевизна» (в терминах потребления компьютерных ресурсов) их применения обусловили складывание вокруг открытых систем культуры разработки (не только системной, но и прикладной), частью которой являются принципы:
1) минимизации функциональности отдельных программ,
2) простоты форматов ввода-вывода и
3) реализации программ в виде фильтров (то есть, преобразующих стандартный ввод в стандартный вывод) везде, где это возможно.
Наиболее очевидным примером реализации этих принципов является набор текстовых утилит, входящих в стандарт, некоторые из которых («cat», «nl», «sort») были уже бегло рассмотрены нами.