3.8 Потоки ввода-вывода и коды возврата

В этом параграфе мы познакомимся с тем, как команды в терминале обмениваются данными. Каждая команда может получать входные данные (stdin), отправлять результат работы (stdout) и сообщения об ошибках (stderr).

Мы научимся управлять этими потоками с помощью перенаправлений и разберёмся, что такое код возврата команды и как его использовать для проверки успеха или ошибки.

Ключевые вопросы параграфа

  1. Что такое стандартные потоки stdin, stdout и stderr?
  2. Что такое перенаправление потока и для чего оно нужно?
  3. Как и для чего комбинировать разные перенаправления?
  4. Что такое коды возврата команд и как их можно проверить?
  5. Что за файл /dev/null и зачем он нужен?

Что такое потоки ввода-вывода

В предыдущем параграфе мы использовали команды вроде grep, wc и diff, чтобы анализировать данные и сравнивать результаты.

Но все эти команды не просто «что-то делают» — они принимают данные, обрабатывают их и отдают результат обратно в терминал.
Именно этот процесс передачи данных называется вводом и выводом (англ. input/output или просто I/O).

Каждая команда в Linux общается с внешним миром через три «канала связи» — потоки ввода-вывода.

Они встроены в систему и существуют для каждой программы по умолчанию:

Поток

Название

Описание

stdin

Стандартный ввод

То, откуда команда получает данные (обычно клавиатура, но может быть файл или вывод другой команды)

stdout

Стандартный вывод

То, куда команда отправляет результат своей работы (обычно экран терминала)

stderr

Стандартный поток ошибок

То, куда команда пишет сообщения об ошибках (тоже обычно экран, но отдельно от основного вывода)

Можно представить это так:

2.8.1

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

Пример 1. Команда без ошибок

1alice@ubuntu:~$ echo "Hello, world!"
2Hello, world!

Команда echo получает текст "Hello, world!" через свои аргументы, выводит его в стандартный поток вывода (stdout), и текст попадает на экран.
Всё прошло успешно — ошибок нет, поэтому в поток stderr ничего не попало.

Пример 2. Команда с ошибкой

1alice@ubuntu:~$ cat missing.txt
2cat: missing.txt: No such file or directory

cat пыталась прочитать файл missing.txt, но не нашла его.
Текст ошибки No such file or directory — это не просто сообщение на экране, это вывод в поток stderr, отдельный от основного вывода stdout.

Почему это важно

Пока команды просто показывают результат на экране, разницы между stdout и stderr не видно.

Но когда вы начнёте перенаправлять вывод в файлы, передавать результаты другим командам или писать скрипты, разделение этих потоков становится критически важным.

Например:

  • вы можете сохранить результат работы программы в файл, но ошибки записать в отдельный;
  • или объединить оба потока, чтобы получить полный журнал выполнения;
  • или вовсе скрыть ошибки, если они вам не нужны.

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

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

Ранее мы уже применяли эту механику, когда вызывали команду и записывали результат в файл c помощью оператора >.

Это частый случай перенаправления вывода. Он нужен, например, чтобы потом проанализировать файл, отправить коллеге или просто не потерять после закрытия терминала.

И точно так же можно перенаправлять вывод ошибок и делать многие другие вещи. Разберём перенаправления подробнее.

Перенаправление стандартного вывода (stdout)

Чтобы записать результат работы команды в файл вместо экрана, используется символ >:

1alice@ubuntu:~$ echo "Hello" > output.txt

Теперь ничего не появилось на экране, потому что текст "Hello" был направлен не в терминал, а в файл output.txt.

Если откроем его, увидим наш результат:

1alice@ubuntu:~$ cat output.txt
2Hello

Символ > создаёт новый файл, если его не было, или перезаписывает существующий, если он уже есть.
Ранее мы создавали новый файл таким образом в качестве альтернативы команде touch.

Запустим команду снова с другим текстом:

1alice@ubuntu:~$ echo "World" > output.txt
2alice@ubuntu:~$ cat output.txt
3World

Видим, что старое содержимое файла исчезло — на его месте теперь только слово World.

Но мы можем не перезаписывать, а добавлять текст в конец файла, и для этого используется двойная стрелка >>:

1alice@ubuntu:~$ echo "Hello" > output.txt
2alice@ubuntu:~$ echo "World" >> output.txt
3alice@ubuntu:~$ cat output.txt
4Hello
5World

Теперь мы видим в файле обе строки, которые мы записали.

Запомните: > — создаёт или перезаписывает файл, >> — добавляет текст в конец файла.

Перенаправление ошибок (stderr)

По умолчанию и обычный вывод (stdout), и ошибки (stderr) печатаются на одном и том же экране.
Но Linux позволяет направлять их в разные файлы.

