3.8. Потоки ввода-вывода и коды возврата
Автор: Кирилл Шевчук
В этой статье мы познакомимся с тем, как команды в терминале обмениваются данными. Каждая команда может получать входные данные (stdin), отправлять результат работы (stdout) и сообщения об ошибках (stderr).
Мы научимся управлять этими потоками с помощью перенаправлений и разберёмся, что такое код возврата команды и как его использовать для проверки успеха или ошибки.
Ключевые вопросы статьи
- Что такое стандартные потоки
stdin,stdoutиstderr?- Что такое перенаправление потока и для чего оно нужно?
- Как и для чего комбинировать разные перенаправления?
- Что такое коды возврата команд и как их можно проверить?
- Что за файл
/dev/nullи зачем он нужен?
Что такое потоки ввода-вывода
В предыдущей статье мы использовали команды вроде grep, wc и diff, чтобы анализировать данные и сравнивать результаты.
Но все эти команды не просто «что-то делают» — они принимают данные, обрабатывают их и отдают результат обратно в терминал.
Именно этот процесс передачи данных называется вводом и выводом (англ. input/output или просто I/O).
Каждая команда в Linux общается с внешним миром через три «канала связи» — потоки ввода-вывода.
Они встроены в систему и существуют для каждой программы по умолчанию:
| Поток | Название | Описание |
|---|---|---|
stdin |
Стандартный ввод | То, откуда команда получает данные (обычно клавиатура, но может быть файл или вывод другой команды) |
stdout |
Стандартный вывод | То, куда команда отправляет результат своей работы (обычно экран терминала) |
stderr |
Стандартный поток ошибок | То, куда команда пишет сообщения об ошибках (тоже обычно экран, но отдельно от основного вывода) |
Можно представить это так:
Потоки ввода и вывода
Эти потоки работают одновременно, но независимо друг от друга. Это позволяет, например, записывать результат работы команды в один файл, а ошибки — в другой.
Пример 1. Команда без ошибок
alice@ubuntu:~$ echo "Hello, world!"
Hello, world!
Команда echo получает текст «Hello, world!» через свои аргументы, выводит его в стандартный поток вывода (stdout), и текст попадает на экран.
Всё прошло успешно — ошибок нет, поэтому в поток stderr ничего не попало.
Пример 2. Команда с ошибкой
alice@ubuntu:~$ cat missing.txt
cat: missing.txt: No such file or directory
cat пыталась прочитать файл missing.txt, но не нашла его.
Текст ошибки No such file or directory — это не просто сообщение на экране, это вывод в поток stderr, отдельный от основного вывода stdout.
Почему это важно
Пока команды просто показывают результат на экране, разницы между stdout и stderr не видно.
Но когда вы начнёте перенаправлять вывод в файлы, передавать результаты другим командам или писать скрипты, разделение этих потоков становится критически важным.
Например:
- вы можете сохранить результат работы программы в файл, но ошибки записать в отдельный;
- или объединить оба потока, чтобы получить полный журнал выполнения;
- или вовсе скрыть ошибки, если они вам не нужны.
Это и есть основа управления вводом и выводом, с которой мы познакомимся дальше.
Перенаправление вывода
Ранее мы уже применяли эту механику, когда вызывали команду и записывали результат в файл c помощью оператора >.
Это частый случай перенаправления вывода. Он нужен, например, чтобы потом проанализировать файл, отправить коллеге или просто не потерять после закрытия терминала.
И точно так же можно перенаправлять вывод ошибок и делать многие другие вещи. Разберём перенаправления подробнее.
Перенаправление стандартного вывода (stdout)
Чтобы записать результат работы команды в файл вместо экрана, используется символ >:
alice@ubuntu:~$ echo "Hello" > output.txt
Теперь ничего не появилось на экране, потому что текст «Hello» был направлен не в терминал, а в файл output.txt.
Если откроем его, увидим наш результат:
alice@ubuntu:~$ cat output.txt
Hello
Символ > создаёт новый файл, если его не было, или перезаписывает существующий, если он уже есть.
Ранее мы создавали новый файл таким образом в качестве альтернативы команде touch.
Запустим команду снова с другим текстом:
alice@ubuntu:~$ echo "World" > output.txt
alice@ubuntu:~$ cat output.txt
World
Видим, что старое содержимое файла исчезло — на его месте теперь только слово World.
Но мы можем не перезаписывать, а добавлять текст в конец файла, и для этого используется двойная стрелка >>:
alice@ubuntu:~$ echo "Hello" > output.txt
alice@ubuntu:~$ echo "World" >> output.txt
alice@ubuntu:~$ cat output.txt
Hello
World
Теперь мы видим в файле обе строки, которые мы записали.
Примечание
Запомните: > — создаёт или перезаписывает файл, >> — добавляет текст в конец файла.
Перенаправление ошибок (stderr)
По умолчанию и обычный вывод (stdout), и ошибки (stderr) печатаются на одном и том же экране.
Но Linux позволяет направлять их в разные файлы.
Для этого используется 2> — цифра 2 обозначает поток ошибок (stderr), а 1 — стандартный вывод (stdout).
Попробуем пример:
alice@ubuntu:~$ ls /root 2> errors.log
Если у пользователя нет прав доступа к каталогу /root, команда ls выведет сообщение об ошибке. Но теперь ошибка окажется в файле errors.log, а не на экране.
alice@ubuntu:~$ cat errors.log
ls: cannot open directory '/root': Permission denied
При этом, если бы в выводе были обычные данные (stdout), они всё равно появились бы в терминале.
Разделение вывода и ошибок
Часто программы выводят и результат, и ошибки одновременно.
Чтобы их не перепутать, можно записать их в разные файлы:
alice@ubuntu:~$ ls -l /var /root > output.log 2> errors.log
Теперь:
- всё, что программа выводит «нормально» (
stdout), попадёт вoutput.log; - всё, что она пишет как ошибку (
stderr), окажется вerrors.log.
Можем проверить наши файлы, чтобы в этом убедиться:
alice@ubuntu:~$ cat output.log
/var:
total 40
drwxr-xr-x 2 root root 4096 Oct 6 14:43 backups
drwxr-xr-x 15 root root 4096 Sep 23 15:17 cache
drwxrwsrwt 2 root root 4096 Jun 5 12:55 crash
drwxr-xr-x 43 root root 4096 Sep 23 15:17 lib
drwxrwsr-x 2 root staff 4096 Apr 22 2024 local
lrwxrwxrwx 1 root root 9 Jun 5 12:53 lock -> /run/lock
drwxrwxr-x 9 root syslog 4096 Oct 19 00:00 log
drwxrwsr-x 2 root mail 4096 Jun 5 12:53 mail
drwxr-xr-x 2 root root 4096 Jun 5 12:53 opt
lrwxrwxrwx 1 root root 4 Jun 5 12:53 run -> /run
drwxr-xr-x 4 root root 4096 Jun 5 12:53 spool
drwxrwxrwt 9 root root 4096 Oct 21 07:45 tmp
alice@ubuntu:~$ cat errors.log
ls: cannot open directory '/root': Permission denied
Разделение потоков вывода — одна из самых практичных возможностей Linux.
Оно позволяет собирать чистые логи, анализировать ошибки отдельно и не терять полезную информацию в большом потоке сообщений.
Объединение потоков
Многие программы выводят и обычные результаты, и сообщения об ошибках. Мы уже знаем, что это два разных потока (stdout и stderr), поэтому если перенаправить только стандартный вывод
command > output.log
— ошибки всё равно появятся на экране.
Чтобы сохранить всё в одном месте, направьте поток ошибок туда же, куда уже уходит обычный вывод. Это называется объединением потоков.
Классический способ
Для объединения потоков используется следующая запись:
command > output.log 2>&1
Разберём, что здесь происходит, по шагам:
> output.log— направляет стандартный вывод (stdout, поток1) в файлoutput.log.2>&1— говорит системе: «Отправь поток ошибок (stderr, поток2) туда же, куда сейчас направлен поток1».
Другими словами, всё, что команда напишет — и обычные сообщения, и ошибки, — попадёт в один и тот же файл.
Пример:
alice@ubuntu:~$ ./script.sh > output.log 2>&1
Теперь в output.log окажутся и успешный вывод, и ошибки:
alice@ubuntu:~$ cat output.log
Starting process...
Warning: configuration file missing
Process completed successfully
Это удобно, когда вы хотите сохранить весь вывод программы для последующего анализа, например, при отладке скриптов или программ.
Современный короткий синтаксис
Начиная с Bash 4 появился более короткий способ объединить потоки:
alice@ubuntu:~$ ./script.sh &> output.log
Он делает то же самое, что и > output.log 2>&1, но выглядит аккуратнее.
А если нужно добавлять в существующий файл, используйте &>>:
alice@ubuntu:~$ ./script.sh &>> output.log
Важно
Обратите внимание: &> и &>> работают только в Bash. Если вы используете другие оболочки (например, sh или dash), делайте классическую запись > file 2>&1.
Один важный нюанс
Может показаться, что записи > output.log 2> output.log и > output.log 2>&1 эквивалентны.
Но это не так.
Дело в том, что Bash открывает файлы по порядку, слева направо. Когда вы пишете:
command > output.log 2> output.log
— Bash делает следующее:
- Открывает
output.logдля стандартного вывода (stdout). - Открывает тот же файл ещё раз отдельно для ошибок (
stderr). - Запускает команду.
В результате у вас два разных файловых дескриптора, оба указывают на один и тот же файл, но с разными указателями позиции записи.
Если программа печатает что-то в оба потока почти одновременно, файл может получиться перемешанным или с обрывками строк, например:
Error: missing file
Output: process sError: retrying...
uccessful
То есть порядок строк и даже целостность данных может нарушиться — файл не будет отражать реальный ход вывода.
А вот 2>&1 создаёт общее соединение потоков: оба потока используют один и тот же файловый дескриптор, а значит, пишут в файл согласованно и в правильной последовательности.
Когда стоит объединять потоки
Объединение потоков полезно, когда:
- вы хотите получить единый лог выполнения команды;
- вам нужно проанализировать ошибки позже, без визуального шума в терминале;
- команда запускается в фоне (через
&) и её вывод нужно сохранить целиком.
Но объединение потоков не всегда удобно — чаще полезнее разделять их, чтобы не смешивать обычные результаты и сообщения об ошибках.
Важно
Выбор зависит от задачи: для отладки — объединяем, для анализа — разделяем.
Перенаправление ввода (stdin)
Мы уже научились управлять выводом команд — направлять результаты в файлы или объединять их в логи.
Теперь посмотрим на процесс с другой стороны: как команды получают данные для работы.
Большинство программ в Linux ждут ввод из стандартного потока ввода (stdin).
Обычно это клавиатура: вы печатаете текст, программа его читает.
Но мы можем заменить клавиатуру файлом, чтобы программа читала данные оттуда автоматически.
Для этого используется символ <.
Пример 1. Подать данные из файла
Команда sort сортирует строки текста.
Мы можем подать ей файл напрямую в качестве аргумента:
alice@ubuntu:~$ sort names.txt
Или сделать то же самое с использованием <:
alice@ubuntu:~$ sort < names.txt
Результат будет одинаковым. sort возьмёт содержимое names.txt из потока ввода (stdin), отсортирует и выведет результат на экран (stdout):
Alice
Bob
Charlie
Diana
Разницы на экране нет, но < становится особенно полезен, когда вы строите цепочки команд или работаете со скриптами, ведь он позволяет гибко заменять источник данных.
Пример 2. Подать файл в wc
Команда wc -l подсчитывает количество строк во входных данных.
Её можно использовать с файлом в качестве аргумента:
alice@ubuntu:~$ wc -l data.txt
42 data.txt
А можно подать файл через стандартный ввод:
alice@ubuntu:~$ wc -l < data.txt
42
Разница в том, что при использовании < команда видит только поток данных, без имени файла.
Поэтому в выводе остаётся только число 42, без лишней информации.
Пример 3. cat < file.txt
Если подать файл через <, команда cat просто прочитает его содержимое из потока ввода и выведет на экран:
alice@ubuntu:~$ cat < file.txt
Hello from stdin!
Эта запись полностью эквивалентна:
alice@ubuntu:~$ cat file.txt
Hello from stdin!
Здесь < просто заменяет «чтение из файла по имени» «чтением из стандартного ввода».
Это мелочь, но в скриптах иногда именно такая форма бывает удобнее, особенно если имя файла хранится в переменной или подставляется автоматически.
Как это работает «под капотом»
Когда вы вводите < file.txt, оболочка открывает файл и подключает его как источник данных для команды.
Команда «думает», что данные приходят с клавиатуры, хотя на самом деле они читаются из файла.
Можно представить это схематично:
Файл в потоке ввода
Таким образом, < — это операция, обратная >:
>отправляет данные из команды в файл,<подаёт данные из файла в команду.
Примеры на практике
| Задача | Команда | Что происходит |
|---|---|---|
| Отсортировать строки из файла | sort < names.txt |
Содержимое файла читается как ввод |
| Подсчитать строки в файле | wc -l < data.txt |
wc считает строки, поданные через stdin |
| Просмотреть содержимое файла | cat < file.txt |
Эквивалентно cat file.txt |
| Передать данные из одного файла в другой | cat < old.txt > new.txt |
Чтение из old.txt, запись в new.txt |
Использование < не обязательно, но удобно, когда вы:
- хотите писать команды в универсальной форме, не привязываясь к конкретным файлам;
- работаете с перенаправлениями в обе стороны (например,
cat < input.txt > output.txt); - хотите автоматизировать передачу данных между программами — в конвейерах, циклах или скриптах.
Примеры сочетаний перенаправлений
Мы уже познакомились с каждым типом перенаправления по отдельности. Теперь посмотрим, как их можно комбинировать.
В Linux можно соединять разные направления потоков как конструктор: перенаправлять ввод и вывод, разделять ошибки, объединять их или добавлять данные в существующие файлы.
Эта гибкость делает терминал невероятно мощным инструментом: вы можете точно контролировать, куда уходит каждый байт информации.
| Задача | Команда | Что происходит |
|---|---|---|
| Записать результат в файл | echo "ok" > log.txt |
Основной вывод (stdout) записывается в файл. Если файл существует — он будет перезаписан |
| Добавить результат к концу файла | echo "done" >> log.txt |
Данные добавляются в конец файла (stdout в режиме дозаписи) |
| Сохранить только ошибки | ls /root 2> err.txt |
Ошибки (stderr) направляются в err.txt, обычный вывод остаётся на экране |
| Разделить потоки | ./run.sh > out.txt 2> err.txt |
Основной вывод (stdout) идёт в out.txt, ошибки (stderr) — в err.txt |
| Объединить всё в один лог | ./run.sh &> all.txt |
И обычный вывод, и ошибки сохраняются в одном файле all.txt |
| Подать ввод из файла | sort < names.txt |
Файл names.txt используется как источник данных (stdin) |
Как это запомнить
Если попытаться визуализировать всё, что происходит, картина будет примерно такая:
Файл в потоке ввода
1>— основной поток (stdout),2>— поток ошибок (stderr),<— поток ввода (stdin).
А комбинации вроде 2>&1 просто говорят оболочке: «Отправь ошибки туда же, куда направлен обычный вывод».
Коды возврата команд
Когда вы запускаете любую команду в терминале, она не только выводит результат на экран, но и сообщает системе, чем всё закончилось.
Это делается при помощи кода возврата (англ. exit code).
Код возврата — это число, которое программа передаёт обратно оболочке после завершения.
Оно показывает, успешно ли выполнена команда, и используется для автоматической проверки результата в скриптах и цепочках команд.
| Код | Значение | Что это значит |
|---|---|---|
0 |
Успех | Команда выполнилась без ошибок |
1 |
Общая ошибка | Что-то пошло не так (универсальный код) |
2 |
Ошибка аргументов | Команде переданы неверные параметры или файл не найден |
126 |
Команда найдена, но не может быть выполнена | Нет прав или это не исполняемый файл |
127 |
Команда не найдена | Bash не смог найти указанную команду |
130 |
Прерывание пользователем (Ctrl + C) | Выполнение остановлено вручную |
Важно
Эти значения не жёстко фиксированы: разные программы могут использовать свои коды ошибок, но принцип остаётся тем же: 0 всегда означает «всё хорошо».
Как проверить код возврата
После выполнения любой команды вы можете узнать её код при помощи специальной переменной $?.
Эта переменная всегда хранит код последней выполненной команды.
Пример успешной команды:
alice@ubuntu:~$ ls /home
alice
bob
carol
alice@ubuntu:~$ echo $?
0
Код 0 говорит о том, что последняя команда (а именно ls /home) выполнилась успешно.
Если команда завершилась с ошибкой:
alice@ubuntu:~$ ls /notfound
ls: cannot access '/notfound': No such file or directory
alice@ubuntu:~$ echo $?
2
Теперь код возврата равен 2. Это значит, что последняя команда выполнилась неуспешно, в нашем случае ls не смогла найти указанный каталог.
Даже если вы не видите вывода на экран, Bash всегда хранит код возврата последней команды. Это ключевой элемент для работы логических операторов и условий в скриптах, о них мы поговорим в следующей статье.
/dev/null — «чёрная дыра»
Иногда команда работает правильно, но её вывод совершенно не нужен.
Например, вы запускаете скрипт, который печатает десятки строк служебной информации, а вам важно только то, что он завершился успешно.
В таких случаях можно «утилизировать» весь вывод, не загромождая терминал.
Для этого в Linux существует специальное место — /dev/null.
Что это такое
/dev/null — это виртуальный файл, который не хранит данные. Всё, что вы в него запишете, навсегда исчезает.
Можно представить его как «чёрную дыру», которая принимает любые данные, но никогда их не возвращает.
Попробуйте:
alice@ubuntu:~$ echo "Hello" > /dev/null
Ничего не произошло, и это нормально.
Файл /dev/null просто «поглотил» строку Hello, не записав её никуда.
Полное подавление вывода
Если вы хотите, чтобы команда выполнялась молча, можно перенаправить в /dev/null и стандартный вывод (stdout), и ошибки (stderr):
alice@ubuntu:~$ ls -l /var /root > /dev/null 2>&1
Разберём, что делает эта запись:
> /dev/null— отправляет обычный вывод (stdout) в «чёрную дыру».2>&1— направляет поток ошибок (stderr) туда же, куда сейчас направленstdout, то есть тоже в/dev/null.
В результате команда выполнится, но на экране не появится ни одной строки, даже если произойдут ошибки.
Примеры использования
| Цель | Команда | Что происходит |
|---|---|---|
| Скрыть успешный вывод, но оставить ошибки | command > /dev/null |
Ошибки всё ещё видны на экране |
| Скрыть только ошибки | command 2> /dev/null |
Основной результат остаётся |
| Скрыть всё | command > /dev/null 2>&1 |
Команда выполняется полностью в тишине |
Когда это полезно
Например, для автоматизации: скрипты и cron-задачи часто перенаправляют вывод в /dev/null, чтобы не засорять логи.
Также в тестировании можно проверять код возврата без вывода, как в примере ниже:
grep "ERROR" app.log > /dev/null
echo $?
А ещё при отладке удобно временно «заглушить» шумную команду, не влияя на логику скрипта.
В Linux есть и другие специальные устройства в /dev/, например:
/dev/zero— бесконечный поток нулей (используется для тестирования и создания файлов);/dev/random— поток случайных данных.
Но /dev/null — самое известное и часто используемое: оно буквально говорит системе: «Забудь про это».
Что дальше
Теперь, когда вы разобрались, как команды в Linux принимают данные, выводят результаты и сообщают об ошибках, пора перейти к очередному важному шагу — научиться связывать команды между собой.
В следующей статье вы узнаете:
- Что такое пайп (
|) и как он передаёт вывод одной команды в другую. - Как строить простые цепочки команд и сложные конвейеры.
- Как управлять последовательностью выполнения команд с помощью логических операторов.
- И почему принцип KISS (Keep It Simple, Stupid) лежит в основе философии Unix и конвейеров команд.
После этого вы сможете объединять инструменты Linux в единые цепочки, автоматизировать проверки и анализ данных — от простых фильтров до целых сценариев.
Ключевые выводы по статье
- Каждая команда в Linux работает с тремя стандартными потоками:
—stdin— стандартный ввод (обычно клавиатура, но может быть файл или другая команда);
—stdout— стандартный вывод (результат работы команды, по умолчанию — на экран);
—stderr— поток ошибок (сообщения о сбоях и предупреждениях).- Перенаправления позволяют управлять этими потоками:
—>— записать результат в файл (перезаписать его);
—>>— добавить результат в конец файла;
—2>— записать ошибки (stderr) в отдельный файл;
—> out.log 2> err.log— разделить основной и ошибочный выводы;
—> all.log 2>&1или&> all.log— объединить оба потока в один файл;
—< file.txt— подать файл как входные данные (stdin).- Комбинируя перенаправления, можно создавать сложные цепочки: сохранять результаты в один файл, ошибки в другой или подавлять вывод вовсе.
- Коды возврата (или exit-коды) — способ, которым команда сообщает, успешно ли она завершилась:
—0— успех;
— значения, отличные от0, — ошибка (например:2— файл не найден,127— команда не найдена).- Код возврата можно проверить через
$?, они часто используются в скриптах и цепочках команд./dev/null— «чёрная дыра» Linux: всё, что туда записано, исчезает. Используется для подавления ненужного вывода (> /dev/null 2>&1).