Pipes: Программные каналы в Linux
Предисловие
В одной остроумной статье я прочел следующую сентенцию: "Лишить приверженца Юникс программных каналов - то же самое, что отобрать мышь у пользователя Виндоуз". Возможно, в этом утверждении и есть некоторое преувеличение, но в прежние времена так оно, по большому счету, и было. Опытные сторонники Юниксовидных систем любят консоль и умеют ею пользоваться. Мы же, нынешние, установив Убунту, уже считаем себя линуксоидами, а что такое консоль, имеем смутное представления. Но проходит некоторое время, и, устав от украшения рабочего стола, прочитав две-три статейки, мы решаемся нажать мышкой на значок монитора в системном трее. Со временем перед нами открывается новый мир, полный удивительных возможностей и беспрерывного совершенствования своих знаний, мир пиршества интеллекта, непрекращающегося эксперимента, и радости оттого, что ты Homo Sapiens. Девиз: "Вернем радость в общение с компьютером!", - как нельзя лучше подходит для этого случая...
Предлагаемая вашему вниманию статья как раз для тех, кто недавно открыл для себя командную строку Линукс.
Выбор термина
Термин pipe (труба) чрезвычайно органично вошел в англоязычный компьютерный жаргон. Этим словом называется не только способ передачи вывода одной команды на ввод другой, но и оператор, которым обозначается это действие: | (вертикальная черта). Кроме того, то же слово служит глаголом, означающим данное действие.
Какие только термины не используют в русском языке для перевода слова "pipes": и трубы, и трубопроводы, и конвейеры, и потоки, и прочее. В контексте все эти термины выглядят довольно неуклюже. И вот еще беда - ни от одного из этих существительных нельзя образовать глагол, не говоря уже о том, чтобы называть так символ вертикальной черты. Можно, правда, употребить глагол "конвейеризировать", но такое не написать, не выговорить невозможно. Я пытался делать наметки этой статьи, используя все перечисленные термины, но не был удовлетворен ни одним.
Совершенно случайно, в книге А. Робачевского "Операционная система UNIX" мне встретился термин "программные каналы". Поначалу он показался мне несколько громоздким, но попробовав его на деле, я убедился в его несомненных преимуществах. Он не выглядит смешно и дико как "трубы", от него легко произвести глагол, и, самое главное, он имеет вполне прижившегося на русской почве брата - "именованные каналы", которые никто не назовет "именованными трубопроводами". Итак, решено, в данной статье термин pipes будет звучать как "программные каналы".
Введение в программные каналы
Программным каналом называется использование вывода одной команды в качестве ввода для другой программы. Например:
dmesg | less
Команда dmesg выводит сообщения ядра Линукс о процессе загрузки ОС (те самые, что пробегают по экрану монитора при загрузке системы). Эти сообщения не умещаются на одном экране, и пролетают так быстро, что прочесть их невозможно. Поэтому вывод программы dmesg передают на ввод команде less. (Команда less позволяет выводу команды dmesg заполнить только один экран. Чтобы прочесть следующую порцию текста, нужно нажать клавишу пробела, а чтобы вернуться к предыдущей порции - клавишу b. Прервать работу программы можно клавишей q). Оператором такой передачи служит вертикальная черта (|). (Пробелы до и после вертикальной черты ставятся для удобства чтения, но можно обойтись и без них). Все вместе и есть простейший программный канал.
Того же результата можно достичь, если сначала перенаправить вывод команды dmesg во временный файл, а затем просмотреть содержимое этого файла на экране монитора.
dmesg > временный.файл
временный.файл > less
Очевидно, что такая схема менее производительна: во-первых, необходимо давать две команды, во-вторых потому, что следующая команда может начать работать только после завершения первой.
Необходимо пояснить понятия, которые я походя назвал "вводом" и "выводом" программы.
Любая программа командной оболочки (шелла) оперирует с тремя потоками данных: стандартным вводом (stdin), стандартным выводом (stdout), и стандартным сообщением об ошибке (stderr). (Подробно об этом можно прочесть в статье "Перенаправление стандартных потоков данных").
По умолчанию, стандартный ввод осуществляется с клавиатуры, а стандартный вывод - на экран монитора. Если же мы задействуем оператор программных каналов (|), то стандартный вывод первой программы станет стандартным вводом второй, при этом на экране монитора он уже не появится.
Такая цепочка вовсе не ограничивается двумя программами, но может продолжаться сколь угодно долго.
Как это работает
В большинстве Юниксовидных систем все процессы в программном канале начинаются одновременно, их потоки соответственно соединяются и управляются планировщиком вместе со всеми остальными процессами, идущими в системе.
Даже если посылающая программа производит 5000 байт в секунду, а принимающая программа может обработать только 100 байт в секунду, все равно никакой потери информации не произойдет, так как программные каналы имеют буферы. Вывод посылающей программы собирается в буфере, ставится в очередь. Когда принимающая программа готова считывать данные, операционная система посылает порцию данных из буфера. В случае переполнения буфера, посылающая программа приостанавливается (блокируется), до тех пор, пока принимающая программа не сможет снова считывать данные, тем самым освобождая буфер.
Механизм этого свойства командной оболочки довольно сложен, в данной статье мы не станем его рассматривать, а будем просто пользоваться этой замечательной способностью шелла.
Как пользоваться программными каналами
Чаще всего употребляются программные каналы, заканчивающиеся командами less и more. Эти две команды схожи по своему действию, однако less новее и имеет ряд дополнительных функций, включая возможность вернуться к предыдущим "страницам" вывода. Многие пользуются этими программными каналами, не подозревая, что занимаются столь мудреными вещами.
Кроме вышеприведенного примера с каналом dmesg | less, часто используется канал ls | less. Команда ls позволяет просматривать содержимое директорий, а с опцией -l дает подробные сведения о файлах, "населяющих" указанную директорию. Если директория содержит достаточно файлов, чтобы их список занял больше одного экрана, то применение программного канала с less или more неизбежно:
ls -l имя_директории | less
или
ls -l имя_директории | more
Для пробы проделайте такой пример:
ls -l -R /usr | less
Только запаситесь терпением - на моей небольшой системе, установленной с одного CD, в выводе было 87 187 строк, сиречь файлов. Дело в том, что опция -R команды ls выводит содержимое директории рекурсивно, то есть открывая подкаталоги, подкаталоги подкаталогов и так далее, пока не перечислит все файлы. Правда, чтобы просмотреть действительно все файлы в директории, нужно войти как администратор (root), потому что некоторые каталоги могут не давать прав доступа рядовому пользователю.
Понятно, что найти "вручную" что-либо в таком списке проблематично, и тут на помощь снова придут программные каналы.
Команда grep найдет нужные вам строки, если вы зададите образец для поиска:
ls -l -R /usr | grep xorg.conf
-rw-r--r-- 1 root root 16681 2008-08-25 23:21 xorg.conf.5.lzma
Обратите внимание на символ # в начале командной строки - он означает, что я вошел с правами суперпользователя.
Команды, входящие в состав программных каналов, часто называются командами-фильтрами, так как они пропускают через себя потоки данных.
Среди команд-фильтров самая употребительная, без сомнения, grep. Она применяется везде, где нужно выбрать искомое из большого объема данных. Скажем, просмотреть все, что касается USB в выводе команды dmesg:
$ dmesg | grep -i usb usbcore: registered new interface driver usbfs usbcore: registered new interface driver hub usbcore: registered new device driver usb ehci_hcd 0000:00:1a.7: new USB bus registered, assigned bus number 1 ehci_hcd 0000:00:1a.7: USB 2.0 started, EHCI 1.00, driver 10 Dec 2004 ...
Это только начало списка строк, выведенных командой grep -i usb, я не привожу его полностью из экономии места. Опция -i приказывает команде grep не замечать разницы между заглавными и строчными буквами.
Любой системный администратор часто пользуется командой ps. С опциями -e и -f она выводит все процессы, текущие в системе в полной форме (подробно). Процессов этих весьма много, поэтому я не привожу полный вывод команды:
$ ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 13:58 ? 00:00:00 init [5] root 2 0 0 13:58 ? 00:00:00 [kthreadd] root 3 2 0 13:58 ? 00:00:00 [migration/0] root 4 2 0 13:58 ? 00:00:00 [ksoftirqd/0] ...
Чтобы найти в этом списке интересующие вас процессы, следует канализировать команду ps с командой grep. Допустим, вас интересуют процессы hald:
$ ps -ef | grep hald 19 2457 1 0 13:58 ? 00:00:00 hald root 2467 2457 0 13:58 ? 00:00:00 hald-runner root 2824 2467 0 13:58 ? 00:00:00 /usr/lib/hald-addon-cpufreq 19 2825 2467 0 13:58 ? 00:00:00 hald-addon-acpi: listening on acpid socket /var/run/acpid.socket root 2831 2467 0 13:58 ? 00:00:01 hald-addon-storage: polling /dev/sr0 (every 2 sec) ya 2884 20820 0 14:49 pts/1 00:00:00 grep hald
С таким коротким списком уже легче работать. (Обратите внимание на последнюю строчку, там представлен сам запущенный нами процесс grep hald).
Другие распространенные команды-фильтры
Кроме команды grep (или вместе с ней) часто употребляются следующие команды:
- sort - сортирует строки по алфавиту или порядку номеров
- wc - подсчитывает количество строк, слов, байт или символов в тексте
- tr - заменяет одни символы другими
- sed - позволяет редактировать текст прямо из командной строки, даже не видя его.
- cut - вырезает из текста нужные куски и выдает их на стандартный вывод
- head/tail - позволяют ограничить просмотр первыми несколькими строками (head - голова), либо последними несколькими строками (tail - хвост).
В этот список я включил только несколько команд-фильтров, освоив которые, можно вдоволь насладиться составлением самых замысловатых программных каналов.
Сложные программные каналы
Вот пример, как наладить проверку орфографии, используя программные каналы. Допустим, что у вас есть файл words.txt, в котором содержатся все слова английского языка (разумеется, такого файла у вас нет, но можно позаимствовать список слов из какого-нибудь словаря; а английского - чтобы не путаться с кодировками). Тогда составляется следующий программный канал:
$ wget "http://en.wikipedia.org/wiki/Pipeline_(Unix)" | \
sed 's/[^a-zA-Z ]/ /g' | \
tr 'A-Z ' 'a-z\n' | \
grep '[a-z]' | \
sort -u | \
comm -23 - words.txt
Примечание: Символ (\) используется для объединения всех шести строк в одну командную строку.
Команда первая: wget получает содержимое HTML web страницы.
Команда вторая: sed удаляет из текста страницы все символы, не являющиеся пробелами или буквами и заменяет их пробелами.
Команда третья: tr переводит все символы верхнего регистра в нижний регистр (заглавные буквы в строчные), а также конвертирует пробелы в строках в символы новой строки, так что теперь каждое "слово" является новой строкой.
Команда четвертая: grep оставляет только строки, содержащие хотя бы один алфавитный символ (попросту букву), удаляя все пустые строки.
Команда пятая: sort сортирует список "слов" в алфавитном порядке, а с опцией -u удаляет дубликаты.
Команда шестая, и последняя: comm находит строки, общие для двух файлов. Первым файлом является стандартный вывод нашего программного канала, для чего вместо имени первого файла стоит прочерк (-), вторым файлом будет файл words.txt. Строки, которые встречаются только во втором файле и те, что встречаются в обоих файлах, подавляются опциями -2 и -3. Результатом будет список слов, встречающихся только в первом файле. И, если считать файл words.txt неким эталонным словарем, то выходящий список будет содержать слова, которых нет в словаре, то есть написанные с ошибками.
Немного истории
Идею программных каналов и значок вертикальной черты как их обозначение придумал Douglas McIlroy, один из авторов ранних командных оболочек. Он обратил внимание на то, сколько времени уходит на обработку вывода одной программы в качестве ввода другой. Его идеи были внедрены в жизнь, когда в 1973 Ken Thompson добавил программные каналы в операционную систему Юникс. Идея была со временем позаимствована другими ОС, такими как DOS, OS/2, Microsoft Windows, и BeOS, часто даже с тем же обозначением.
Понятие именованного канала
Английское название именованного канала - named pipe или FIFO (File In, File Out - файл пришел, файл ушел). Именованные каналы служат в основном для межпроцессного взаимодействия, когда различные процессы в системе обмениваются информацией. Тема это сложная и большая, заслуживающая отдельной статьи. Поэтому в данной работе я только вкратце коснусь ее.
В отличие от анонимного программного канала, автоматически создаваемого шеллом, именованный канал обладает именем, и создается явно при помощи команд mknod или mkfifo. Создадим именованный канал fifo1:
mkfifo fifo1
Теперь запустим процесс, обращающийся к данному каналу:
grep fs < fifo1
Несмотря на нажатие клавиши ENTER ничего не происходит, что не удивительно, ведь файл fifo1 пока пуст, и команде grep нечего обрабатывать. Однако консоль оказывается занята ждущим процессом, и разблокировать ее можно только прервав процесс (скажем, нажатием клавиш CTRL+c).
Чтобы наполнить именной канал содержимым, нужно чтобы к нему обратился второй процесс. Для этого мы должны открыть вторую консоль и запустить какую-либо команду, передающую данные в файл fifo1. Например:
(Другая консоль)
ls /etc > fifo1
Немедленно в первой консоли сработает команда grep:
grep fs < fifo1 fstab gettydefs login.defs mke2fs.conf
Совершенно ясно, что пользоваться таким неудобным механизмом в пользовательских целях никто не будет, ведь гораздо проще запустить один программный канал:
ls /etc | grep fs
и получить тот же результат.
Этот пример я привел лишь для демонстрации создания и работы именованного канала. Другое дело, когда именованные каналы создаются самими процессами для обмена информацией друг с другом. Но повторюсь, что тема эта непростая и в данной статье рассматриваться не будет.
Резюме
Программные каналы - мощнейший инструмент Юниксовидных операционных систем. Создание программных каналов многократно ускоряет процесс обработки данных, сокращает количество "писанины" в командной строке, а также позволяет получать результат в наиболее удобном виде.