Работа со стандартными потоками ввода-вывода в Unix. Каналы

Конспект, заметки по работе со стандартными потоками ввода-вывода, работа с каналами. Примеры проверены на Debian Linux. Буферизация STDOUT и STDERR в perl. Использование /dev/null . Mknod и mkfifo.

Стандартные потоки ввода-вывода: STDIN, STDOUT, STDERR

Процесс взаимодействия программы с окружением выполняется в терминах записи и чтения в файл. Так, вывод на экран представляется как запись в файл, а ввод данных — как чтение файла. Файл, из которого осуществляется чтение, называется стандартным потоком ввода, а в который осуществляется запись — стандартным потоком вывода.

Кроме потоков ввода и вывода, существует еще и стандартный поток ошибок, на который выводятся все сообщения об ошибках и те информативные сообщения о ходе работы программы, которые не могут быть выведены в стандартный поток вывода.

Большинство программ, которые работают с потоками ввода и вывода, работают с ними как с простыми файлами, и не рассчитывают на то, что поток подключен к терминалу.

Вывод данных на экран и чтение их с клавиатуры происходит потому, что по умолчанию стандартные потоки ассоциированы с терминалом пользователя. Это не является обязательным — потоки можно подключать к чему угодно — к файлам, программам и даже устройствам - эта операция называется перенаправлением.

Стандартные потоки привязаны к файловым дескрипторам с номерами 0, 1 и 2.

  • Стандартный поток ввода (STDIN) — 0;
  • Стандартный поток вывода (STDOUT) — 1;
  • Стандартный поток ошибок (STDERR) — 2.

Пример

Создадим очень простую perl-программу, которая будет отправлять информацию в STDOUT и STDERR. И используем ее в дальнейшем, для проверки разных способов перенаправления вывода.

Sleep используется для того, чтобы успеть получить информацию у запущенном процессе.

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

Кстати, следует учитывать тот факт, что по умолчанию, perl буферизирует вывод STDOUT,
но не буферизирует STDERR.
Попытка выполнения вот такого кода:

может породить вот такую ситуацию:

Текст об ошибке оказался в начале строки, несмотря на то, что должен был оказаться в середине.

То же самое произойдет, если попытаться выполнить код:

В данном случае, сначала программа подождет 20 секунд, и только потом на экране появится сообщение. Хотя на самом деле, сообщение будет отправлено в STDOUT раньше, чем будет вызван sleep. Когда встречаешь подобное поведение системы в первый раз, оно может озадачить.

Отключить буферизацию можно, используя переменную $| и присвоив ей значение 1.

Другой вариант - в конце каждой строки добавлять "\n". Если записать код программы следующим образом:

результат выполнения будет именно таким, как ожидается:

 

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

< filename

Используем указанный файл как источник данных для стандартного потока ввода.

> filename

Перенаправляем стандартный поток вывода в указанный файл. Если файл не существует - он будет создан, если существует - перезаписан.

В данном случае, в файл перенаправлены только те данные, которые подавались на STDOUT. Поток STDERR по прежнему связан с терминалом и выдает данные на него.

Чтобы обнулить содержимое файла, можно использовать команду:

2> filename

Перенаправляем стандартный поток ошибок в указанный файл. Если файл не существует - он будет создан, если существует - перезаписан.

При этом, поток STDOUT продолжает выводить данные на терминал.

В данном случае, "2" - это идентификатор потока ввода-вывода. Идентификатор с номером 2, обычно соответствует потоку STDERR, но можно указать номер любого другого потока вывода.

Вызов "perl stdout.pl 1> filename" эквивалентен вызову "perl stdout.pl > filename".

Можно оба потока вывода перенаправить в файлы:

Если выводимые данные не требуются и вы не хотите их просматривать в дальнейшем, можно перенаправить вывод в /dev/null .

 

/dev/null - это символьное псевдоустройство. Можно обращаться к нему, как к файлу. Не обязательно использовать /dev/null , можно создать свой собственный null-файл с помощью утилиты mknod. Mknod - предназначена для создания специальных символьных или блочных файлов. Кроме того, mknod можно использовать для создания именованых каналов.

mknod FILE c 1 3 - создает null-файл.

/dev/null очень полезен, когда требуется запустить скрипт, выдающий множество предупреждений в STDERR, особенно если этот скрипт запущен через cron, а cron, в свою очередь, шлет огромные текстовые сообщения вам на почту:

