# Process substitution: <(cmd) и >(cmd) _Процессы и ресурсы · LinuxLab Knowledge Base_ **TL;DR:** Bash-синтаксис `<(cmd)` подставляет команду как «псевдо-файл» на чтение. `>(cmd)` - на запись. Получаешь временный файл которого не нужно убирать. ## Зачем Часто команда хочет **файл** на вход, а у тебя есть **команда** которая выводит данные. Классический пример: `diff` сравнивает два файла, а ты хочешь сравнить вывод двух команд. Без process substitution - приходится писать во временные файлы: ```bash ls /etc | sort > /tmp/a.txt ls /usr | sort > /tmp/b.txt diff /tmp/a.txt /tmp/b.txt rm /tmp/a.txt /tmp/b.txt # не забыть убрать ``` С process substitution - одна строка: ```bash diff <(ls /etc | sort) <(ls /usr | sort) ``` ## Как это работает `<(cmd)` под капотом - это `/dev/fd/N` (где N - какой-то FD, обычно 63 и далее). Bash: 1. Запускает `cmd` в подпроцессе 2. Создаёт пайп 3. Подставляет в командную строку путь `/dev/fd/63` который читает с этого пайпа Команда (`diff`) открывает этот «файл» как обычный путь - и читает вывод подпроцесса. Никаких временных файлов на диске не создаётся. ```bash echo <(ls) # → /dev/fd/63 cat <(echo hello) # → hello ``` ## Симметричный вариант: >(cmd) `>(cmd)` - наоборот, псевдо-файл **на запись**, в который команда читает stream: ```bash echo "data" | tee >(wc -c > /tmp/byte-count.txt) >/dev/null # → tee пишет копию в подпроцесс wc -c # → wc -c считает байты и сохраняет в файл ``` Применение - раздать вывод одной команды в несколько обработчиков параллельно: ```bash some_command | tee >(grep ERROR > errors.log) >(grep WARN > warns.log) > /dev/null ``` ## Где НЕ работает - **POSIX `/bin/sh`** - нет process substitution. Только bash и zsh. - **Pipe в process substitution** - exit-code теряется. Команда внутри `<(...)` упала, а скрипт этого не заметит даже с `set -e`. Если важен exit-code - пиши в файл и проверяй явно. - **Через `ssh`** - `ssh host '<(cmd)'` не сработает потому что выполняется на удалённой стороне которая может не быть bash. ## Идиомы которые точно встретятся ```bash # Сравнить два каталога по содержимому diff <(ls -la /etc) <(ls -la /etc.bak) # Проверить что вывод команды совпадает с файлом diff <(my_program) expected.txt && echo OK # Записать STDOUT и STDERR в РАЗНЫЕ файлы (без 2>&1) cmd > >(tee out.log) 2> >(tee err.log >&2) # Использовать while read с подсчётом совпадений (НЕ через pipe - иначе # переменная теряется в subshell) count=0 while read -r line; do ((count++)) done < <(grep ERROR app.log) echo "$count" # ← переменная сохранилась, потому что НЕТ pipe ``` Последний случай - частая ловушка: `grep ... | while read; do ((count++)); done` не работает (count останется 0), потому что pipe запускает while в subshell. Process substitution это лечит. ## Команды ```bash diff <(cmd1) <(cmd2) ``` Сравнить вывод двух команд без временных файлов ```bash while read x; do ...; done < <(cmd) ``` Цикл по строкам команды без потери переменных в subshell ```bash tee >(grep err > errors) >(grep warn > warns) > /dev/null ``` Параллельный fan-out одного потока в несколько обработчиков ```bash comm -12 <(sort a) <(sort b) ``` Найти строки общие для двух команд (intersection) ## См. также - [File descriptors (stdin, stdout, stderr)](/kb/file-descriptors.md) - [Here-doc и here-string: данные внутри скрипта](/kb/heredoc.md) - [Процесс и PID](/kb/process-and-pid.md)