Как мне написать stderr в файл при использовании «tee» с конвейером?

372

У меня есть команда, которая будет выводить вывод aaa.sh на экран, одновременно записывая stdout на bbb.out; однако я также хотел бы написать stderr в файл с именем ccc.out. Любые предложения о том, как изменить фигуру ниже?

./aaa.sh | tee ./bbb.out

Обновление: stdout и stderr все равно должны быть напечатаны на экране, независимо.

  • 2
    Чтобы уточнить - вы хотите, чтобы stderr шел на экран вместе с файлом?
  • 0
    Я сделал, я буду редактировать свой пост, чтобы уточнить это. Я верю, что решения Луната будет достаточно. Спасибо всем за помощь!
Теги:

8 ответов

590
Лучший ответ

Я предполагаю, что вы хотите видеть STDERR и STDOUT на терминале. Вы можете пойти на ответ Джоша Келли, но я нахожу, что в фоновом режиме tail выводят ваш файл журнала очень хаки и cludgy. Обратите внимание, как вам нужно сохранить exra FD и после этого очистить, убив его, и технически это нужно сделать в trap '...' EXIT.

Существует лучший способ сделать это, и вы уже обнаружили его: tee.

Только вместо того, чтобы просто использовать его для вашего stdout, у вас есть тройник для stdout и один для stderr. Как вы это сделаете? Замена процессов и перенаправление файлов:

command > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)

Разделите его и объясните:

> >(..)

>(...) (подстановка процесса) создает FIFO и позволяет tee прослушивать его. Затем он использует > (перенаправление файлов) для перенаправления STDOUT command в FIFO, который прослушивает ваш первый tee.

То же самое для второго:

2> >(tee -a stderr.log >&2)

Мы снова используем замещение процесса, чтобы сделать процесс tee, который читает из STDIN и выгружает его в stderr.log. tee выводит свой вход обратно на STDOUT, но поскольку его вход является нашим STDERR, мы хотим снова перенаправить tee STDOUT на наш STDERR. Затем мы используем перенаправление файлов для перенаправления command STDERR на вход FIFO (tee STDIN).

См. http://mywiki.wooledge.org/BashGuide/InputAndOutput

Подстановка процесса - одна из тех прекрасных вещей, которые вы получаете в качестве бонуса при выборе bash в качестве вашей оболочки вместо sh (POSIX или Bourne).


В sh вам придется делать что-то вручную:

out="${TMPDIR:-/tmp}/out.$$" err="${TMPDIR:-/tmp}/err.$$"
mkfifo "$out" "$err"
trap 'rm "$out" "$err"' EXIT
tee -a stdout.log < "$out" &
tee -a stderr.log < "$err" >&2 &
command >"$out" 2>"$err"
  • 4
    Я попробовал это: $ echo "HANG" > >(tee stdout.log) 2> >(tee stderr.log >&2) который работает, но ждет ввода. Есть ли простая причина, почему это происходит?
  • 0
    @Justin: я не знаю, что вы подразумеваете под "ждет ввода". Команда, которую вы дали, ничего не «ждет» со мной.
Показать ещё 5 комментариев
431

почему бы не просто:

./aaa.sh 2>&1 | tee -a log

Это просто перенаправляет stderr на stdout, поэтому tee эхосигнал как для регистрации, так и для экранирования. Может быть, я что-то упустил, потому что некоторые из других решений кажутся очень сложными.

Примечание: Поскольку bash версия 4, вы можете использовать |& как аббревиатуру для 2>&1 |:

./aaa.sh |& tee -a log
  • 0
    Действительно, они сложны. Мне интересно это самому.
  • 73
    Это прекрасно работает, если вы хотите, чтобы и stdout (канал 1), и stderr (канал 2) были зарегистрированы в одном и том же файле (один файл, содержащий смесь как stdout, так и sterr). Другое, более сложное решение позволяет разделить stdout и stderr на 2 разных файла (stdout.log и stderr.log соответственно). Иногда это важно, иногда нет.
Показать ещё 10 комментариев
33

Это может быть полезно для людей, которые находят это через Google. Просто раскомментируйте пример, который вы хотите опробовать. Конечно, не стесняйтесь переименовывать выходные файлы.

#!/bin/bash

STATUSFILE=x.out
LOGFILE=x.log

### All output to screen
### Do nothing, this is the default


### All Output to one file, nothing to the screen
#exec > ${LOGFILE} 2>&1


### All output to one file and all output to the screen
#exec > >(tee ${LOGFILE}) 2>&1


### All output to one file, STDOUT to the screen
#exec > >(tee -a ${LOGFILE}) 2> >(tee -a ${LOGFILE} >/dev/null)


### All output to one file, STDERR to the screen
### Note you need both of these lines for this to work
#exec 3>&1
#exec > >(tee -a ${LOGFILE} >/dev/null) 2> >(tee -a ${LOGFILE} >&3)


### STDOUT to STATUSFILE, stderr to LOGFILE, nothing to the screen
#exec > ${STATUSFILE} 2>${LOGFILE}


### STDOUT to STATUSFILE, stderr to LOGFILE and all output to the screen
#exec > >(tee ${STATUSFILE}) 2> >(tee ${LOGFILE} >&2)


### STDOUT to STATUSFILE and screen, STDERR to LOGFILE
#exec > >(tee ${STATUSFILE}) 2>${LOGFILE}


### STDOUT to STATUSFILE, STDERR to LOGFILE and screen
#exec > ${STATUSFILE} 2> >(tee ${LOGFILE} >&2)