С помощью /dev/null можно обнулить содержимое файла:

>>filename

Перенаправляем стандартный поток вывода в заданный файл. Если файл не существует - он будет создан, если существует - данные будут дописаны в конец файла.

2>>filename

Перенаправляем стандартный поток ошибок в указанный файл. Если файл не существует - он будет создан. Если существует - данные будут дописаны в конец файла.

&>filename, >&filename

Перенаправляем и стандартный поток вывода, и стандартный поток ошибок в указанный файл.

>&-

Закрываем стандартный поток вывода.

В данном случае, для скрипта был отключен STDOUT. Скрипт был выполнен, но никаких надписей на экране терминала при этом сделано не было.

2>&-

Закрываем стандартный поток вывода ошибок.

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

 

Каналы

Неименованые каналы

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

В bash канал выглядит как последовательность команд, отделенных друг от друга символом |:

Стандартный поток вывода command1 подключается к стандартному потоку ввода command2, стандартный поток вывода command2 в свою очередь подключается к потоку ввода command3 и т.д.

Программы, образующие канал, выполняются параллельно как независимые процессы.

Пример 1

Создадим perl-скрипт, который будет принимать данные на вход.

Это простая программа, которая считывает строки со стандартного входа и печатает для каждой строки количество символов. Если запустить программу как есть, то она будет ожидать ввода строки с терминала, печатать количество введенных символов, и считывать следующую строку, до тех пор, пока вам это не надоест и вы не введете Ctrl+C.

Либо, можно использовать канал и подать программе на вход какой-нибудь файл, чтобы она подсчитала количество символов в его строках.

К сожалению, процесс cat выполняется очень быстро, передает данные в канал и завершается, поэтому его данные тут не указаны.

 

Пример 2

Создадим программу, которая передает данные другой программе. Для связи между ними будет использован неименованый канал.

Запускаем perl-скрипт и передаем сгенерированные данные утилите wc:

Утилита wc, в своем самом простом варианте применения, подсчитывает количество строк, слов и байт в заданном файле, или в том тексте, который был передан ей на вход.

Если обратить внимание, то можно заметить, что система создала оба процесса одновременно, у них один и тот же родитель.Кроме того, был создан неименованый канал. Этот канал скрипт использует вместо /dev/pts/1 (файл, псевдоустройство - псевдотерминал, PTS, pseudo-terminal slave), в качестве стандартного вывода, и тот же самый канал указан для wc вместо /dev/pts/1, в качестве стандартного входа.

 

Пример 3

Из perl-скрипта вызывается на выполнение netstat, и из этого же скрипта создается канал для связи с ней. Символ вертикальной черты в функции open, как раз указывает на то, что будет создан канал.

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

 

Именованные каналы

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

Для решения этой задачи используются именованные каналы fifo (first in, first out). Они во всём повторяют обычные каналы (pipe), только имеют привязку к файловой системе. Создать именованный канал можно командой mkfifo:

Созданный канал можно использовать для соединения процессов между собой.

 

Пример использования именованого канала

Для теста будем использовать уже известный скрипт stdin.pl:

Кроме того, содаем текстовый файл file.txt, который содержит пару строк:

Создаем именованый канал:

Далее, вызываем утилиту cat для того, чтобы она прочитала данные из файла file.txt и передала их на вход каналу mypipe, и одновременно вызываем perl-скрипт stdin.pl, которому поручаем забрать данные из канала и обработать их.

Пока скрипт отрабатывается, мы имеем возможность посмотреть, с какими файловыми дескрипторами работает stdin.pl, и убедиться, что канал mypipe действительно передает данные на вход скрипту:

 

Полезные ссылки по теме потоков ввода-вывода и каналов

Стандартные потоки ввода/вывода

wikipedia.org: /dev/null

Стандартный канал вывода, стандартный канал ошибок и перенаправление в командной строке

 

Работа со стандартными потоками ввода-вывода в Unix. Каналы: 1 комментарий

  1. elx

    Спасибо за статью, очень познавательно. Но мне остался непонятным один момент - я не совсем понял, как работает амперсанд. Я понял, что &- закрывает поток, но как работает &>filename, >&filename мне так и осталось не понятно. Что тут поменялось от перестановки амперсандов и вообще какой они тут несут смысл?

Комментарии запрещены.