Работа с потоком данных и его фильтрация

При выполнении команд в bash существуют так называемые стандартные файлы. При этом термин «файл» имеет несколько иное значение, чем обычно, — речь идет не о настоящих файлах, а о дескрипторах файлов, которые обрабатываются на уровне операционной системы как обычные файлы.

  1. Стандартный ввод — программа, исполняемая в настоящий момент (например, bash или любая запущенная из нее команда), считывает весь стандартный ввод. Источником стандартного ввода обычно является клавиатура.

  2. Стандартный вывод — сюда переадресовывается весь программный вывод (соответствующий списку файлов, выводимому командой ls). Стандартный вывод обычно отображается в окне терминала.

  3. Стандартные ошибки — в действующем терминале обычно показываются и сообщения об ошибках.

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

Например, возможен случай, в котором содержание текущего каталога должно не отображаться на экране, а сохраняться в файле. Таким образом, стандартный вывод должен переадресовываться в настоящий файл. В bash это делается с помощью символа >:

user$ ls *.tex > content

Сейчас в файле content находится список всех TEX-файлов, расположенных в текущем каталоге. Чаще всего применяется именно такой способ переадресации вывода. Однако есть еще два варианта: 2> файл переадресовывает сообщения об ошибках в указанный файл; >& файл или &> файл переадресовывают в указанный файл и сообщения об ошибках, и программный вывод. Если использовать вместо > двойной символ >>, то весь ввод дописывается в конце уже имеющегося файла.

Переадресация ввода осуществляется с помощью < файл: команды, ожидающие ввода с клавиатуры, считывают ввод из указанного таким образом файла.

ВНИМАНИЕ

Невозможно одновременно обрабатывать файл и записывать в него же результаты обработки!

Команда sort dat > dat или sort < dat > dat приводит к удалению файла dat! В табл. ниже показаны возможности переадресации ввода и вывода.

Что такое каналы?

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

В Linux у нас есть два типа каналов: каналы (также известные как анонимные или безымянные каналы) и каналы FIFO (также известные как именованные каналы).

Каналы

Каналы используются путем объединения команд, разделенных символом канала ‘|‘. Это часто называют конвейером, и каждая оболочка определяет его поведение.

Оболочка выполняет каждую команду в отдельном процессе, работающем в фоновом режиме, начиная с крайней левой команды.

Затем стандартный вывод команды с левой стороны подключается к стандартному вводу команды с правой стороны. Это обеспечивает однонаправленность потока.

Этот механизм действует до тех пор, пока не будут завершены все процессы в конвейере.

Такие оболочки, как Bash и Zsh, используют маркер “|&” для ссылки на конвейер, соединяя как стандартный вывод, так и стандартную ошибку команды на левой стороне со стандартным вводом команды на правой стороне.

Допустим, мы хотим использовать команду netstat, чтобы увидеть, какие процессы запущены с помощью localhost, и отфильтровать с помощью утилиты grep:

netstat -tlpn | grep 127.0.0.1 
(Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) 
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN -

В этом примере вывода мы видим как stdout, так и stderr нашего скрипта в netstat независимо от фильтра.

Теперь давайте объединим stderr в стандартный вывод и передадим его в stdin grep:

netstat -tlpn |& grep 127.0.0.1
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN -

И мы отключили это предупреждающее сообщение.

Конвейеры в Bash

В Bash есть переменная с именем PIPESTATUS, которая содержит список состояний завершения процессов в самом последнем выполненном конвейере:

$ exit 1 | exit 2 | exit 3 | exit 4 | exit 5
$ echo ${PIPESTATUS[@]}
1 2 3 4 5

Возвращаемый статус выполнения всего конвейера будет зависеть от статуса переменной pipefail.

Если эта переменная установлена, возвращаемым статусом конвейера будет статус завершения самой правой команды с ненулевым статусом или будет равен нулю, если все команды завершатся успешно:

$ set -o pipefail
$ exit 1 | exit 2 | exit 3| exit 4 | exit 0
$ echo $?
4

При отключенной опции pipefail возвращаемым статусом канала будет статус завершения последней команды:

$ set +o pipefail
$ exit 1 | exit 2 | exit 3| exit 4 | exit 0
$ echo $?
0

В Bash также есть опция lastpipe, которая инструктирует оболочку выполнить последнюю команду на переднем плане текущей среды.

Конвейеры в Zsh

Zsh имеет аналогичный контроль над конвейерами, что и Bash, но с несколькими отличиями. Например, в Zsh есть команда pipestatus, которая похожа на переменную PIPESTATUS в Bash.

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

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

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

Таким образом, имя работает только как ориентир для процессов, которым необходимо использовать имя в файловой системе.

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

Еще одной важной особенностью FIFO является то, что он обеспечивает двунаправленную связь.

