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

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

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

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

  • вывод информации (stdout);
  • вывод ошибок (stderr).

Оба эти потока, по умолчанию, привязаны к терминалу, поэтому весь вывод вы видите на экране терминала. Например, попробуем найти что-нибудь в каталоге /home:

$ find /home/ -name alex
find: ‘/home/dima’: Отказано в доступе
find: ‘/home/lena’: Отказано в доступе
/home/alex

Вывод этой команды попал на экран терминала, при этом ошибки шли через stderr а нормальный вывод через stdout.

Кроме этого существует стандартный поток ввода (stdin). Он также привязан к терминалу, но к клавиатуре терминала. Получается, когда мы вводим что-нибудь в окне терминала, то мы используем (stdin).

ПотокФайловый
дескриптор
Связанное
устройство
Файл по умолчанию
stdin
стандартный поток ввода
0клавиатура
терминала
/dev/stdin
stdout
стандартный поток вывода
1экран
терминала
/dev/stdout
stderr
стандартный поток ошибок
2экран
терминала
/dev/stderr
Потоки ввода вывода

Давайте посмотрим на эти файлы:

$ ls -l /dev/std*
lrwxrwxrwx 1 root root 15 сен  9 10:57 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root 15 сен  9 10:57 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15 сен  9 10:57 /dev/stdout -> /proc/self/fd/1

Получается все они ссылаются на какие-то файловые дескрипторы. Посмотрим теперь на них:

$ ls -l /proc/self/fd/[0,1,2]
lrwx------ 1 alex alex 64 сен 12 14:28 /proc/self/fd/0 -> /dev/pts/0
lrwx------ 1 alex alex 64 сен 12 14:28 /proc/self/fd/1 -> /dev/pts/0
lrwx------ 1 alex alex 64 сен 12 14:28 /proc/self/fd/2 -> /dev/pts/0

Все эти файловые дескрипторы ссылаются на одно устройство. Посмотрим на него:

$ ls -l /dev/pts/0
crw--w---- 1 alex tty 136, 0 сен 12 14:28 /dev/pts/0

Устройство /dev/pts/0 — это псевдо-терминал. Именно к этому псевдо-терминалу (pts/0) я подключен по ssh:

$ loginctl list-sessions
SESSION  UID USER SEAT TTY
     83 1000 alex      pts/0

1 sessions listed.

$ w
 14:43:19 up 3 days,  3:45,  1 user,  load average: 0,00, 0,00, 0,00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
alex     pts/0    192.168.0.14     10:46    0.00s  0.17s  0.00s w

И все эти потоки можно перенаправлять, например можно пустить:

  • stdout или stderr не на терминал а в файл;
  • stdin не из терминала а из файла;
  • stdout от одного процесса на stdin другому.

Про эти потоки можно почитать в официальном мануале здесь.

Перенаправление вывода в файл

команда > файлперенаправление stdoutфайл перезаписывается
команда 2> файлперенаправление stderr файл перезаписывается
команда &> файлперенаправление всегофайл перезаписывается
команда >> файлперенаправление stdoutфайл дозаписывается с конца
команда 2>> файлперенаправление stderr файл дозаписывается с конца
команда &>> файлперенаправление всегофайл дозаписывается с конца

Очень часто требуется перенаправлять вывод не в файл, а просто в никуда. Для этого служит специальное устройство /dev/null.

Примеры:

  • выполним команду без перенаправлений, все потоки пойдут на терминал:
$ find /home/ -name alex
find: ‘/home/dima’: Отказано в доступе
find: ‘/home/lena’: Отказано в доступе
/home/alex
  • перенаправим stdout в файл, при этом stderr будет отправлен на терминал:
$ find /home/ -name alex > stdout.txt
find: ‘/home/dima’: Отказано в доступе
find: ‘/home/lena’: Отказано в доступе
  • перенаправим stderr в файл, при этом stdout будет отправлен на терминал:
$ find /home/ -name alex 2> stderr.txt
/home/alex
  • оба потока, до-записываем в файлы, одной командой, при этом вывода на терминале не будет:
$ find /home/ -name alex >> stdout.txt 2>>stderr.txt

Прочитаем получившиеся файлы:

$ cat stdout.txt
/home/alex
/home/alex

$ cat stderr.txt
find: ‘/home/dima’: Отказано в доступе
find: ‘/home/lena’: Отказано в доступе
find: ‘/home/dima’: Отказано в доступе
find: ‘/home/lena’: Отказано в доступе

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

$ cat test.txt
hello world
hi world
goodbye world

$ cat test.txt > test.txt

$ cat test.txt

В примере выше мы вначале очистили файл test.txt, а затем прочитали пустой файл и записали прочитанное обратно в файл. В результате файл оказался пустым.

Примеры

Разберём ещё некоторые практические примеры.

  • Необходимо убрать сообщения об ошибках, если они не нужны.
