РЕДАГУВАТИ: Я бачу, що мене зіпсували з рейок, і в підсумку я відповів на запитання, відмінне від поставленого. Відповідь на справжнє питання знаходиться внизу відповіді Пола Томбліна. (Якщо ви хочете вдосконалити це рішення для перенаправлення stdout та stderr окремо з якихось причин, ви можете скористатися описаною тут технікою.)
Я прагнув відповіді, яка зберігає різницю між stdout та stderr. На жаль, усі відповіді, що наведені на сьогодні, що зберігають цю відмінність, схильні до перегонів: вони ризикують, що програми побачать неповний внесок, на що я вказував у коментарях.
Я думаю, що я нарешті знайшов відповідь, яка зберігає розбіжність, не схильна до раси і не є страшною дотепністю.
Перший будівельний блок: поміняти місцями stdout та stderr:
my_command 3>&1 1>&2 2>&3-
Другий будівельний блок: якби ми хотіли фільтрувати (наприклад, трійник) лише 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, але жахливо багатослівне).
З іншого боку, ви можете скористатися таємничим вбудованим неявним трійником zsh, щоб отримати набагато коротше рішення для zsh, яке взагалі не запускає трійник:
my_command >&1 >stdout.txt 2>&2 2>stderr.txt
(Я б не здогадався з документів, які я виявив, що >&1
і 2>&2
є тим, що викликає неявний взаємозв'язок zsh; я виявив це методом спроб і помилок.)