Для этого используется 2> — цифра 2 обозначает поток ошибок (stderr), а 1 — стандартный вывод (stdout).

Попробуем пример:

1alice@ubuntu:~$ ls /root 2> errors.log

Если у пользователя нет прав доступа к каталогу /root, команда ls выведет сообщение об ошибке. Но теперь ошибка окажется в файле errors.log, а не на экране.

1alice@ubuntu:~$ cat errors.log
2ls: cannot open directory '/root': Permission denied

При этом, если бы в выводе были обычные данные (stdout), они всё равно появились бы в терминале.

Разделение вывода и ошибок

Часто программы выводят и результат, и ошибки одновременно.
Чтобы их не перепутать, можно записать их в разные файлы:

1alice@ubuntu:~$ ls -l /var /root > output.log 2> errors.log

Теперь:

  • всё, что программа выводит «нормально» (stdout), попадёт в output.log;
  • всё, что она пишет как ошибку (stderr), окажется в errors.log.

Можем проверить наши файлы, чтобы в этом убедиться:

1alice@ubuntu:~$ cat output.log
2/var:
3total 40
4drwxr-xr-x  2 root root   4096 Oct  6 14:43 backups
5drwxr-xr-x 15 root root   4096 Sep 23 15:17 cache
6drwxrwsrwt  2 root root   4096 Jun  5 12:55 crash
7drwxr-xr-x 43 root root   4096 Sep 23 15:17 lib
8drwxrwsr-x  2 root staff  4096 Apr 22  2024 local
9lrwxrwxrwx  1 root root      9 Jun  5 12:53 lock -> /run/lock
10drwxrwxr-x  9 root syslog 4096 Oct 19 00:00 log
11drwxrwsr-x  2 root mail   4096 Jun  5 12:53 mail
12drwxr-xr-x  2 root root   4096 Jun  5 12:53 opt
13lrwxrwxrwx  1 root root      4 Jun  5 12:53 run -> /run
14drwxr-xr-x  4 root root   4096 Jun  5 12:53 spool
15drwxrwxrwt  9 root root   4096 Oct 21 07:45 tmp
1alice@ubuntu:~$ cat errors.log
2ls: cannot open directory '/root': Permission denied

Разделение потоков вывода — одна из самых практичных возможностей Linux.
Оно позволяет собирать чистые логи, анализировать ошибки отдельно и не терять полезную информацию в большом потоке сообщений.

Давайте проверим, как вы разобрались с потоками и перенаправлениями вывода:

Чтобы добавить в заметки выделенный текст, нажмите Ctrl + E
Проверь себяСоедините порядковый номер со словом

Объединение потоков

Многие программы выводят и обычные результаты, и сообщения об ошибках. Мы уже знаем, что это два разных потока (stdout и stderr), поэтому если перенаправить только стандартный вывод

1command > output.log

— ошибки всё равно появятся на экране.

Чтобы сохранить всё в одном месте, направьте поток ошибок туда же, куда уже уходит обычный вывод. Это называется объединением потоков.

Классический способ

Для объединения потоков используется следующая запись:

1command > output.log 2>&1

Разберём, что здесь происходит, по шагам:

  1. > output.log — направляет стандартный вывод (stdout, поток 1) в файл output.log.
  2. 2>&1 — говорит системе: «Отправь поток ошибок (stderr, поток 2) туда же, куда сейчас направлен поток 1».

Другими словами, всё, что команда напишет — и обычные сообщения, и ошибки, — попадёт в один и тот же файл.

Пример:

1alice@ubuntu:~$ ./script.sh > output.log 2>&1

Теперь в output.log окажутся и успешный вывод, и ошибки:

1alice@ubuntu:~$ cat output.log
2Starting process...
3Warning: configuration file missing
4Process completed successfully

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

Современный короткий синтаксис

Начиная с Bash 4 появился более короткий способ объединить потоки:

1alice@ubuntu:~$ ./script.sh &> output.log

Он делает то же самое, что и > output.log 2>&1, но выглядит аккуратнее.
А если нужно добавлять в существующий файл, используйте &>>:

1alice@ubuntu:~$ ./script.sh &>> output.log

Обратите внимание: &> и &>> работают только в Bash. Если вы используете другие оболочки (например, sh или dash), делайте классическую запись > file 2>&1.

Один важный нюанс

Может показаться, что записи > output.log 2> output.log и > output.log 2>&1 эквивалентны.

Но это не так.

Дело в том, что Bash открывает файлы по порядку, слева направо. Когда вы пишете:

1command > output.log 2> output.log

— Bash делает следующее:

  1. Открывает output.log для стандартного вывода (stdout).
  2. Открывает тот же файл ещё раз отдельно для ошибок (stderr).
  3. Запускает команду.

