Вперед Назад Содержание

10. Создание библиотеки инструментальных программных средств

Эта глава взята из `Linux Journal', том 1, номер 2, из статьи Арнольда Робинсона 'Что такое GNU?'.

Общие сведения о инструментальных программных средствах (утилитах)

В этой статье рассматривается проект GNU. Мы опишем ряд инструментальных средств GNU для Вашей системы Linux, и расскажем, как их можно использовать. Действительно, развивается ли философия "Инструментальных программных средств" ?

Философия инструментальных программных средств является важнейшей концепцией для реализации и развития UNIX (аналогами которой являются Linux и GNU). Современная пресса по работе в Интернет и мелькающими графическими пользовательскими интерфейсами обходит этот вопрос стороной. Это очень плохо, т.к. эта философия предоставляет мощнейшие ментальные модели для решения многих задач.

Многие люди носят в кармане шведский (складной) нож с несколькими лезвиями (как бы аналог библиотеки утилит). Он очень удобен для мелких повседневных нужд: для каждой проблемы в нем найдется лезвие (главное лезвие называется "миротворец").

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

Система UNIX разрабатывалась в Bell Labs профессиональными программистами и учеными-компьютерщиками. Они установили, что универсальные программы

Они пришли к выводу, что программы должны быть своеобразными инструментами. Короче, каждая программа должна безупречно делать что-то одно. Ни больше, ни меньше. Такие программы легче продумать, написать и отладить.

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

Переопределение ввода-вывода

Предполагается, что Вы знакомы с основными переопределениями ввода-вывода в интерпретаторе shell, особенно с понятием "стандартного ввода", "стандартного вывода" и "стандартной ошибки". Короче, "стандартный ввод" - это источник данных. Программе должно быть безразлично, откуда поступают данные, будь то файл, клавиатура, магнитофон или перфокарта. Аналогично, "стандартный вывод" - это приемник данных, и программа не должна заботиться, чем он может быть. Программа, которая просто читает из стандартного ввода, обрабатывает получаемые данные и выводит их, называется "фильтром". По аналогии с фильтрами в водопроводе.

C помощью shell-интерпретатора UNIX, можно легко управлять потоком данных:

program_to_create_data | filter1 | .... | filterN > final.pretty.data
Эта строка начинается с программы, генерирующей данные. Далее поток данных проходит через программы-фильтры до конца командного канала (цепочки команд).

Да, стандартный ввод и стандартный вывод это замечательно. Но откуда же берется стандартная ошибка? Рассмотрим фильтр `filter1' в нашем командном канале. Что происходит, если при обработке данных происходит ошибка? Если выводить сообщение об ошибке в стандартный вывод, то оно попадет в стандартный ввод фильтра `filter2', и пользователь может не узнать о нем. Т.е. программам необходимо место, куда можно послать сообщение об ошибке, и где пользователь может его заметить. Это и есть стандартная ошибка, и она выводится на экран, даже если Вы переопределите стандартный вывод не на монитор.

