Из статьи вы узнаете про стандартные потоки ввода и вывода, и перенаправление этих потоков в файл или от одного процесса другому.
Стандартные потоки
Когда вы выполняете какую-нибудь команду и это действие выводит что-то на экран терминала. То этот вывод может идти по разным потокам:
- вывод информации (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.