В результате у вас два разных файловых дескриптора, оба указывают на один и тот же файл, но с разными указателями позиции записи.
Если программа печатает что-то в оба потока почти одновременно, файл может получиться перемешанным или с обрывками строк, например:

1Error: missing file
2Output: process sError: retrying...
3uccessful

То есть порядок строк и даже целостность данных может нарушиться — файл не будет отражать реальный ход вывода.

А вот 2>&1 создаёт общее соединение потоков: оба потока используют один и тот же файловый дескриптор, а значит, пишут в файл согласованно и в правильной последовательности.

Когда стоит объединять потоки

Объединение потоков полезно, когда:

  • вы хотите получить единый лог выполнения команды;
  • вам нужно проанализировать ошибки позже, без визуального шума в терминале;
  • команда запускается в фоне (через &) и её вывод нужно сохранить целиком.

Но объединение потоков не всегда удобно — чаще полезнее разделять их, чтобы не смешивать обычные результаты и сообщения об ошибках.

Выбор зависит от задачи: для отладки — объединяем, для анализа — разделяем.

Давайте попробуем составить команду, которая скопирует каталог /var/log в /mnt/backup, а результат выполнения и ошибки копирования сохранит в файл backup.log:

Чтобы добавить в заметки выделенный текст, нажмите Ctrl + E
Проверь себяРасставь слова по порядку

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

Мы уже научились управлять выводом команд — направлять результаты в файлы или объединять их в логи.
Теперь посмотрим на процесс с другой стороны: как команды получают данные для работы.

Большинство программ в Linux ждут ввод из стандартного потока ввода (stdin).
Обычно это клавиатура: вы печатаете текст, программа его читает.
Но мы можем заменить клавиатуру файлом, чтобы программа читала данные оттуда автоматически.

Для этого используется символ <.

Пример 1. Подать данные из файла

Команда sort сортирует строки текста.
Мы можем подать ей файл напрямую в качестве аргумента:

1alice@ubuntu:~$ sort names.txt

Или сделать то же самое с использованием <:

1alice@ubuntu:~$ sort < names.txt

Результат будет одинаковым. sort возьмёт содержимое names.txt из потока ввода (stdin), отсортирует и выведет результат на экран (stdout):

1Alice
2Bob
3Charlie
4Diana

Разницы на экране нет, но < становится особенно полезен, когда вы строите цепочки команд или работаете со скриптами, ведь он позволяет гибко заменять источник данных.

Пример 2. Подать файл в wc

Команда wc -l подсчитывает количество строк во входных данных.
Её можно использовать с файлом в качестве аргумента:

1alice@ubuntu:~$ wc -l data.txt
242 data.txt

А можно подать файл через стандартный ввод:

1alice@ubuntu:~$ wc -l < data.txt
242

Разница в том, что при использовании < команда видит только поток данных, без имени файла.
Поэтому в выводе остаётся только число 42, без лишней информации.

Пример 3. cat < file.txt

Если подать файл через <, команда cat просто прочитает его содержимое из потока ввода и выведет на экран:

1alice@ubuntu:~$ cat < file.txt
2Hello from stdin!

Эта запись полностью эквивалентна:

1alice@ubuntu:~$ cat file.txt
2Hello from stdin!

Здесь < просто заменяет «чтение из файла по имени» «чтением из стандартного ввода».
Это мелочь, но в скриптах иногда именно такая форма бывает удобнее, особенно если имя файла хранится в переменной или подставляется автоматически.

Как это работает «под капотом»

Когда вы вводите < file.txt, оболочка открывает файл и подключает его как источник данных для команды.
Команда «думает», что данные приходят с клавиатуры, хотя на самом деле они читаются из файла.

Можно представить это схематично:

2.8.2

Таким образом, < — это операция, обратная >:

  • > отправляет данные из команды в файл,
  • < подаёт данные из файла в команду.

Примеры на практике

Задача

Команда

Что происходит

Отсортировать строки из файла

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);
  • хотите автоматизировать передачу данных между программами — в конвейерах, циклах или скриптах.

Давайте попробуем составить команду, которая считает количество строк в файле data.txt, подавая его через стандартный ввод, а результат сохранит в файл result.txt:

Чтобы добавить в заметки выделенный текст, нажмите Ctrl + E
Проверь себяРасставь слова по порядку

Примеры сочетаний перенаправлений

Мы уже познакомились с каждым типом перенаправления по отдельности. Теперь посмотрим, как их можно комбинировать.

В 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)

Как это запомнить

Если попытаться визуализировать всё, что происходит, картина будет примерно такая:

2.8.3

  • 1> — основной поток (stdout),
  • 2> — поток ошибок (stderr),
  • < — поток ввода (stdin).

А комбинации вроде 2>&1 просто говорят оболочке: «Отправь ошибки туда же, куда направлен обычный вывод».

