РЕДАКТИРОВАТЬ: Я вижу, что я сошел с рельсов и в конечном итоге ответил на вопрос, отличный от заданного. Ответ на настоящий вопрос находится в конце ответа Пола Томблина. (Если вы по какой-то причине хотите улучшить это решение для перенаправления stdout и stderr отдельно, вы можете использовать метод, который я описываю здесь.)
Я давно хотел получить ответ, который сохранит различие между stdout и stderr. К сожалению, все ответы, данные до сих пор, которые сохраняют это различие, предрасположены к расе: они рискуют увидеть в программах неполный ввод, как я отмечал в комментариях.
Думаю, я наконец-то нашел ответ, который сохраняет различия, не предрасположен к расе и не слишком утомителен.
Первый строительный блок: поменять местами stdout и stderr:
my_command 3>&1 1>&2 2>&3-
Второй строительный блок: если бы мы хотели фильтровать (например, tee) только stderr, мы могли бы сделать это, поменяв местами stdout и stderr, фильтруя, а затем вернув обратно:
{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-
Теперь все остальное просто: мы можем добавить фильтр stdout в начале:
{ { my_command | stdout_filter;} 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-
или в конце:
{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3- | stdout_filter
Чтобы убедить себя в том, что обе вышеперечисленные команды работают, я использовал следующее:
alias my_command='{ echo "to stdout"; echo "to stderr" >&2;}'
alias stdout_filter='{ sleep 1; sed -u "s/^/teed stdout: /" | tee stdout.txt;}'
alias stderr_filter='{ sleep 2; sed -u "s/^/teed stderr: /" | tee stderr.txt;}'
Выход:
...(1 second pause)...
teed stdout: to stdout
...(another 1 second pause)...
teed stderr: to stderr
и моя подсказка возвращается сразу после " teed stderr: to stderr
", как и ожидалось.
Сноска о zsh :
Вышеупомянутое решение работает в bash (и, возможно, в некоторых других оболочках, я не уверен), но не в zsh. Есть две причины, по которым он не работает в zsh:
- синтаксис
2>&3-
не понимается zsh; это должно быть переписано как2>&3 3>&-
- в zsh (в отличие от других оболочек), если вы перенаправляете дескриптор файла, который уже открыт, в некоторых случаях (я не совсем понимаю, как он решает) вместо этого он выполняет встроенное поведение, подобное тройнику. Чтобы избежать этого, вы должны закрыть каждый fd перед его перенаправлением.
Так, например, мое второе решение нужно переписать для zsh as {my_command 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stderr_filter;} 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stdout_filter
(которое работает и в bash, но ужасно многословно).
С другой стороны, вы можете воспользоваться таинственным встроенным неявным teeing в zsh, чтобы получить гораздо более короткое решение для zsh, которое вообще не запускает tee:
my_command >&1 >stdout.txt 2>&2 2>stderr.txt
(Я бы не догадался из документов, которые я обнаружил, что >&1
и 2>&2
- это то, что запускает неявную игру zsh; я обнаружил это методом проб и ошибок.)