Перейти к содержанию

2.6 Перенаправление и конвейеры (Using Redirection and Piping)

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

  • стандартные файловые дескрипторы bash;
  • перенаправление (redirection) вывода и ввода команд оболочки;
  • работа с конвейерами (pipes).

Начнём с обзора файловых дескрипторов оболочки bash.

Стандартные файловые дескрипторы bash (Standard bash File Descriptors)

Прежде чем научиться перенаправлять ввод-вывод или строить конвейеры из команд bash, необходимо разобраться с файловыми дескрипторами (file descriptors) оболочки bash. Для каждой команды, вводимой в приглашении командной строки, доступны три файловых дескриптора (как показано на рис. 2-11):

Рис. 2-11. Схема файловых дескрипторов оболочки bash: stdin, stdout и stderr.

Рис. 2-11. Файловые дескрипторы оболочки bash.

  • stdin — стандартный ввод (standard input): входные данные, передаваемые команде для обработки. Стандартному вводу команды соответствует дескриптор с номером 0.
  • stdout — стандартный вывод (standard output): результат работы команды. Например, список файлов каталога, формируемый командой ls, является её стандартным выводом. Стандартному выводу соответствует дескриптор с номером 1.
  • stderr — стандартный поток ошибок (standard error): код ошибки, генерируемый командой (если ошибка возникла). Стандартному потоку ошибок соответствует дескриптор с номером 2.

Не все команды используют все три дескриптора, однако многие из них — используют. Рассмотрим несколько примеров. Предположим, что мы выводим содержимое файла из домашнего каталога командой cat ~/test2.txt:

rtracy@openSUSE:~> cat ~/test2.txt
This is a text file named test2.txt.

Команда cat отображает содержимое файла на экране. Это и есть стандартный вывод команды cat. Теперь введём ту же команду, но укажем несуществующий файл:

rtracy@openSUSE:~> cat ~/test1.txt
cat: /home/rtracy/test1.txt: No such file or directory

Поскольку файл не существует, команда cat генерирует ошибку. Это стандартный поток ошибок команды cat. Зная эти дескрипторы, можно перенаправлять каждый из них в нужное место при выполнении команды. О том, как это делается, речь пойдёт далее.

Перенаправление вывода и ввода команд оболочки (Redirecting Output and Input for Shell Commands)

С помощью трёх описанных дескрипторов можно управлять тем, откуда команда получает данные и куда направляет результаты своей работы. В этой части раздела мы рассмотрим, как это делается из командной строки. Будут обсуждены следующие темы:

  • перенаправление вывода;
  • перенаправление ввода.

Начнём с перенаправления вывода.

Перенаправление вывода (Redirecting Output)

Оболочка bash позволяет управлять тем, куда направляется вывод команды после её выполнения. По умолчанию он отображается на экране, однако можно задать иное назначение. Например, очень часто вывод команды перенаправляют с экрана в текстовый файл на диске — особенно если вывод очень объёмный.

Перенаправление выполняется с помощью символа > в командной строке. Синтаксис перенаправления вывода: команда дескриптор> имя_файла_или_устройства. Предположим, нам нужно с помощью команды tail просмотреть последние строки системного журнала /var/log/messages и сохранить вывод в файл lastmessages в текущем каталоге. Это делается командой tail /var/log/messages 1> lastmessages. Она предписывает оболочке перенаправить стандартный вывод (1) в файл lastmessages. Вывод команды действительно перенаправляется: на экране он не отображается. Весь текст стандартного вывода записывается в указанный файл, как показано ниже:

openSUSE:~ # tail /var/log/messages 1>lastmessages
openSUSE:~ # cat lastmessages
2014-10-13T19:14:58.719210-06:00 linux su: pam_unix(su-l:auth): authentication
failure; logname=rtracy uid=1000 euid=0 tty=pts/0 ruser=rtracy rhost= user=root
2014-10-13T19:15:01.106998-06:00 linux /usr/sbin/cron[23401]:
pam_unix(crond:session): session opened for user root by (uid=0)

Если номер дескриптора в команде не указан, оболочка предполагает, что нужно перенаправить только стандартный вывод. В приведённом примере можно было бы ввести tail /var/log/messages > lastmessages с тем же результатом.

С помощью той же техники можно перенаправить стандартный поток ошибок с экрана в файл. Например, если попытаться отобразить командой cat несуществующий файл, сообщения об ошибках можно перенаправить в файл: cat myfiles.odt 2> errorfile. Поскольку файл не существует, команда cat генерирует сообщение об ошибке (stderr) вместо обычного вывода (stdout):

openSUSE:~ # cat myfiles.odt 2>errorfile
openSUSE:~ # cat errorfile
cat: myfiles.odt: No such file or directory