Для взаимодействия фильтров, необходимо заранее согласовать формат данных. Самый прямолинейный и легкий путь, использовать простые текстовые строки. В UNIX файлы данных это просто набор байтов, в строках разделенных ASCII символом перевода строки ("newline" или, если Вы программируете на Си то `'\n''). Это традиционный формат для всех программ-фильтров. (Большинство ранних операционных систем использовали программы обработки двоичных данных. UNIX уже отошол от этого, подразумевая, что гораздо удобнее просматривать и редактировать данные в текстовом редакторе.)

Теперь, можно приступать к делу. Ниже мы рассмотрим некоторые инструментальные программы, и увидим как обеспечить их взаимодействие. При этом, будут упомянуты только те опции командной строки, которые нас интересуют. Для полного понимания Вы должны трижды (!) прочитать Вашу системную документацию, и так Вы должны делать всегда! Команда `who' `who' выдает текущий список ползователей вошедьших в систему. Предположим, что в систему вошло несколько пользователей:

$ who arnold console Jan 22 19:57 miriam ttyp0 Jan 23 14:19(:0.0) bill ttyp1 Jan 21 09:32(:0.0) arnold ttyp2 Jan 23 20:48(:0.0)
Здесь `$' - это обычное приглашение оболочки. В систему вошло 3 пользователя, причем один из них вошел дважды. Имя пользователя не может содержать больше восьми символов. `who' выдает информацию довольно удобно, но при таком способе выводятся не все данные.

Команда `cut'

Следующая рассматриваемая команда - команда `cut'. Она выделяет определенные поля входных данных. Например, мы можем осуществить вывод только системного имени и полного имени из файла `/etc/passwd file'. В каждой строке файла `/etc/passwd' определено семь полей, разделенных символом ':':

arnold:xyzzy:2076:10:Arnold D. Robbins:/home/arnold:/bin/ksh
Для вывода только первого и пятого поля нам необходимо использовать `cut' следующим образом:
$ cut -d: -f1,5 /etc/passwd root:Operator ... arnold:Arnold D. Robbins miriam:Miriam A. Robbins ...
Если задана опция `-c', `cut' будет выводить указанные символы (колонки) входной строки. Эта команда удобна для фильтрации данных.

Команда `sort'

Теперь рассмотрим команду `sort'. Это одна из мощнейших команд в UNIX системах, особенно при сортировке потока данных. `sort' читает и сортирует каждый файл, указанный в командной строке. При этом она сливает сортируемые данные и выводит их в стандартный вывод. Если не указаны входные файлы она читает стандартный ввод. Сортировка основана на 'машинной' упорядоченной последовательности (ASCII) или на заданном пользователем критерии упорядочивания. Команда `uniq'

И, наконец, рассмотрим программу `uniq'. При сортировке, Вы встретитесь с повторяющимися строками. Обычно, Вам нужен только один экземпляр каждой строки. Здесь нужна команда `uniq'. Программа `uniq' читает стандартный ввод, который должен быть уже отсортированн. Она выводит только по одному экземпляру каждой повторяющейся строки. Она имеет несколько опций. Позже, мы используем опцию `-c', которая выводит нетождественные строки, с приписанным к началу строки числом повторений этой строки на входе. Параллельный запуск утилит Теперь, предположим, что наша система - большая электронная доска объявлений (BBS) с десятками пользователей вошедших в систему. Руководство советует системному оператору (сисопу) написать программу, которая выводит упорядоченный список вошедьших в систему пользователей. Более того, если кто-то вошел в систему несколько раз, его или ее имя должно только однажды упомиаться в списке.

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

$ who | cut -c1-8 arnold miriam bill arnold
И далее, отсортировать список:
$ who | cut -c1-8 | sort arnold arnold bill miriam
И наконец пропустив список через `uniq', отбросить повторения имен:
$ who | cut -c1-8 | sort | uniq
`uniq' есть другие возможности, которые не допускаются командой `sort -u'.

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

# cat > /usr/local/bin/listusers who | cut -c1-8 | sort | uniq ^D # chmod +x /usr/local/bin/listusers
Отметим четыре основных пункта.

Первый, состоит в том, что сисоп сэкономил два часа работы, записав несколько команд в одной командной строке. Более того, действие shell-интерпретатора не менее эффективно, чем было бы действие Си программы. Время программиста гораздо дешевле машинного времени, и в современном обществе, в котором "времени никогда не хватает", экономия двух часов личного времяни программиста это не подвиг.

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

В третьих, можно пошагово построить командный канал, как мы это уже проделали. Такой способ позволяет просматривать данные на каждом шаге и убедиться в правильнсти использования утилит.

Наконец, если поместить наш командный канал в файл, то им могут воспользоваться другие пользователи. И при запуске эти shell-файлы неотличимы от компилируемых программ.

Теперь мы можем рассмотреть более сложные командные каналы, а для этого необходимо ввести еще две утилиты.

Первая - это команда `tr'. Она работает по принципу символ-за-символ замены символов. Обычно, она используется для таких вещей, как преобразование символов верхнего регистра в символы нмжнего регистра:

$ echo ThIs ExAmPlE HaS MIXED case! | tr '[A-Z]' '[a-z]' this example has mixed case!
Для нас представляют интерес следующие опции:
`-c'

работает с дополнением списка символов, т.е. оперирует символами не указанными в списке;

`-d'

опускает при выводе символы указанные в первом списке;

`-s'

сжимает повторяющиеся символы в один символ.

Мы будем использовать все три опции сразу.

Следующая рассматриваемая команда это `comm'. Она читает отсортированные файлы и выводит их строки в три колонки. Колонки состоят из несовпадающих строк первого файла, несовпадающих строк второго файла, и общих строк этих файлов. Опции `-1', `-2', и `-3' подавляют вывод соответствующей колонки. Например:

$ cat f1 11111 22222 33333 44444 $ cat f2 00000 22222 33333 55555 $ comm f1 f2 00000 11111 22222 33333 44444 55555

Если вместо имени ф

из стандартного входа.

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

Сначала необходимо преобразовать символы всех регистров в символы какого-нибудь конкретного (у нас нижнего) регистра, т.к. "The" и "the" это одно и тоже слово.

$ tr '[A-Z]' '[a-z]' < whats.gnu | ...
Далее, отбросим пунктуацию. Это прстейший способ добиться того, чтобы слова в кавычках и без кавычек воспринимались одинаково.
$ tr '[A-Z]' '[a-z]' < whats.gnu | tr -cd '[A-Za-z0-9_ \012]' | ...
Вторая команда `tr' преобразует дополнение списка, содержащего пробел, символ подчеркивания, все буквы и цифры, в символ `\012' (перевода строки). Для удобства вывода можно включить в список еще и ASCII символ TAB.

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

$ tr '[A-Z]' '[a-z]' < whats.gnu | tr -cd '[A-Za-z0-9_ \012]' | > tr -s '[ ]' '\012' | ...
Эта команда заменяет пробелы на символы перевода строки. Опция `-s' сжимает последовательные символы перевода стрки в один. (Здесь символ `>' это shell-подсказка, которая указывает, что необходимо закончить список команд.)

Теперь у нас есть строки, содержащие (каждая) по одному слову, при чем все символы относятся к одному регистру. Подсчитаем число повторений каждого слова:

$ tr '[A-Z]' '[a-z]' < whats.gnu | tr -cd '[A-Za-z0-9_ \012]' | > tr -s '[ ]' '\012' | sort | uniq -c | ...
Получаемые данные будут выглядеть примерно так:
60 a 2 able 6 about 1 above 2 accomplish 1 acquire 1 actually 2 additional
Они отсортированны по словам, а не по числу повторений! Но мы хотим найти наиболее употребительные слова. Нам помогут еще две опции команды `sort': Итоговый командный канал выглядит так:
$ tr '[A-Z]' '[a-z]' < whats.gnu | tr -cd '[A-Za-z0-9_ \012]' | > tr -s '[ ]' '\012' | sort | uniq -c | sort -nr 156 the 60 a 58 to 51 of 51 and ...
Замечательно! Применяя уже известные нам принципы, мы создали полезную программу из шести команд. Для написания той же программы на Си потребовалось бы гораздо больше времени.

Немного изменив построенную цепочку команд, мы получим программу проверки орфографии, которая проверяет правильность написания слов, путем выделения тех слов, которых нет в словаре. Но, для этого нам нужен словарь. Если у Вас стоит "Slackware Linux" полностью, то у Вас есть файл `/usr/lib/ispell/ispell.words'. Это упорядоченный словарь на 38400 слов.

Но как связать наш файл со словарем? Ранее мы генерировали отсортированный список слов, помещенных в отдельные строки:

$ tr '[A-Z]' '[a-z]' < whats.gnu | tr -cd '[A-Za-z0-9_ \012]' | > tr -s '[ ]' '\012' | sort -u | ...
Сейчас, нам нужны все слова списка, которых нет в словаре. Применим здесь команду `comm'.
$ tr '[A-Z]' '[a-z]' < whats.gnu | tr -cd '[A-Za-z0-9_ \012]' | > tr -s '[ ]' '\012' | sort -u | > comm -23 - /usr/lib/ispell/ispell.words
Опции `-2' и `-3' отменяют вывод строк, которые есть только в словаре (т.е. во втором файле), и общих строк заданных файлов. Строки, представленные только в первом файле (здесь подразумевается стандартный ввод), это слова, которых нет в словаре. В найденных словах, судя по всему, присутствуют орфографические ошибки.

Следующие утилиты заслуживают особого внимания:

`grep'

ищет файлы, содержащие соответствия регулярному выражению;

`egrep'

аналогично `grep', но оперирует более мощными регулярными выражениями;

`wc'

подсчитывает строки, слова и символы;

`tee'

передает данные в стандартный вывод и, одновременно, копирует их в файл (напоминает Т-образное ответвление командного канала);

`sed'

пакетный редактор, сложная утилита;

`awk'

интерпретатор языка обработки строк, другая сложная утилита.

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

Короче:

  1. Каждая утилита должна безупречно делать что-то одно. Ни больше, ни меньше.
  2. Комбинирование таких утилит гораздо более действенно, чем использование универсальных программ, и позволяет использовать программы по новому (так, как авторы и представить не могли).
  3. Утилита не должна выводить заголовок и хвост, если данные посылаются по командному каналу. (Хотя, мы не всегда следовали этому правилу.)
  4. Пусть кто-то другой делает труднейшую часть работы.
  5. Разбирайтесь в своих утилитах (которые и есть ваши инструментальные программные средства)! Используйте их по назначению. А, если соответствующей утилиты нет, то создайте ее.
Все программы, которые мы обсудили доступны через анонимный `ftp' сервер в `prep.ai.mit.edu', в каталоге `/pub/gnu/textutils-1.9.tar.gz'.

В этой статье не написано ничего нового. Философию инстументальных программных средств впервые описали Брэйн Керниган и П.Дж.Плогер в книге "Инструментальные программные средства" (`Software Tools', Addison-Wesley, ISBN 0-201-03669-X). Эта книга рассказывает как писать и использовать инстументальные программные средства. Сама она была написана в 1976 году, с использованием препроцессора для Фортрана, названного `ratfor' (RATional FORtran). В то время Фортран был гораздо популярнее чем Си. Но препроцессор Фортрана похож на Си, и если Вы знаете Си, то с `ratfor'-программами у Вас проблем не будет.

В 1981 году книгу усовершенствовали и выпустили как "Инструментальные программные средства Паскаля" (`Software Tools in Pascal', Addison-Wesley, ISBN 0-201-10342-7). Обе книги остались в печати. Они полезны для программистов, и вообще, они коренным образом изменили отношение к программированию.

Изначально, программы представленные в книге можно было найти (на магнитной ленте) в издательстве "Addison-Wesley". Но теперь их можно достать только через Интернет. Ранее существовало общество пользователей инстументальных программных средств (Software Tools Users Group), члены которого перенесли исходные `ratfor' программы в каждую систему с Фортран компилятором.

Сейчас эти программы не популярны особенно по сравнению с GNU программами и другими аналогами UNIX программ. Тем не менее эти книги уникальны, как примеры хорошего стиля программирования. И последнее. Когда редактировалась эта статья в ходу была версия 1.9, так что сначала cправьтесь в ближайшем GNU архиве о текущей версии.


Вперед Назад Содержание