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

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















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




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




Я уже писал о том, что в Linux всё считается файлом. Из этого следует, когда команда выводит результат своей работы, она пишет в какой-то файл. А когда получает данные, она читает какой-то файл.




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




Также существует стандартный поток ошибок — это файл, в который процесс записывает ошибки, если они возникают при работе.




В Linux стандартные потоки это виртуальные файлы и по умолчанию стандартные потоки вывода ассоциированы с экраном терминала пользователя. Поэтому вывод результата или ошибок поступает на экран терминала. А стандартный поток ввода связан с клавиатурой терминала, поэтому чтение данных происходит с клавиатуры.




НазваниеФайловый
дескриптор
Связанное
устройство
Файл
stdin
стандартный поток ввода
0клавиатура
терминала
/dev/stdin
stdout
стандартный поток вывода
1экран
терминала
/dev/stdout
stderr
стандартный поток ошибок
2экран
терминала
/dev/stderr
Таблица файлов стандартных потоков в Linux




Вот как эти файлы увидеть:




alex@deb-11:~$ 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

alex@deb-11:~$ 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

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




Из вывода мы можем понять что файлы потоков это символические ссылки, ведущие на номера файловых дескрипторов. А эти файловые дескрипторы ведут на одно и тоже устройство — /dev/pts/0. Это устройство называется псевдо-терминалом. Именно этому псевдо-терминалу (pts/0) подключен я по ssh:




alex@deb-11:~$ loginctl list-sessions
SESSION  UID USER SEAT TTY
     83 1000 alex      pts/0

1 sessions listed.

alex@deb-11:~$ 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    172.28.80.14     10:46    0.00s  0.17s  0.00s w




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




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




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




Перенаправление потоков stdout и stderr в файл




Перенаправление stdout в файл




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




alex@deb-11:~$ id
uid=1000(alex) gid=1000(alex) группы=1000(alex),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev)




Мы можем перенаправить результат в файл с помощью символа «>«:




alex@deb-11:~$ id > id.txt

alex@deb-11:~$ cat id.txt
uid=1000(alex) gid=1000(alex) группы=1000(alex),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev)




Как видим на терминале ничего показано не было, а всё записалось в файл. При этом, если бы файла не было то он создастся. А если бы файл был, то он пере-запишется, то есть все содержимое файла очищается и заменяется.




Если мы не хотим пере-записывать файл целиком, а хотим дописать в файл, то нужно использовать «>>«:




alex@deb-11:~$ id root >> id.txt

alex@deb-11:~$ cat id.txt
uid=1000(alex) gid=1000(alex) группы=1000(alex),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev)
uid=0(root) gid=0(root) группы=0(root)




Перенаправление stderr в файл




Если нам нужно перенаправить stderr в файл, то используется «2>«:




alex@deb-11:~$ touch test.txt

alex@deb-11:~$ ls test.txt test2.txt
ls: невозможно получить доступ к 'test2.txt': Нет такого файла или каталога
test.txt

alex@deb-11:~$ ls test.txt test2.txt 2> ls-error.txt
test.txt

alex@deb-11:~$ cat ls-error.txt
ls: невозможно получить доступ к 'test2.txt': Нет такого файла или каталога




Как видим при таком перенаправлении stdout идет на терминал, а stderr в файл.




Тут как и с перенаправлением stdout:




  • «2>» — перезапишет файл;
  • «2>>» — допишет файл.




Перенаправление потоков stdout и stderr в файл одновременно




Вы можете простым образом перенаправить оба потока:




alex@deb-11:~$ ls test.txt test2.txt > ls-out.txt 2> ls-error.txt

alex@deb-11:~$ cat ls-out.txt
test.txt

alex@deb-11:~$ cat ls-error.txt
ls: невозможно получить доступ к 'test2.txt': Нет такого файла или каталога




А чтобы все перенаправить в один файл используется довольно интересная конструкция: > file 2>&1:




alex@deb-11:~$ ls test.txt test2.txt > ls-out.txt 2>&1

alex@deb-11:~$ cat ls-out.txt
ls: невозможно получить доступ к 'test2.txt': Нет такого файла или каталога
test.txt




То есть мы перенаправляем stdout в файл, а stderr в stdout, напомню что stdout имеет файловый дескриптор 1.




Есть такой файл в Linux — /dev/null, это как черная дыра, все что идет в /dev/null никуда не сохраняется. Во многих инструкциях можно найти примерно такую команду:




# команда > /dev/null 2>&1




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




Перенаправление stdout одного процесса на stdin другого




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




Я очень долго путал stdin с параметрами команды, то есть я думал что:




alex@deb-11:~$ ls test.txt
test.txt




Результат предыдущей команды «test.txt» пере-направится как параметр. То есть я думал что:




alex@deb-11:~$ ls test.txt | cat




будет равен:




alex@deb-11:~$ cat test.txt
1
2
3




Но на самом деле:




alex@deb-11:~$ ls test.txt | cat
test.txt




Как же это работает на самом деле?




Pipeline заставляет cat читать не из файла а из stdout предыдущей команды. А первая команда пишет в stdout слово «test.txt«, вот и cat читает посимвольно слово «test.txt«.




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




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




alex@deb-11:~$ ls
apache2_2.4.53-1~deb11u1_amd64.deb  date.log      ls-out.txt  rootCA.srl  site.key         test.txt
crash                               id.txt        rootCA.crt  site.crt    sysadminium.cnf  timer.sh
crash.c                             ls-error.txt  rootCA.key  site.csr    testfolder       ulimit-t.sh

alex@deb-11:~$ ls | grep l
date.log
ls-error.txt
ls-out.txt
rootCA.srl
testfolder
ulimit-t.sh









Итог




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




В статье все примеры были проведены на Debian 11, но всё точно также будет работать и в Ubuntu 22.04.










2022-09-13T10:31:12
Администрирование Linux