Искусство программирования для Unix
Шрифт:
Программа pic(1) подробнее рассматривается в главе 8 при обсуждении конструкций мини-языков.
7.2.2.4. Учебный пример: утилиты bc(1) и dc(1)
Частью классического инструментального набора, происходящего из Unix Version 7, является пара программ-калькуляторов. Программа dc( 1) представляет собой простой калькулятор, принимающий на стандартный ввод текстовые строки, состоящие из обратных польских записей (Reverse-Polish Notation— RPN),
Хотя современная GNU-реализация Ьс(1) является автономной, ее классическая версия передавала команды в программу dc( 1) посредством канала. В этом разделении труда утилита Ьс( 1) осуществляет подстановку значений переменных, разложение функций и преобразование инфиксной записи в обратную польскую, но сама, по существу, не выполняет вычислений. Вместо этого результаты RPN-преобра-зования входных выражений для расчета передаются программе dc(1).
Такое разделение функций имеет очевидные преимущества. Это означает, что пользователям приходится выбирать предпочтительную форму записи, но дублировать логику для числовых расчетов с произвольной точностью (умеренно сложную) не требуется. Каждая из двух программ может быть менее сложной, чем один калькулятор с выбором формы записи. Отладку и мысленное моделирование можно осуществлять независимо для каждого компонента.
В главе 8 данные программы рассматриваются в несколько другом свете, как узкоспециальные мини-языки.
7.2.2.5. Контрпример: почему программа fetchmail не выполнена в виде конвейера
В понятиях Unix fetchmail является неудобной большой программой, изобилующей различными параметрами. Рассматривая способ транспортировки почты, можно предположить, что данную программу можно было бы разложить на компоненты конвейера. Предположим, что она разбита на ряд программ: несколько программ доставки для получения почты с РОРЗ- и IMAP-узлов, и локальный SMTP-инжектор. Конвейер мог бы передавать почтовый формат Unix. Существующую сложную конфигурацию fetchmail можно было бы заменить сценарием оболочки, содержащим строки команд. В такой конвейер можно также добавить фильтры для блокировки спама.
#!/bin/sh
imap jrandom®imap.ccil.org | spamblocker | smtp jrandom imap jrandom@imap.netaxs.com | smtp jrandom # pop ed@pop.terns.com | smtp j random
Такая конструкция была бы весьма изящной и соответствовала бы духу Unix, но не смогла бы работать. Причина рассматривалась выше — конвейеры являются однонаправленными.
Одной из функций программы доставки (imap или pop) было бы принятие решения о том, отправлять ли запрос на удаление каждого принимаемого сообщения. В существующей организации fetchmail отправка такого запроса POP- или IMAP-серверу может быть задержана до тех пор, пока программа не получит подтверждения о том, что локальный SMTP-слушатель взял на себя ответственность за данное сообщение. Версия программы, организованная в виде конвейера из небольших компонентов,
Рассмотрим, например, последствия аварийного завершения smip-инжектора вследствие того, что SMTP-получатель сообщил о переполнении диска. Если программа доставки уже удалила почту, сообщения будут утеряны. Это означает, что программа доставки не может удалять почту до тех пор, пока не получит соответствующее уведомление от smtp-инжектора. Причем с данной проблемой связан ряд вопросов. Каким образом программы обменивались бы данными? Какое в точности сообщение было бы возвращено инжектором? Общая сложность такой системы и ее подверженность неочевидным ошибкам были бы выше, чем сложность монолитной программы.
Конвейеры являются превосходными инструментами, но они не универсальны.
7.2.3. Упаковщики
Противоположностью вызова с созданием подоболочки является упаковщик (wrapper). Упаковщик создает новый интерфейс для вызываемой программы или определяет его. Часто упаковщики используются для сокрытия деталей сложных shell-конвейеров. Упаковщики интерфейсов обсуждаются в главе 11. Наиболее специализированные упаковщики являются достаточно простыми и, тем не менее, весьма полезными.
Как и в случае вызова с созданием подоболочки, связанного протокола не существует, поскольку программы не обмениваются данными во время выполнения вызываемой программы. Однако обычно упаковщик существует для указания аргументов, изменяющих поведение этой программы.
7.3.2.1. Учебный пример: сценарии резервного копирования
Специализированные упаковщики представляют собой классический пример использования Unix shell и других языков сценариев. Одним из распространенных и характерных видов специализированных упаковщиков является сценарий резервного копирования. Он может быть однострочным, таким же простым, как приведенный ниже.
tar -czvf /dev/stO
Приведенная выше команда является упаковщиком для утилиты архивирования tar(1), который предоставляет один фиксированный аргумент (накопитель на магнитных лентах /dev/stO) и передает tar все остальные аргументы, указанные пользователем ("$@")50.
7.2.4. Оболочки безопасности и цепи Бернштайна
Один из распространенных способов использования сценариев упаковщиков — это создание оболочек безопасности (security wrappers). Сценарий безопасности может вызывать программу-диспетчер (gatekeeper) для проверки мандата (credential), а затем в зависимости от значения, возвращенного программой-диспетчером, запустить другую программу.
Образование цепей Бернштайна (Bernstein chaining) представляет собой специализированную методику применения оболочек безопасности, впервые разработанную Даниелем Бернштайном (Daniel J. Bernstein), который задействовал ее в многих своих пакетах. (Подобная модель наблюдается в таких командах, как nohup(1) иsu(1), но условность выполнения отсутствует.) Концептуально цепи Бернштайна подобны конвейерам, но, в отличие от последних, в цепях каждый успешный этап заменяет предыдущий, а не выполняется одновременно с ним.