Эта команда предписывает перенаправить стандартный поток ошибок (2) команды cat в файл errorfile в текущем каталоге. Поскольку поток ошибок перенаправлен, сообщение об ошибке не отображается на экране, а записывается в указанный файл.

При перенаправлении вывода в несуществующий файл оболочка bash создаёт его автоматически — именно так и произошло в приведённых примерах. Если же файл уже существует, следует учитывать, что оболочка по умолчанию удалит его содержимое и заменит новым выводом. Чтобы добавить новый вывод к существующему содержимому файла, не стирая его, используйте >> вместо >. Например, чтобы записать стандартный вывод команды ps в файл myprocesses, не затирая имеющиеся данные, следует ввести ps 1>> myprocesses.

Можно одновременно перенаправить как стандартный поток ошибок, так и стандартный вывод в текстовые файлы. Для этого к команде добавляется два указания перенаправления — одно для стандартного вывода, другое для стандартного потока ошибок. Синтаксис: команда 1> имя_файла_stdout 2> имя_файла_stderr. Стандартный вывод попадёт в один файл, а стандартный поток ошибок — в другой. Например, чтобы записать стандартный вывод команды mount в файл mntok, а стандартный поток ошибок — в файл mnterr, следует ввести mount 1> mntok 2> mnterr.

Оба потока можно также направить в один и тот же файл, используя синтаксис команда 1> имя_файла 2> &1. При этом сначала стандартный вывод записывается в указанный файл, а затем стандартный поток ошибок (2) перенаправляется туда же, куда и стандартный вывод (&1). При использовании этой конструкции очень важно не забыть символ & перед 1: он сообщает оболочке, что следующий символ является файловым дескриптором, а не именем файла. Если этот символ опустить, оболочка запишет стандартный поток ошибок в отдельный файл с именем 1.

Теперь, разобравшись с перенаправлением вывода, перейдём к перенаправлению ввода команд.

Перенаправление ввода (Redirecting Input)

Так же как можно задать назначение для вывода команды, можно указать и источник ввода (stdin) команды. Для этого используется символ < — зеркальное отражение символа перенаправления вывода. Синтаксис: команда < текст_или_файл.

Например, можно ввести tail < /var/log/messages в приглашении командной строки. Это передаёт строку текста /var/log/messages команде tail в качестве входных данных. Однако для большинства команд такой вариант не слишком удобен: в приведённом примере проще было бы ввести просто tail /var/log/messages. Данная возможность по-настоящему полезна тогда, когда команде требуется передать большой объём текста.

Например, можно передать команде sort список слов из текстового файла, и она упорядочит их. Предположим, что с помощью текстового редактора создан файл words, содержащий несколько строк:

rtracy@openSUSE:~> cat words
Who
What
Why
Where
How

После создания этого файла его можно использовать как источник ввода для команды sort. Для этого в приглашении командной строки вводим sort < words. Отсортированный результат будет выведен на экран:

rtracy@openSUSE:~> sort < words
How
What
Where
Who
Why

Закрепим работу с перенаправлением в следующем упражнении.

Упражнение 2-9. Перенаправление ввода и вывода

Это упражнение можно выполнить на виртуальной машине, поставляемой с книгой. Для получения правильно настроенного окружения используйте снимок 2-1.

Видео. Посмотрите видео к Упражнению 2-9, в котором продемонстрировано выполнение этого задания.

В этом упражнении вы попрактикуетесь в перенаправлении ввода и вывода. Выполните следующие шаги:

  1. При необходимости загрузите систему Linux и войдите под обычным пользователем.
  2. Переключитесь на учётную запись суперпользователя root, введя su – и затем пароль root.
  3. Воспользуйтесь командой tail, чтобы просмотреть последние строки файла /var/log/messages, и перенаправьте стандартный вывод в текстовый файл в домашнем каталоге: tail /var/log/messages 1> lastlines.
  4. Введите ls l* и убедитесь, что файл lastlines создан.
  5. Просмотрите файл lastlines командой cat lastlines в приглашении командной строки.
  6. Добавьте последние строки файла журнала /var/log/firewall в конец файла lastlines: tail /var/log/firewall 1>> lastlines в приглашении командной строки.
  7. Ещё раз просмотрите файл lastlines командой cat lastlines. Вы должны увидеть строки из журнала брандмауэра, добавленные в конец файла.
  8. Перенаправьте стандартный поток ошибок в файл журнала: tail /var/log/mylog 2> errorout.
  9. Просмотрите файл errorout командой cat errorout. Почему предыдущая команда сгенерировала ошибку?
  10. Передайте файл lastlines в качестве стандартного ввода команде sort: sort < lastlines. Команда sort должна вывести содержимое файла на экран (стандартный вывод) в алфавитном порядке.

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

Конвейеры (Piping Information)

