
Недавно я наткнулся на неожиданную проблему с недействительным файлом /dev/stderr
в обновленной версии Cygwin, которая также присутствует в хорошо развитой установке Debian . (Изменение: вопреки тому, что я изначально думал, моя система Debian не выдает эту ошибку, а просто выдает желаемый вывод. Теперь я должен предположить, что это ошибка Cygwin.)
Предыстория: Я использую инструменты, которые выдают тысячи строк вывода (в частности: системы контроля версий на большой производственной системе). Я запускаю их под управлением скрипта и хотел бы опционально перенаправить шумный вывод инструмента в файл журнала. Казалось бы, простым решением всегда перенаправлять их вывод (stderr и stdout) в место назначения файловой системы, которое хранится в переменной среды. Если нужен вывод на терминал (или в какое-либо место назначения, контролируемое пользователем), местом назначения DBG_STDERR
будет просто "/dev/stderr", в противном случае — имя какого-то временного файла. Типичная строка выполнения инструмента будет тогда напоминать noisy_command >> "$DBG_STDERR" 2>&1
.
Это работает нормально, если я не передаю вывод скрипта. Вот минимальное воспроизведение:
$ uname -a
CYGWIN_NT-6.1-WOW xxxxxxx 2.8.1(0.312/5/3) 2017-07-03 14:06 i686 Cygwin
$ bash --version
GNU bash, version 4.4.12(3)-release (i686-pc-cygwin)
$ cat say-something.sh
#!/bin/sh
echo something > /dev/stderr
$ (x=$(./say-something.sh 2> /dev/stderr)) 2>&1 |cat
./say-something.sh: line 2: /dev/stderr: No such file or directory
$ (x=$(./say-something.sh 2> /dev/stderr)) 2>&1
something
$ (x=$(./say-something.sh 2> /dev/stderr)) |cat
something
$ x=$(./say-something.sh 2> /dev/stderr) 2>&1 |cat
something
Конечно, все перенаправления и вложенные оболочки выглядят забавно вне контекста. Дополнительная оболочка необходима, поскольку say-something.sh на самом деле будет вызвана другим скриптом. Избыточное перенаправление fd 2 в stderr — это «переключатель», облегчающий необязательное перенаправление в файл (/dev/stderr или другой путь — это настраиваемое содержимое переменной).
Похоже, что все составляющие этого конвейера необходимы, как показывают эксперименты после неудачного примера: все они успешны.
- Нам нужен последний канал stdout
- Нам нужно копирование stderr в stdout вызывающей стороной
- Нам нужна внешняя оболочка вокруг подстановки команд.
решение1
Имя /dev/stderr
на самом деле допустимо при перенаправлении на канал. Что может быть невозможным, так это открыть конечную цель /dev/stderr
напрямую. Просто посмотрите:
$ (echo Testing testing > /dev/stderr) |& cat
Testing testing
Труба, созданная |
или |&
обычно являющаясяанонимная трубка; отображаемое имяне соответствует объекту в файловой системе. Для иллюстрации вы можете попробовать что-то простое, например:
$ ls -la /dev/fd/ |& cat
total 0
dr-x------ 2 alexp alexp 0 Jul 6 18:23 .
dr-xr-xr-x 9 alexp alexp 0 Jul 6 18:23 ..
lrwx------ 1 alexp alexp 64 Jul 6 18:23 0 -> /dev/pts/4
l-wx------ 1 alexp alexp 64 Jul 6 18:23 1 -> pipe:[1058859]
l-wx------ 1 alexp alexp 64 Jul 6 18:23 2 -> pipe:[1058859]
lr-x------ 1 alexp alexp 64 Jul 6 18:23 3 -> /proc/4335/fd
Очень необычно пытаться открыть (конечную) цель /dev/stderr
; имя /dev/stderr
указано в порядкеизбегатьутруждая себя выяснением фактической цели.
решение2
Я думаю, проблема в порожденном readline-процессе. Он получает свой собственный канал для перенаправления, который закрывается, когда процесс останавливается (полученный вами pid не является pid оболочки, а pid readlink-процесса). Канал становится недействительным, когда процесс завершается. Попробуйте использовать fifos/именованные каналы.