Представьте: вы хотите быстро отредактировать файл и сразу передать его содержимое другой команде. В голову приходит хитрость с подстановкой процесса через >(cat)
, которая отлично работала с echo
. Но стоит попробовать это с nano
или vim
– терминал замирает, курсор мигает, а вы остаётесь в недоумении. Что пошло не так? Давайте разберёмся шаг за шагом.
Как работают анонимные каналы в Bash
Конструкция >(команда)
создаёт анонимный канал (pipe), который связывает вывод одной программы с входом другой. Например, в команде:
echo 42 > >(cat)
…Bash сначала создаёт канал, запускает cat
с подключённым к нему входом, а затем перенаправляет вывод echo
в этот канал. Всё работает, потому что:
echo
записывает данные и сразу завершается- Канал закрывается, и
cat
получает сигнал EOF (конец файла)
Но с редакторами всё иначе. Когда вы пишете:
EDITOR=nano
$EDITOR >(cat)
…происходит следующее:
- Создаётся канал, но он остаётся открытым – редактор начинает работать с ним как с файлом
- Nano пытается прочитать текущее содержимое «файла» (канала), но там ещё ничего нет
- Канал не закрыт – редактор ждёт данных бесконечно (это как стоять у пустого почтового ящика и ждать письмо, которое никогда не придёт)
Кстати, в Linux
/dev/fd/##
– это не прямая ссылка на дескриптор, а новый «вход» в тот же канал. Редактор открывает его заново, создавая петлю зависимости.
Почему named pipes и временные файлы – спасение
Чтобы обойти проблему, нужно разорвать цикл ожидания. Есть два подхода:
1. Именованные каналы (mkfifo)
mkfifo mypipe # создаём именованный канал
cat mypipe & # запускаем читателя в фоне
nano mypipe # редактор будет писать в канал
Но тут есть нюанс:
- Nano откроет канал для записи, но не закроет его автоматически
- После сохранения файла нужно вручную послать EOF (Ctrl+D) или завершить процесс
- Не самый удобный вариант для повседневного использования
2. Временные файлы через mktemp (рекомендуется)
TMPFILE=$(mktemp) # создаём уникальный временный файл
nano "$TMPFILE" # редактируем как обычно
cat "$TMPFILE" # используем результат
rm "$TMPFILE" # не забываем очистить
Преимущества:
- Редактор работает с реальным файлом – нет блокировок
- Данные сохраняются даже при аварийном завершении
- Можно использовать любые редакторы, включая графические
Если очень хочется использовать каналы, можно схитрить:
(sleep 1; cat) < >(nano)
Но это рискованно – тайминги могут сбиться, а данные потеряться.
Глубже в проблему: как каналы влияют на дескрипторы
Когда вы используете >(cat)
, Bash:
- Создаёт pipe с двумя концами: запись (fd 63) и чтение (у cat)
- Подставляет путь вида
/dev/fd/63
как аргумент для редактора - Редактор открывает этот путь заново, получая новый дескриптор (fd 3)
Теперь:
- Редактор держит оба конца канала (исходный fd 63 для записи и новый fd 3 для чтения)
- При попытке чтения из fd 3 он ждёт данных от… самого себя через fd 63
- Получается логическая петля – как микрофон, направленный на колонку
# Примерная схема файловых дескриптеров:
nano:
fd 0 → tty (ввод с клавиатуры)
fd 1 → tty (вывод в терминал)
fd 63 → pipe (запись)
fd 3 → pipe (чтение)
Чтобы избежать таких ситуаций, помните:
- Анонимные каналы подходят для однонаправленных операций с быстрыми командами
- Редакторы и интерактивные программы требуют «стабильных» файловых объектов
- Даже если обойти блокировку (например, через
dd
), данные могут теряться при неаккуратном использовании
Совет из практики: если нужно быстро отредактировать вывод команды, используйте vim <(curl example.com)
– здесь работает подстановка через <(cmd)
, которая создаёт временный файл с содержимым вывода. Но для сохранения изменений всё равно потребуется явно указать куда писать результат.