Программные фильтры
В UNIX существуют программы, получающие данные из стандартного потока ввода (stdin) и выводящие результаты свое работы в стандартный поток вывода (stdout). Общее название таких программ – программы-фильтры (или просто фильтры). Фильтр обычно (но не всегда) умеет также читать данные из файлов, имена которых были переданы ему в качестве аргументов, причем в этом случае чтение из файлов происходит именно в том порядке, в каком имена файлов были указаны в качестве параметров. Совместно с возможностью перенаправления стандартных потоков (stdin, stdout и stderr), использование программ-фильтров предоставляет пользователю большие возможности по обработке информации в командной строке или скриптах.
Фильтр cat
Программа cat является классическим представителем фильтров. То, что прочитано из stdin или из файлов, выводится в stdout. Несмотря на кажущуюся простоту такого действия, использование cat позволяет пользователю быстро и эффективно решать необходимые задачи. Например, если необходимо просмотреть содержимое файла, нет нужды запускать пейджер или, тем более, редактор:
$ cat /etc/fstab # Device Mountpoint FStype Options Dump Pass# /dev/ad0s1b none swap sw 0 0 /dev/ad0s1a / ufs rw 1 1 /dev/ad0s1e /tmp ufs rw 2 2 /dev/ad0s1f /usr ufs rw 2 2 /dev/ad0s1d /var ufs rw 2 2 /dev/acd0 /cdrom cd9660 ro,noauto 0 0
Для добавления строки в конец файла file1:
$ cat >> file1 new line
Опции, используемые с командой cat можно увидеть на странице man.
Показать последние строки файла — tail
Программа tail выводит на stdout последние строки файла (без опции n выводится 10 строк):
Последние 5 строк:
$ tail -n 5 /var/log/messages
Последние строки, начиная с 10-й:
$ tail -n +10 /var/log/messages
Динамически отслеживать запись в файл:
$ tail -F /var/log/messages
Показать первые строки файла — head
Программа head выводит на stdout первые строки файла (без опции n выводится 10 строк):
Первые 5 строк:
$ head -n 5 /var/log/messages
Выбор фрагментов строк — cut
Программа cut вырезать из входного потока или файла фрагменты строк, соответствующие определенным позициям символов в строке (опция с) или определенным полям (опция f). В последнем случае считается, что файл имеет табличную структуру (т. е. разбит на колонки) и программе требуется указать разделитель полей с помощью опции d (разделитем по умолчанию является символ табуляции, в этом случае опция d не нужна).
Примеры:
$ cut -d: -f1 /etc/passwd $ cut -d: -f1,7 /etc/passwd $ cut -d: -f13 /etc/passwd $ cut -c1-5 /etc/passwd
Сортировка файлов — sort
Программа sort позволяет сортировать данные в алфавитном или числовом порядке (опция -n). Подробно с опциями sort можно ознакомится на странице справочного руководства.
Пример сортировки пользователей в системе по алфавиту:
$ grep -v '^#' /etc/passwd | sort
Пример сортировки пользователей в системе по значению UID:
$ grep -v '^#' /etc/passwd | sort t: -k3 -n
Конвейеры команд
Конвейер — некоторое множество процессов, для которых выполнено следующее перенаправление ввода-вывода: то, что выводит на поток стандартного вывода предыдущий процесс, попадает в поток стандартного ввода следующего процесса. Запуск конвейера реализован с помощью системного вызова pipe().
Оператор конвейера позволяет соединить две UNIX-команды для выполнения нужных программ непосредственно в командной строке. Однако конвейер похож на черный ящик, скрывающий процесс передачи данных от одной утилиты к другой. Программа Pipe Viewer позволяет взглянуть на поток данных, проходящих через конвейер.
С помощью конвейера команд можно конструировать импровизированные программы непосредственно в командной строке. Конвейер собирает команды в цепочки, в которых стандартный вывод одной команды становится стандартным вводом для следующей команды.
Но у конвейера есть один серьезный недостаток: он похож на черный ящик. Когда вы соединяете две команды, единственным признаком хода выполнения процесса является вывод, генерируемый последней в последовательности командой. Можно вставить в последовательность команду tee, а также наблюдать за ростом выходного файла с помощью tail, но эти решения отлично работают только при однократном применении в команде, в противном случае стандартные потоки вывода (stdout) и стандартные потоки ошибок (stderr) различных фаз перемешиваются. К тому же оба эти решения являются грубыми индикаторами, которые, вероятно, не покажут, сколько в действительности вычислений требуется на каждом шаге.
Конечно, можно разбить сложную последовательность команд на множество отдельных шагов, каждый из которых будет генерировать свой собственный выходной файл. В самом деле, если вы хотите проверять результат работы на каждом интервале, декомпозиция является идеальным решением. Создайте сценарий, в котором на каждом шаге генерируйте отдельный файл с данными и используйте его в качестве входных данных для следующего шага и т.д. В результате вы получите файл с итоговыми результатами. Однако такая практика плохо подходит к импровизационной природе командной строки.
То, что нам нужно – это индикатор прогресса выполнения задачи, который можно встраивать в командную строку для измерения скорости передачи данных. В идеале хорошо было бы использовать этот индикатор повторно, чтобы сравнивать производительность на каждом шаге. Ну и, поскольку нет предела совершенству, хорошо бы, чтобы это был инструмент с открытым кодом, который бы работал с различными вариантами UNIX, такими как Linux и Mac OS X.
Всем нашим пожеланиям удовлетворяет программа Pipe Viewer (pv), написанная системным администратором Эндрю Вудом и улучшаемая на протяжении последних четырех лет многими другими разработчиками. Она позволяет заглянуть внутрь "трубопровода" командной строки. « Можно вставлять в конвейер между двумя процессами для получения визуальной индикации того, как быстро передаются между ними данные, как много времени прошло и насколько близко завершение работы". Заметим, что для измерения относительной производительности можно использовать pv несколько раз в одной последовательности команд.
Конвейеры UNIX: трубопровод для процессов
На рисунке 1 показаны этапы создания конвейера, соединяющего два независимых процесса.
Рисунок
1. Создание конвейера, соединяющего два
процесса
В начале, на фазе 1, процесс-инициатор читает данные из stdin, выводит результат в stdout, а ошибки в stderr. stdin,stdout и stderr являются файловыми дескрипторами. Каждая операция, выполняемая с файловым дескриптором, например open, read, write, rewind, truncate или close, изменяет состояние файла.
Затем в фазе 2 процесс-инициатор создает конвейер. Конвейер состоит из очереди и двух файловых дескрипторов. Один нужен, чтобы помещать данные в очередь, другой - чтобы извлекать их из очереди. Конвейер представляет собой структуру данных типа FIFO (первым пришел - первым вышел).
Сам по себе конвейер мало полезен; он предназначен для того, чтобы соединять процесс-поставщик и процесс-потребитель (данных). Поэтому процесс-инициатор в фазе 3 ответвляет или создает себе процесс, с которым будет работать в паре.
В фазе 4 (предполагая, что новый процесс является потребителем данных), исходный процесс заменят свой stdout дескриптором, помещающим данные в конвейер, а stdin только что созданного процесса - дескриптором, считывающим данные из конвейера. После этих манипуляций каждая операция write исходного процесса (поставщика) будет помещена в очередь и впоследствии прочитана новым процессом (потребителем).
Фазы 1-4 иллюстрируют то, как оболочка соединяет с помощью конвейера (|) две утилиты одну с другой в командной строке. Заметим только, что оболочка создает для каждой утилиты новый процесс, а сама занимается управлением ходом работы.
Например, на рисунке 2 показано, как можно с помощью конвейеров соединить команды find, grep и wc, чтобы найти и подсчитать количество файлов, имена которых начинаются с буквы a в нижнем регистре. Оболочка остается независимой; вывод find служит входным потоком для grep, результат работы которого в свою очередь направляется команде wc. wc обрабатывает полученные от grep данные и выводит результат своей работы в stdout. Обычно оболочка выводит stdout на терминал, но его также можно перенаправить в файл.
Рисунок
2. Соединяем команды с помощью конвейеров
Если вы хотите изучить работу двух UNIX-процессов, создайте два конвейера и переопределите файловые дескрипторы процессов так, чтобы они являлись друг для друга и поставщиком и потребителем данных. На рисунке 3 показан обмен данными между процессами, в котором для обоих процессов переопределяются потоки stdin и stdout.
Рисунок
3. Исследуем два UNIX-процесса
На примере конвейеров команд хорошо виден так называемый путь UNIX (UNIX way). В UNIX редко встречаются универсальные программы, которые умеют делать все. Существует большое количество небольших программ. Каждая программа хорошо делает определённое действие: хорошо сортирует файлы, хорошо фильтрует данные и т.д. Все программы умеют работать со стандартными вводом и выводами. Объединяя эти программы в конвейер команд, в результате мы получаем обработку данных без написания новой программы. Таким образом, по-разному комбинируя программы в конвейере, можно получить различные результаты.