Перенаправление — мощный инструмент, однако у него есть один недостаток: оно позволяет перенаправлять ввод или вывод только в файл файловой системы или системное устройство и из них. Что делать, если нужно передать вывод одной команды на вход другой? Это вполне возможно! Для этого используются конвейеры (pipes). В этой части раздела объясняется, как они работают.

Конвейеры чрезвычайно полезны при работе в командной строке. Например, ранее в этой главе упоминалось, что можно использовать | more с командами set и env. Символ конвейера (|) в команде предписывает оболочке взять вывод первой команды и передать его на вход второй указанной программы.

Например, если ввести cat /var/log/messages | more в приглашении командной строки, команда cat прочитает содержимое файла /var/log/messages и отправит его в стандартный вывод. В обычных условиях этот вывод отображался бы на экране. Однако из-за использования конвейера оболочка знает, что стандартный вывод команды cat не нужно отображать на экране напрямую: вместо этого он передаётся в качестве стандартного ввода следующей команде — в данном случае more. Команда more принимает вывод cat и выводит текст на экран по одной строке.

Конвейеры можно использовать с любыми командами, которые производят какой-либо вывод и принимают какой-либо ввод. Отличный пример — команда grep. Конечно, grep можно использовать и самостоятельно, однако особенно удобна она в составе конвейера. Синтаксис: команда | grep выражение. При этом вывод первой команды передаётся на вход grep, которая ищет в нём информацию, соответствующую заданному выражению.

Предположим, например, что мы используем команду cat для отображения содержимого файла /var/log/messages. Нас интересуют только те записи журнала, которые были созданы при поднятии сетевых интерфейсов. Можно отфильтровать вывод cat, направив его по конвейеру в grep и выполнив поиск строки ifup в выводе cat. Для этого вводим cat /var/log/messages | grep ifup:

openSUSE:~ # cat /var/log/messages | grep ifup
2014-10-13T15:31:01.402528-06:00 linux ifup[2493]: Service
network not started -> skipping
2014-10-13T15:31:12.448163-06:00 linux ifup[3225]:     lo
2014-10-13T15:31:12.744951-06:00 linux ifup[3225]:     lo
2014-10-13T15:31:12.762301-06:00 linux ifup[3225]: IP address: 127.0.0.1/8
2014-10-13T15:31:12.776442-06:00 linux ifup[3225]:
2014-10-13T15:31:13.537806-06:00 linux systemd[1]: Starting
ifup managed network interface eno16777736...

Обратите внимание: grep отображает на экране только те строки вывода cat, которые соответствуют выражению ifup.

Примечание

Помните: при использовании конвейера на экране отображается вывод только последней команды в цепочке. Вывод всех остальных команд передаётся на вход следующей команды конвейера и на экране не отображается.

В данном примере совпадающих записей слишком много, чтобы уместиться на одном экране. Что делать? Можно включить в конвейер сразу несколько команд. В данной ситуации нужно с помощью cat сформировать начальный вывод, затем отфильтровать его через grep, чтобы отобрать только строки с ifup, а затем передать вывод grep команде more для постраничного отображения. Для этого вводим:

cat /var/log/messages | grep ifup | more

Иногда требуется одновременно отобразить вывод команды на экране и записать его в файл. Это можно сделать с помощью команды tee. Синтаксис: команда | tee имя_файла. Например, чтобы стандартный вывод команды ls -l одновременно отображался на экране и записывался в файл output.txt, используем следующую команду:

ls -l | tee output.txt

Закрепим работу с конвейерами в следующем упражнении.

Упражнение 2-10. Использование конвейеров

В этом упражнении вы попрактикуетесь в использовании конвейеров для передачи стандартного вывода одной команды на стандартный ввод другой. Упражнение можно выполнить на виртуальной машине, поставляемой с книгой. Для получения правильно настроенного окружения используйте снимок 2-1.

Видео. Посмотрите видео к Упражнению 2-10, в котором продемонстрировано выполнение этого задания.

Выполните следующие шаги:

  1. При необходимости загрузите систему Linux и войдите под обычным пользователем.
  2. При необходимости откройте сеанс терминала.
  3. Переключитесь на учётную запись суперпользователя root, введя su – и затем пароль root.
  4. Просмотрите все записи системного журнала, содержащие слово kernel, направив вывод cat по конвейеру в grep: cat /var/log/messages | grep kernel.
  5. Вывод предыдущей команды, вероятно, оказался очень длинным. Направьте вывод cat через grep в more: cat /var/log/messages | grep kernel | more.
  6. Отправьте вывод предыдущей команды одновременно на экран и в файл kernel.txt в домашнем каталоге: cat /var/log/messages | grep kernel | tee ~/kernel.txt.
  7. Убедитесь, что данные записаны в kernel.txt: cat ~/kernel.txt.

Совет к экзамену

Для удаления значения переменной используйте команду unset. Достаточно ввести unset имя_переменной в приглашении командной строки.