Коды возврата команд

Когда вы запускаете любую команду в терминале, она не только выводит результат на экран, но и сообщает системе, чем всё закончилось.
Это делается при помощи кода возврата (англ. exit code).

Код возврата — это число, которое программа передаёт обратно оболочке после завершения.
Оно показывает, успешно ли выполнена команда, и используется для автоматической проверки результата в скриптах и цепочках команд.

Код

Значение

Что это значит

0

Успех

Команда выполнилась без ошибок

1

Общая ошибка

Что-то пошло не так (универсальный код)

2

Ошибка аргументов

Команде переданы неверные параметры или файл не найден

126

Команда найдена, но не может быть выполнена

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

127

Команда не найдена

Bash не смог найти указанную команду

130

Прерывание пользователем (Ctrl + C)

Выполнение остановлено вручную

Эти значения не жёстко фиксированы: разные программы могут использовать свои коды ошибок, но принцип остаётся тем же: 0 всегда означает «всё хорошо».

Как проверить код возврата

После выполнения любой команды вы можете узнать её код при помощи специальной переменной $?.
Эта переменная всегда хранит код последней выполненной команды.

Пример успешной команды:

1alice@ubuntu:~$ ls /home
2alice
3bob
4carol
5alice@ubuntu:~$ echo $?
60

Код 0 говорит о том, что последняя команда (а именно ls /home) выполнилась успешно.

Если команда завершилась с ошибкой:

1alice@ubuntu:~$ ls /notfound
2ls: cannot access '/notfound': No such file or directory
3alice@ubuntu:~$ echo $?
42

Теперь код возврата равен 2. Это значит, что последняя команда выполнилась неуспешно, в нашем случае ls не смогла найти указанный каталог.

Даже если вы не видите вывода на экран, Bash всегда хранит код возврата последней команды. Это ключевой элемент для работы логических операторов и условий в скриптах, о них мы поговорим в следующем параграфе.

/dev/null — «чёрная дыра»

Иногда команда работает правильно, но её вывод совершенно не нужен.
Например, вы запускаете скрипт, который печатает десятки строк служебной информации, а вам важно только то, что он завершился успешно.
В таких случаях можно «утилизировать» весь вывод, не загромождая терминал.

Для этого в Linux существует специальное место — /dev/null.

Что это такое

/dev/null — это виртуальный файл, который не хранит данные. Всё, что вы в него запишете, навсегда исчезает.
Можно представить его как «чёрную дыру», которая принимает любые данные, но никогда их не возвращает.

Попробуйте:

1alice@ubuntu:~$ echo "Hello" > /dev/null

Ничего не произошло, и это нормально.

Файл /dev/null просто «поглотил» строку Hello, не записав её никуда.

Полное подавление вывода

Если вы хотите, чтобы команда выполнялась молча, можно перенаправить в /dev/null и стандартный вывод (stdout), и ошибки (stderr):

1alice@ubuntu:~$ ls -l /var /root > /dev/null 2>&1

Разберём, что делает эта запись:

  1. > /dev/null — отправляет обычный вывод (stdout) в «чёрную дыру».
  2. 2>&1 — направляет поток ошибок (stderr) туда же, куда сейчас направлен stdout, то есть тоже в /dev/null.

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

Примеры использования

Цель

Команда

Что происходит

Скрыть успешный вывод, но оставить ошибки

command > /dev/null

Ошибки всё ещё видны на экране

Скрыть только ошибки

command 2> /dev/null

Основной результат остаётся

Скрыть всё

command > /dev/null 2>&1

Команда выполняется полностью в тишине

Когда это полезно

Например, для автоматизации: скрипты и cron-задачи часто перенаправляют вывод в /dev/null, чтобы не засорять логи.

Также в тестировании можно проверять код возврата без вывода, как в примере ниже:

1grep "ERROR" app.log > /dev/null
2echo $?

А ещё при отладке удобно временно «заглушить» шумную команду, не влияя на логику скрипта.

В Linux есть и другие специальные устройства в /dev/, например:

  • /dev/zero — бесконечный поток нулей (используется для тестирования и создания файлов);
  • /dev/random — поток случайных данных.

Но /dev/null — самое известное и часто используемое: оно буквально говорит системе: «Забудь про это».

Давайте проверим, как вы разобрались с сочетаниями перенаправлений:

Чтобы добавить в заметки выделенный текст, нажмите Ctrl + E
Проверь себяСоедините порядковый номер со словом

Что дальше

Теперь, когда вы разобрались, как команды в 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).


Чтобы добавить в заметки выделенный текст, нажмите Ctrl + E
Предыдущий параграф3.7. Поиск файлов и текста (find, grep, wc)
Следующий параграф3.9. Пайпы, логические операторы и принцип KISS