Использование одной и той же процедуры print
для этих разных
целей не так уж странно: ведь экран терминала во многом ведёт себя как два
файла, один из которых открыт для чтения, а другой — для записи. Чтение
приводит к получению символов, введённых с клавиатуры. Записанные символы
попадают на экран. Этих аналогий вполне достаточно, чтобы работать
с клавиатурой и экраном как с файлами при помощи операций ввода/вывода.
Каждая программа изначально имеет три канала для связи с внешним миром: один
для ввода и два для вывода. Эти «файлы» уже открыты, и от программиста не
требуется их открывать заново (если не хочется чего-то особенного). Дескрипторы
файлов программа может получить с помощью процедур STDIN
,
STDOUT
и STDERR
(строго говоря, это
не совсем процедуры, но для нас это не важно). Приставка
STD…
в этих именах означает, что эти дескрипторы
стандартные,
то есть полагающиеся каждой программе по стандарту.
Вывод в дескриптор STDOUT
приводит к печати строк на
экране. Две следующих команды равносильны:
Perlprint {STDOUT} "Привет!\n"; print "Привет!\n";
Иными словами, STDOUT
— это дескриптор, используемый
процедурой print
по умолчанию.
Чтение из STDIN
останавливает программу, которая затем
ждёт до тех пор, пока конец ввода не будет обозначен нажатием клавиш Ctrl+D.
Perlread STDIN, $buffer, 1;
Дескриптор STDERR
во многом похож на
STDOUT
, но предназначен для вывода сообщений об ошибках.
Операционная система Unix
, а вслед за
ней операционные системы семейства Linux
, основаны на нескольких базовых
идеологических принципах. Один из них отдаёт предпочтение
узкоспециализированным программам, которые умеют делать немногое, но делают это
хорошо. Такие программы часто называют
утилитами. При противоположном
подходе предпочтительнее программы универсальные. Разница здесь примерно
такая же, как между хорошим набором инструментов и швейцарским армейским ножом,
в котором есть всё, начиная с отвёртки и заканчивая штопором.
У универсальности, помимо достоинств, есть и существенный недостаток.
Универсальным инструментом, у которого с одной стороны ложка, а с другой —
лопата, одинаково неудобно и копать землю, и кушать.
Конечно, большинство задач, которые пользователи решают при помощи компьютера,
достаточно сложны. Как же решать их при помощи набора простых узкопрофильных
программ? Естественно, нужно каким-то образом комбинировать возможности
имеющихся программ. Для этих целей служит так называемый системный
клей. Это простой язык программирования, с помощью
которого можно соединить возможности простых программ для решения сложной
задачи. В операционных системах Linux
в качестве системного клея чаще всего применяется командный
язык
(Shell). Язык Perl тоже хорош в роли
системного клея.
Хорошо нам знакомая командная строка позволяет вводить и выполнять команды командного языка. В командной строке можно записывать по нескольку команд, образующих небольшие программы.
Важной возможностью командного языка является переадресация ввода/вывода.
Программу, которая умеет выводить текст только на стандартный вывод, можно заставить записывать тот же текст в заданный файл. Для этого не требуется править исходный код программы. Это делается так:
%
./hello.pl
Hello, world!
%
./hello.pl >hello.txt
Здесь при втором запуске программы мы не увидим приветствия — оно отправилось
в файл hello.txt
. Следует отметить, что файл, в который
переадресуется вывод, создаётся при необходимости. Если же файл существовал, он
предварительно опустошается.
В случае, если требуется дописать текст в файл, то есть добавить его в конец файла, следует применить другую разновидность переадресации вывода:
%
./hello.pl >>hello.txt
Точно так же можно обмануть программу, читающую со стандартного ввода, подсунув
вместо него содержимое файла. Допустим, программа sum.pl
находит сумму чисел, введённых пользователем. Можно записать числа в файл
data.txt
, а затем переадресовать стандартный ввод на ввод
из файла:
%
./sum.pl
1 2 3 4 5 6 ␄
21
%
./sum.pl <data.txt
21
Ничего не мешает переадресовать и стандартный ввод, и стандартный вывод
программы. К примеру, утилита sort умеет читать строки
со стандартного ввода, и выводить их же на стандартный вывод, но уже
в лексикографическом порядке. С её помощью можно упорядочить по алфавиту строки
в файле input.txt
и записать результат в файл
output.txt
:
%
sort <input.txt >output.txt
Понятно, что все эти возможности позволяют программам обмениваться данными при помощи файловой системы в качестве передаточного звена, посредника.
Не обязательно при передаче данных между программами прибегать к услугам
посредника — можно и «из рук в руки». Командный язык позволяет организовать
конвейер.
Рассмотрим для примера такую задачу. Имеется текстовый файл
input.txt.gz
, сжатый компрессором gzip.
Требуется подсчитать все строки файла, в которых встречается буквосочетание
Perl
. Эта сложная задача естественным образом раскладывается
на три подзадачи: распаковка сжатого файла, отбор нужных строк и их подсчёт.
Для каждой подзадачи имеется подходящая утилита. Декомпрессор
zcat распакует сжатый файл и печатает его содержимое на
стандартный вывод. Утилита grep читает строки
со стандартного ввода и записывает на стандартный вывод те из них, которые
содержат заданное слово. Наконец, утилита wc умеет, помимо
прочего, подсчитывать строки, поступающие на стандартный ввод, и печатать на
стандартный вывод количество. Соберём конвейер из этих трёх программ:
%
zcat input.txt.gz |grep Perl |wc -l
11
Стандартный вывод каждой из программ в конвейере присоединяется к стандартному вводу следующей. Программа, стоящая слева в конвейере, приостанавливает свою работу до тех пор, пока её соседка справа не захочет прочитать её вывод. Таким образом программы в конвейере синхронизируют свою работу.
Другой пример. Нужно вывести в файл output.txt
строки
файла input.txt
с седьмой по десятую:
%
cat <input.txt |head -10 |tail -4 >output.txt
Наверное, читатель догадался, что делают утилиты head и tail: выводят на стандартный вывод заданное количество первых (последних) строк, прочитанных со стандартного ввода.
Описанные выше возможности по переадресации вывода затрагивают только стандартный вывод, но не стандартный вывод сообщений об ошибках. Сообщения об ошибках по-прежнему попадают на экран терминала. Это особенно удобно в конвейере: каждая программа — участница конвейера имеет возможность «пожаловаться» пользователю на что-нибудь, продолжая работу по обработке данных.
Впрочем, можно переадресовать и вывод сообщений об ошибках:
%
find / >files.txt 2>errors.log
Здесь утилита find выводит полный список всех файлов
файловой системы в файл files.txt
. Все жалобы, вроде
запрета на чтение оглавления отдельных директорий, отправляются в файл
errors.log
.
При желании можно переадресовать вывод сообщений об ошибках на стандартный вывод, а стандартный вывод — в файл:
%
program 2>&1 >output.txt
Это бывает полезно при отладке программ. Если, к примеру, в программе на Perl включен режим вывода предупреждений, предупреждения попадают на стандартный вывод сообщений об ошибках и смешиваются со стандартным выводом. Весь вывод можно переадресовать в файл, как было показано выше, или просмотреть пейджером less:
%
program 2>&1 |less
В заключение особенно подчеркнём, что возможность переадресации ввода/вывода
никак не связана с языком Perl — это свойство командной оболочки операционной
системы. Программы, чей ввод или вывод переадресуется, могут быть написаны на
любом языке. Описанные возможности присутствуют в Linux
. Большая их часть имеется и в Windows
.
Написать о буферизации.