stdin, stdout и stderr
Каждый процесс стартует с тремя открытыми файловыми дескрипторами: stdin (fd 0) читает ввод, stdout (fd 1) пишет обычный вывод, stderr (fd 2) пишет ошибки. Понимание этих трёх потоков — фундамент конвейеров оболочки.
Каждая запускаемая тобой команда — ls, cat, grep — откуда-то читает и куда-то пишет. Ты видел, как текст появляется в терминале при запуске команд, но, вероятно, не задумывался, куда этот текст идёт и почему ошибки оказываются вперемешку с обычным выводом. Оболочка даёт каждому процессу три открытых канала с момента его запуска. Понимание этих трёх каналов делает конвейеры и перенаправление понятными.
К концу урока ты будешь знать, что такое stdin, stdout и stderr, какой номер файлового дескриптора несёт каждый из них, и почему важно держать ошибки в отдельном потоке.
После этого урока ты сможешь назвать три стандартных потока, указать их номера файловых дескрипторов (0, 1, 2), объяснить назначение каждого и описать, зачем stderr отделён от stdout.
Каждый процесс наследует три открытых файловых дескриптора. Когда ядро запускает любой процесс, оно передаёт ему три уже открытых файловых дескриптора — числовых дескриптора, ссылающихся на открытые файлы или устройства. Ядро назначает небольшие целые числа:
- fd 0 — стандартный ввод (stdin)
- fd 1 — стандартный вывод (stdout)
- fd 2 — стандартный поток ошибок (stderr)
Файловый дескриптор — это просто число, которое процесс использует для чтения из чего-либо или записи куда-либо. Ядро хранит таблицу, сопоставляющую каждое число реальному ресурсу (терминальному устройству, файлу, каналу). Три стандартных дескриптора — соглашение, на которое опирается каждая Unix-программа.
stdin (fd 0) — это место, откуда процесс читает ввод. По умолчанию stdin подключён к клавиатуре — терминальному устройству. Когда запускается команда, читающая ввод (cat без аргументов, read, grep без имени файла), она блокируется в ожидании твоего ввода. Всё, что ты вводишь, попадает в fd 0.
cat
hello world
hello world
^Dcat без имени файла читает из stdin до конца файла (отправляется нажатием Ctrl+D). Он отражает каждую строку обратно в stdout по мере чтения.
stdout (fd 1) — это место, куда процесс пишет обычный вывод. По умолчанию stdout подключён к экрану терминала. Когда ls выводит список файлов или echo печатает строку, эти байты идут в fd 1. Поскольку stdout и stderr по умолчанию подключены к одному экрану терминала, ты обычно не можешь визуально отличить их друг от друга — оба выглядят как текст в терминале.
echo "Hello from stdout"Вывод:
Hello from stdoutЭта строка пошла в fd 1 → экран терминала.
stderr (fd 2) — это место, куда процесс пишет сообщения об ошибках и диагностику. По умолчанию он тоже подключён к экрану терминала, но является отдельным каналом от stdout. Программы пишут ошибки, предупреждения и сообщения о ходе работы в fd 2, чтобы не засорять обычный поток данных на fd 1.
ls /nonexistentВывод:
ls: cannot access '/nonexistent': No such file or directoryЭто сообщение об ошибке пошло в fd 2, а не в fd 1. Когда оба потока идут на один терминал, ты видишь их вперемежку, но они являются физически отдельными файловыми дескрипторами — а значит, их можно перенаправлять независимо.
Почему это разделение важно? Если скрипт производит 10 000 строк вывода, которые будет обрабатывать другой инструмент, любые сообщения об ошибках, смешанные с fd 1, испортят данные. Хранение ошибок в fd 2 означает, что потребитель fd 1 всегда получает чистый вывод.
Оболочка подключает эти потоки к терминалу по умолчанию, но ты можешь перепроложить их. Терминал — лишь цель по умолчанию. Оболочка может направить fd 0 в файл (чтобы программа читала из файла вместо клавиатуры), направить fd 1 в файл (чтобы вывод сохранялся, а не печатался) или подключить fd 1 одного процесса к fd 0 другого (конвейер). Весь этот механизм — перенаправление и каналы — построен поверх этих трёх файловых дескрипторов. Синтаксис ты узнаешь в следующих уроках; пока просто знай, что fd 0, 1 и 2 — это сантехника, которая делает всё это возможным.
Видим stdout и stderr по отдельности.
Запусти команду, которая одним вызовом производит и обычный вывод, и ошибку:
ls /etc/hosts /nonexistentВывод (перемешанный в терминале):
ls: cannot access '/nonexistent': No such file or directory
/etc/hostsСтрока ошибки пришла из fd 2, а список файлов — из fd 1. Они отображаются вместе, потому что оба потока по умолчанию идут на терминал. Теперь перенаправь stdout в файл, чтобы увидеть их раздельно:
ls /etc/hosts /nonexistent > /tmp/out.txtВ терминале видно (только stderr, потому что stdout пошёл в файл):
ls: cannot access '/nonexistent': No such file or directorycat /tmp/out.txt/etc/hostsДва потока всегда были раздельными — терминал просто отображал оба в одном окне.
▸Почему это работает
На macOS те же три стандартных потока с теми же номерами файловых дескрипторов. Ядро и библиотека C разные, но соглашение POSIX (fd 0/1/2 = stdin/stdout/stderr) идентично. Каждая Unix-производная система его придерживается.
▸Частая ошибка
Распространённое заблуждение: stderr не «менее важен» — он просто отдельный. Многие программы пишут прогресс-бары, отладочную информацию и подробные логи в stderr, чтобы не засорять stdout. Когда ты перенаправляешь stdout в файл, все эти сообщения всё равно появляются в терминале, потому что идут в stderr. Это намеренный дизайн, а не баг.
Программа записывает сообщение об ошибке. Какой файловый дескриптор она использует по соглашению?
Каждый процесс стартует с тремя стандартными файловыми дескрипторами: fd 0 (stdin) для чтения ввода, fd 1 (stdout) для записи обычного вывода и fd 2 (stderr) для записи ошибок и диагностических сообщений. По умолчанию все три подключены к терминалу, поэтому оба потока вывода отображаются на экране. Хранение ошибок в отдельном потоке (fd 2) означает, что потребитель fd 1 всегда получает чистые данные — а оболочка может независимо перенаправлять или соединять в конвейер каждый поток. Эта трёхдескрипторная модель — фундамент, на котором работают все перенаправления и конвейеры оболочки.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.