$ find / -name alex 2> /dev/null
/home/alex
  • Запишем в файл большой вывод для дальнейшего изучения. А поток ошибок отправим в /dev/null.
$ ls -R / > files.txt 2> /dev/null
  • Когда вы что-то выполняете не интерактивно, например по расписанию, то никакой вывод вам не нужен. Перенаправим его в /dev/null, либо в какой нибудь log файл. Это можно сделать тремя способами:
$ команда > file.log 2> file.log
$ команда > file.log 2> &1
$ команда &> file.log
  • С помощью перенаправления можно создать файл, или очистить его (если он уже существует).
$ > файл
  • Или мы можем создать файл и сразу поместить в него текст:
$ echo 123 > файл
  • Если нужно создать файл многострочный:
$ cat <<EOT > файл
строка 1
строка 2
строка 3
EOT

Перенаправление ввода из файла

Есть утилиты которые работают с файлами: cat, sort, grep. Им можно в качестве параметра указать файл с коротым они должны работать. Но эти утилиты умеют работать не только с файлами, но и с потоком ввода (stdin). Если этим утилитам не указывать файл, то они начинают работать с потоком ввода (по умолчанию с терминалом). Но можно, с помощью перенаправления, указать вместо терминала файл.

Подготовим файл для примеров:

$ cat <<EOT > test.txt
hello world
hi world
goodbye world
EOT

Здесь тоже используются перенаправления. Мы указываем что cat будет получать ввод из терминала, но когда встретит строку EOT — это будет означать закончить приём новых строк. Дальше всё что мы вводили утилите cat будет перенаправлено в файл test.txt. Это работает не только с cat, например:

$ tr 'a-z' 'A-Z' <<EOT
> hello
> world
> EOT
HELLO
WORLD

Примеры

Следующие команды делают одно и тоже:

# здесь test.txt это параметр, мы говорим утилите с каким файлом работать
$ sort test.txt 
goodbye world
hello world
hi world

# здесь test.txt это файл, мы перенаправляем его содержимое утилите sort 
$ sort < test.txt 
goodbye world
hello world
hi world

# здесь test.txt это файл, мы читаем его утилитой cat, и прочитанное по каналу передаём утилите sort
$ cat test.txt | sort 
goodbye world
hello world
hi world

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

  • cat test.txt | утилита — мы читаем файл и передаём прочитанное в канал.
  • утилита < test.txt — мы читаем файл и передаём прочитанное утилите.

Про каналы (пайпы) будет написано ниже.

Некоторые утилиты могут работать только с вводом, то есть им нельзя указать, с помощью параметра, файл. Одной из таких утилит является — tr. С её помощью мы можем изменять некоторые символы в тексте.

$ tr 'a-z' 'A-Z'
hello
HELLO

То есть мы печатаем (помещаем в stdin) маленькие буквы, а получаем вывод большими.

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

$ tr 'a-z' 'A-Z' < test.txt
HELLO WORLD
HI WORLD
GOODBYE WORLD

$ cat test.txt | tr 'a-z' 'A-Z'
HELLO WORLD
HI WORLD
GOODBYE WORLD

Если нам нужно прочитать файл и прочитанное отправить утилите, то не обязательно использовать утилиту cat и канал. Достаточно использовать перенаправление stdin.

А так мы можем отфильтровать содержимое одного файла и занести вывод в другой файл:

$ tr 'a-z' 'A-Z' < test.txt > test-copy.txt

$ cat test-copy.txt
HELLO WORLD
HI WORLD
GOODBYE WORLD

Но с одним файлом этот приём не сработает, так как файл будет очищен ещё до начала выполнения команды.

$ tr 'a-z' 'A-Z' < test.txt > test.txt

$ cat test.txt

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

Каналы (пайпы) нужны для перенаправления stdout одного процесса на stdin другого. Именно вывод утилиты, а не содержимое файла.

Допустим первая команда выводит какой-то результат в stdout, и нам нужно этот результат использовать как stdin для следующей команды. В этом случае используется пайп (pipeline) — «|» .

Канал заставляет утилиту читать не из файла а из stdout предыдущей команды.

$ ls /home/ | cat
alex
dima
lena

Чтобы пайплайны работали, вторая команда должна уметь читать из stdin, а это умеют не все утилиты. Но почти все утилиты, которые умеют читать данные из файла могут читать и из stdin. Как пример, могу привести следующие утилиты которые умеют принимать данные из stdin: cat, grep, less, tail, head, wc.

Вот еще один пример, найдем все файлы, в которых есть буква «l«:

$ ls | grep l
files.txt

Мы узнали про стандартные потоки ввода и вывода: stdin, stdout, stderr. Научились перенаправить stdout и stderr в файл. Научились перенаправлять stdin из файла. И перенаправлять stdout одной команды на stdin другой.

Если понравилась статья, подпишись на мой канал в VK.

Оставьте комментарий