Как перенаправить stdout+stderr в один файл, сохраняя потоки отдельно?

Перенаправление stdout+stderr таким образом, что оба записываются в файл, а вывод на stdout все же достаточно прост:

cmd 2>&1 | tee output_file

Но тогда теперь оба stdout / stderr из cmd приходят на стандартный вывод. Я хотел бы написать stdout+stderr в тот же файл (так что порядок сохраняется, предполагая, что cmd является однопоточным), но затем все еще можно перенаправить их отдельно, что-то вроде этого:

some_magic_tee_variant combined_output cmd > >(command-expecting-stdout) 2> >(command-expecting-stderr)

Таким образом, комбинированный_отчет содержит оба параметра с сохраненным порядком, но команда-ожидающий-stdout получает только stdout, а команда-ожидающий-stderr получает только stderr. По сути, я хочу войти в stdout+stderr, при этом позволяя stdout и stderr отдельно перенаправляться и передаваться по конвейеру. Проблема с тройным подходом состоит в том, что он объединяет их. Есть ли способ сделать это в bash/zsh?

5 ответов

Из того, что я понимаю, это то, что вы ищете. Сначала я написал небольшой скрипт для написания на stdout и stderr. Это выглядит так:

$ cat foo.sh 
#!/bin/bash

echo foo 1>&2
echo bar

Затем я запустил это так:

$ ./foo.sh 2> >(tee stderr | tee -a combined) 1> >(tee stdout | tee -a combined)
foo
bar

Результаты в моем bash выглядеть так:

$ cat stderr
foo
$ cat stdout 
bar
$ cat combined 
foo
bar

Обратите внимание, что флаг -a необходим, поэтому teeне перезаписывать другой teeсодержание.

Вот как я это делаю:

exec 3>log ; example_command 2>&1 1>&3 | tee -a log ; exec 3>&-

Работал Пример

bash$ exec 3>log ; { echo stdout ; echo stderr >&2 ; } 2>&1 1>&3 | \
      tee -a log ; exec 3>&-
stderr
bash$ cat log
stdout
stderr

Вот как это работает:

exec 3>log устанавливает дескриптор файла 3 для перенаправления в файл с именем log до дальнейшего уведомления.

example_command чтобы сделать это рабочим примером, я использовал { echo stdout ; echo stderr >&2 ; }, Или вы могли бы использовать ls /tmp doesnotexist чтобы обеспечить вывод вместо.

Нужно прыгнуть вперед к трубе | на данный момент, потому что Bash делает это первым. Канал устанавливает канал и перенаправляет дескриптор файла 1 в этот канал. Так что теперь STDOUT идет в трубу.

Теперь мы можем вернуться туда, где мы были в следующей интерпретации слева направо: 2>&1 это говорит о том, что ошибки программы должны идти туда, куда указывает STDOUT, то есть в канал, который мы только что установили.

1>&3 означает, что STDOUT перенаправляется в файловый дескриптор 3, который мы ранее настроили для вывода на log файл. Таким образом, STDOUT из команды просто входит в файл журнала, а не в STDOUT терминала.

tee -a log принимает его входные данные из канала (которые вы запомните, теперь это ошибки команды) и выводит их в STDOUT, а также добавляет их в log файл.

exec 3>&- закрывает дескриптор файла 3.

Комментарий Виктора Сергиенко - это то, что сработало для меня, добавление exec в начало делает эту работу для всего сценария (вместо того, чтобы ставить его после отдельных команд)

exec 2> >(tee -a output_file >&2) 1> >(tee -a output_file)

{ { cmd | tee out >&3; } 2>&1 | tee err >&2; } 3>&1

Или, чтобы быть педантичным:

{ { cmd 3>&- | tee out >&3 2> /dev/null; } 2>&1 | tee err >&2 3>&- 2> /dev/null; } 3>&1

Обратите внимание, что бесполезно пытаться сохранить порядок. Это в принципе невозможно. Единственным решением было бы изменить "cmd" или использовать некоторые LD_PRELOAD или же gdb хак,

Порядок действительно может быть сохранен. Вот пример, который фиксирует стандартный вывод и ошибку в порядке их генерации в лог-файл, отображая при этом только стандартную ошибку на любом экране терминала, который вам нравится. Настроить, чтобы удовлетворить ваши потребности.

1. Откройте два окна (раковины)

2. Создайте несколько тестовых файлов

touch /tmp/foo /tmp/foo1 /tmp/foo2

3. В окне 1:

mkfifo /tmp/fifo
</tmp/fifo cat - >/tmp/logfile

4. Затем в окне 2:

(ls -l /tmp/foo /tmp/nofile /tmp/foo1 /tmp/nofile /tmp/nofile; echo successful test; ls /tmp/nofile1111) 2>&1 1>/tmp/fifo | tee /tmp/fifo 1>/dev/pts/1

Где /dev/pts/1 может быть любым отображением терминала, которое вы хотите. Подоболочка запускает некоторые команды "ls" и "echo" последовательно, некоторые из них выполняются успешно (предоставление stdout), а некоторые - неудачно (предоставление stderr), чтобы создать смешанный поток выходных сообщений и сообщений об ошибках, чтобы вы могли проверить правильность упорядочения в файл журнала.

Другие вопросы по тегам