# File descriptors (stdin, stdout, stderr) _Процессы и ресурсы · LinuxLab Knowledge Base_ **TL;DR:** File descriptor - целое число, через которое процесс обращается к открытому файлу/сокету/pipe. У каждого процесса 0/1/2 = stdin/stdout/stderr. ## Что такое fd Когда [process-and-pid](/kb/process-and-pid.md) открывает что-то (файл, сокет, pipe) - ядро возвращает маленькое целое - **file descriptor**. Дальше процесс обращается к ресурсу по этому числу: `read(fd, ...)`, `write(fd, ...)`, `close(fd)`. fd - индекс в табличке процесса: `/proc//fd/N` указывает на цель. ## Три «магических» fd Каждый запущенный процесс получает три fd по умолчанию: | fd | имя | по умолчанию | |---|---|---| | 0 | stdin | клавиатура (терминал) | | 1 | stdout | экран | | 2 | stderr | экран (отдельный поток!) | Это конвенция Unix: программа читает из 0, пишет результат в 1, ошибки в 2. Шелл умеет перенаправлять каждый поток отдельно. ## Перенаправления в bash ```bash cmd > out.log # stdout → файл (= 1>out.log) cmd 2> err.log # stderr → файл cmd > all.log 2>&1 # СНАЧАЛА stdout в файл, потом stderr туда же cmd > out.log 2> err.log # развести по разным cmd &> all.log # bash-shortcut: и stdout и stderr в один файл cmd < input.txt # stdin ← файл cmd << EOF ... EOF # heredoc: stdin ← inline-текст cmd <<< 'one line' # here-string: stdin ← одна строка ``` ВАЖНО: `2>&1` означает «продублировать fd 1 в fd 2». Порядок имеет значение: `cmd >file 2>&1` правильно (сначала stdout в файл, потом stderr в тот же файл), а `cmd 2>&1 >file` - нет (stderr ушёл туда где stdout БЫЛ - на терминал). ## /dev/null и /dev/stderr ```bash cmd > /dev/null # выкинуть stdout cmd 2> /dev/null # выкинуть stderr (типичное «не показывай ошибки») cmd > /dev/null 2>&1 # выкинуть всё ``` `/dev/null` - спецфайл, всё что пишут - теряется; чтение даёт EOF. ## Pipe = fd между процессами ```bash ls | grep '.txt' ``` Шелл создаёт **pipe** (две связанные fd), форкает оба процесса: у `ls` stdout заменён на write-конец pipe, у `grep` stdin заменён на read-конец. Получается потоковая передача без файлов. ## /proc//fd - что открыто прямо сейчас ```bash ls -l /proc/$$/fd/ # fd текущего шелла (symlink'и на цели) ls -l /proc/$$/fd/0 # обычно → /dev/pts/X (терминал) ls -l /proc//fd/ # любой процесс (нужны права) ``` Это то же что показывает [cmd-lsof](/kb/cmd-lsof.md) `-p `. ## Лимиты Сколько fd может одновременно держать один процесс: ```bash ulimit -n # текущий soft-лимит (обычно 1024 или 65535) cat /proc/sys/fs/file-max # глобальный лимит на ВСЕ процессы ``` «Too many open files» = упёрлись в лимит. Поднимается через `ulimit -n` в shell или `LimitNOFILE=` в systemd unit. ## Дублирование fd: `dup2()` Из C-кода - `dup2(src, dst)` копирует src в dst. Это и есть основа редиректов: shell делает `dup2(open("file"), 1)` чтобы перенаправить stdout процесса перед `exec`-ом. ## Команды ```bash ls -l /proc/$$/fd/ ``` Что открыто текущим шеллом (0/1/2 + всё что fork-ом унаследовали) ```bash command > out.log 2>&1 ``` И stdout, и stderr в один файл - порядок важен ```bash command 2> >(grep -v WARN >&2) ``` Process substitution: фильтровать stderr через grep, не теряя stdout ```bash exec 3>>app.log; echo data >&3 ``` Открыть свой fd 3 на запись и писать туда (полезно в скриптах) ```bash ulimit -n ``` Текущий лимит fd на процесс - упор сюда даёт «Too many open files» ## См. также - [Процесс и PID](/kb/process-and-pid.md) - [lsof - кто что открыл](/kb/cmd-lsof.md) - [strace - какие syscall'ы делает процесс](/kb/cmd-strace.md) - [io_uring - third-rank async I/O syscall](/kb/io-uring.md) - [Inode](/kb/inode.md)