echo "This is a test"
ls -l sdgshgswogswghthb_this_file_will_not_exist_so_we_get_output_to_stderr_aronkjegralhfaff
ls -l ${0}
  • 4
    Нет, и я думаю, что exec может использовать некоторые объяснения. exec > означает, переместить цель дескриптора файла в определенное место назначения. По умолчанию используется значение 1, поэтому exec > /dev/null теперь перемещает вывод stdout в / dev / null в этом сеансе. Текущие файловые дескрипторы для этого сеанса можно увидеть, выполнив ls -l /dev/fd/ . Попытайся! Затем посмотрите, что происходит, когда вы exec 2>/tmp/stderr.log. Кроме того, exec 3>&1 означает, что создайте новый файловый дескриптор с номером 3 и перенаправьте его на цель файлового дескриптора 1. В этом примере целью был экран, когда была выполнена команда.
17

Чтобы перенаправить stderr в файл, отобразите stdout на экран, а также сохраните stdout в файл:

./aaa.sh 2>ccc.out | tee ./bbb.out

EDIT. Чтобы отобразить как stderr, так и stdout на экран, а также сохранить оба файла, вы можете использовать bash I/O перенаправление:

#!/bin/bash

# Create a new file descriptor 4, pointed at the file
# which will receive stderr.
exec 4<>ccc.out

# Also print the contents of this file to screen.
tail -f ccc.out &

# Run the command; tee stdout as normal, and send stderr
# to our file descriptor 4.
./aaa.sh 2>&4 | tee bbb.out

# Clean up: Close file descriptor 4 and kill tail -f.
exec 4>&-
kill %1
  • 1
    Я ожидаю, что пользователь хочет, чтобы stderr пошел к своей консоли в дополнение к файлу, хотя это явно не было указано.
  • 1
    Я должен был быть более ясным, я действительно хотел, чтобы stderr на экран тоже. Мне все еще нравилось решение Джоша Келли, но я нашел решение lhunath, которое больше соответствует моим потребностям. Спасибо, парни!
13

Другими словами, вы хотите передать stdout в один фильтр (tee bbb.out) и stderr в другой фильтр (tee ccc.out). Нет стандартного способа прокладывать что-то другое, кроме stdout, в другую команду, но вы можете обойти это, жонглируя файловыми дескрипторами.

{ { ./aaa.sh | tee bbb.out; } 2>&1 1>&3 | tee ccc.out; } 3>&1 1>&2

См. также Как выполнить стандартный поток ошибок (stderr)? и Когда вы будете использовать дополнительный файловый дескриптор?

В bash (и ksh и zsh), но не в других оболочках POSIX, таких как тире, вы можете использовать замену процессов:

./aaa.sh > >(tee bbb.out) 2> >(tee ccc.out)

Остерегайтесь, что в bash эта команда возвращается, как только заканчивается ./aaa.sh, даже если команды tee все еще выполняются (ksh и zsh ждут подпроцессы). Это может быть проблемой, если вы делаете что-то вроде ./aaa.sh > >(tee bbb.out) 2> >(tee ccc.out); process_logs bbb.out ccc.out. В этом случае используйте juggling дескриптора файла или ksh/zsh.

  • 4
    Это кажется единственным ответом, который позволяет сохранить потоки stdout / stderr как есть (например, не объединяя их). Милая!
9

При использовании bash:

# Redirect standard out and standard error separately
% cmd >stdout-redirect 2>stderr-redirect

# Redirect standard error and out together
% cmd >stdout-redirect 2>&1

# Merge standard error with standard out and pipe
% cmd 2>&1 |cmd2

Кредит (не отвечающий с моей головы) идет здесь: http://www.cygwin.com/ml/cygwin/2003-06/msg00772.html

1

В моем случае команда script выполняла команду, перенаправляя как stdout, так и stderr в файл, например:

cmd > log 2>&1

Мне нужно было его обновить, чтобы при сбое выполнить некоторые действия на основе сообщений об ошибках. Я мог бы, конечно, удалить dup 2>&1 и захватить stderr из script, но затем сообщения об ошибках не попадут в файл журнала для справки. Хотя принятый ответ от @lhunath должен делать то же самое, он перенаправляет stdout и stderr в разные файлы, чего я не хочу, но это помогло мне найти точное решение, которое мне нужно:

(cmd 2> >(tee /dev/stderr)) > log

С приведенным выше, журнал будет иметь копию как stdout, так и stderr, и я могу записать stderr из моего script, не беспокоясь о stdout.

0

Для KornShell (ksh), где подстановка процесса недоступна, будет работать следующее:

# create a combined(stdin and stdout) collector
exec 3 <> combined.log

# stream stderr instead of stdout to tee, while draining all stdout to the collector
./aaa.sh 2>&1 1>&3 | tee -a stderr.log 1>&3

# cleanup collector
exec 3>&-

Настоящий трюк здесь - это последовательность 2>&1 1>&3, которая в нашем случае перенаправляет stderr в stdout и перенаправляет дескриптор stdout в дескриптор 3. На этом этапе stderr и stdout еще не объединены.

По сути, stderr (as stdin) передается в tee, где он регистрируется в stderr.log, а также перенаправляется на дескриптор 3.

И дескриптор 3 записывает его в combined.log все время. Таким образом, combined.log содержит как stdout, так и stderr.

Сообщество Overcoder
Наверх
Меню