В Linux мы можем создать FIFO с помощью команд mknod (используя букву “p” для обозначения типа FIFO) и mkfifo:

$ mkfifo pipe1
$ mknod pipe2 p
$ ls -l
prw-r--r-- 1 cuau cuau 0 Oct 7 21:17 pipe1
prw-r--r-- 1 cuau cuau 0 Oct 7 21:17 pipe2

Здесь мы можем видеть, что тип файла нашего FIFO обозначен буквой “p”.

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

Именованные и анонимные каналы можно использовать вместе. Давайте создадим обратную оболочку, объединяющую как FIFOs, так и каналы.

Мы будем использовать утилиту nc для создания клиент-серверного приложения, в котором "серверная” сторона предоставит свою оболочку, а “клиентская” сторона сможет получить к ней доступ.

Сначала давайте установим пакет netcat-openbsd. Мы можем установить его в любую систему Ubuntu / Debian с помощью:

$ sudo apt install netcat-openbsdКопировать

Далее давайте создадим FIFO с именем fifo_reverse, набрав mkfifo fifo_reverse.

Затем давайте войдем в систему с двумя разными пользователями, каждый из которых будет действовать как “клиент“ (скажем, "пользователь1”) и как “сервер” (скажем, “пользователь2”). Давайте запустим этот конвейер в оболочке user2:

user2_prompt$ bash -i < fifo_reverse |& nc -l 127.0.0.1 1234 > fifo_reverse
Копировать

В этом однострочнике оболочка считывает содержимое нашего FIFO и передает его интерактивной оболочке Bash.

Затем оба стандартных вывода и stderr интерактивной оболочки будут переданы команде nc, которая будет прослушивать порт 1234 с адресом 127.0.0.1.

И, наконец, когда “клиент” успешно установит соединение, nc запишет полученное в наш FIFO, и интерактивная оболочка сможет выполнить полученное.

Теперь, используя оболочку user1, давайте наберем:

user1_prompt$ nc 127.0.0.1 1234
user2_prompt$Копировать

И мы получили приглашение user2, но используя оболочку user1, объединяющую анонимные и именованные каналы.

5. Временные именованные каналы

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

Обозначение для этого механизма в Bash и Zsh - <(список команд) для передачи результата списка в стандартный ввод фактической команды или >(список команд) для передачи стандартного вывода фактической команды в стандартный ввод списка.

Давайте воспользуемся тем, что мы видели, чтобы передать выходные данные нескольких команд в команду wc:

$ wc -l \
    <(find / -mindepth 1 -maxdepth 1 -type d) \
    <(find /opt -mindepth 1 -maxdepth 1 -type d)
     20 /proc/self/fd/11
      2 /proc/self/fd/12
     22 total
Копировать

В этом примере вывода мы используем команду find, чтобы получить количество каталогов внутри каталогов / и /opt.

6. Когда следует использовать именованные или анонимные каналы?

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

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

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

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

Программные каналы

Программный канал (pipe) создается с помощью символа |. При этом вывод первой команды используется как ввод для второй команды. На практике программные каналы часто объединяются командой less, если нужно просматривать длинный ввод в виде постраничной разбивки.

user$ ls -l | less

Приведенная команда позволяет получить «оглавление» текущего каталога и записать его в программный канал. Оттуда команда less, выполняемая параллельно, считывает ввод предыдущей команды и отображает его на экране.

Программные каналы великолепно подходят для комбинирования различных команд. Так, например, следующая команда возвращает отсортированный список всех установленных пакетов RPM:

user$ rpm -qa | sort

Вместо программных каналов для переадресации ввода и вывода могут использоваться так называемые FIFO-файлы. FIFO означает «первым пришел — первым обслужен» (first in first out) и реализует идею программного канала в форме файла. Работать с FIFO-файлами при вводе значительно сложнее, чем с программными каналами, однако они позволяют уяснить, на что же именно влияет символ |. На практике такие файлы применяются для того, чтобы две программы, работающие независимо друг от друга, могли обмениваться информацией.

user$ mkfifo fifo

user$ ls -l > fifo &

user$ less < fifo

С помощью трех вышеуказанных команд сначала создается FIFO-файл. Кроме того, в виде фонового процесса запускается ls. Она записывает свой вывод в файл. Оттуда less вновь считывает данные и отображает их на экране.

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

Размножение вывода командой tee

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

user$ ls | tee content

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

user$ ls | tee content1 > content2

В итоге получаются два одинаковых файла: content1 и content2. Предыдущая команда приведена просто для примера. Следующий пример понять сложнее, но он несколько ближе к практике:

user$ ls -l | tee content1 | sort +4 > content2

В content1 расположено обычное оглавление, которое автоматически отсортировано командой ls по имени. Копия этого вывода передается sort, а затем происходит сортировка по размеру файла (пятый столбец, то есть параметр +4) и сохранение информации в файле content2.

Last updated