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

544

Я знаю, как использовать teeдля записи выходных данных ( STDOUT) aaa.shв bbb.out, при этом отображая его в терминале:

./aaa.sh | tee bbb.out

Как бы мне теперь записать STDERRфайл с именем ccc.out, пока он все еще отображается?

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

Ответы:

786

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

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

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

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, мы хотим снова перенаправить teeSTDOUT на наш STDERR. Затем мы используем перенаправление файлов для перенаправления commandSTDERR на вход FIFO (tee STDIN).

Смотрите http://mywiki.wooledge.org/BashGuide/InputAndOutput

Замена процесса - это одна из тех приятных вещей, которые вы получаете в качестве бонуса выбора в bashкачестве оболочки (в отличие от shPOSIX или 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"
lhunath
источник
5
Я попробовал это: $ echo "HANG" > >(tee stdout.log) 2> >(tee stderr.log >&2)который работает, но ждет ввода. Есть ли простая причина, почему это происходит?
Джастин
1
@SillyFreak Я не понимаю, что ты хочешь делать или в чем проблема. эхо-тест; Выход не производит никакого вывода на стандартный вывод, поэтому ошибка останется пустой.
июня
1
спасибо за этот комментарий; Я выяснил, в чем заключалась моя логическая ошибка: при вызове в качестве интерактивной оболочки bash печатает командную строку и выводит сообщение о выходе в stderr. Однако если stderr перенаправлен, bash по умолчанию запускается как неинтерактивный; сравнить /bin/bash 2> errи/bin/bash -i 2> err
Silly Freak
15
А для тех, кто «видит, значит верить», быстрый тест:(echo "Test Out";>&2 echo "Test Err") > >(tee stdout.log) 2> >(tee stderr.log >&2)
Мэтью Уилкоксон
2
Ничего себе . Хороший ответ: «Давайте разбить его и объяснить» +1
jalanb
674

почему не просто

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

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

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

./aaa.sh |& tee -a log
user542833
источник
85
Это отлично работает, если вы хотите, чтобы и stdout (канал 1), и stderr (канал 2) были зарегистрированы в одном и том же файле (один файл, содержащий смесь как stdout, так и sterr). Другое, более сложное решение позволяет разделить stdout и stderr на 2 разных файла (stdout.log и stderr.log соответственно). Иногда это важно, иногда нет.
Тайлер Рик
19
Другие решения намного сложнее, чем это необходимо во многих случаях. Этот работает отлично для меня.
dkamins
13
Проблема этого метода заключается в том, что вы теряете код выхода / состояния из процесса aaa.sh, что может быть важно (например, при использовании в make-файле). У вас нет этой проблемы с принятым ответом.
Стефаан
9
если вы не возражаете против слияния stdout / stderr, то ./aaa.sh |& tee aaa.logработает (в bash).
JFS
5
@ Stefan Я полагаю, что вы можете сохранить статус выхода, если вы добавите цепочку команд с set -o pipefailпоследующим ;или &&если я не ошибаюсь.
Дэвид
59

Это может быть полезно для людей, которые находят это через 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}
Энтони
источник
6
Нет, и я думаю, что exec может использовать некоторые объяснения. exec >означает, переместить цель дескриптора файла в определенное место назначения. По умолчанию установлено значение 1, поэтому exec > /dev/nullтеперь в этом сеансе вывод stdout в / dev / null перемещается. Текущие файловые дескрипторы для этого сеанса можно увидеть, выполнив ls -l /dev/fd/. Попробуй это! Затем посмотрим, что произойдет, когда вы выполните команду « exec 2>/tmp/stderr.log.Дополнительно», exec 3>&1значит, создайте новый файловый дескриптор с номером 3 и перенаправьте его на цель файлового дескриптора 1. В этом примере целью был экран, когда была выполнена команда.
барабанный огонь
Пример показа stdout и stderr как на экране, так и в отдельных файлах - это круто !!! Большое спасибо!
Марчелло де Сейлс
21

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

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

РЕДАКТИРОВАТЬ : Чтобы вывести на экран и stderr, и stdout, а также сохранить их в файл, вы можете использовать перенаправление ввода / вывода bash :

#!/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 шел к своей консоли в дополнение к файлу, хотя это явно не было указано.
Чарльз Даффи
2
Я должен был быть более ясным, я действительно хотел, чтобы stderr на экран тоже. Мне все еще нравилось решение Джоша Келли, но я нашел решение lhunath, которое больше соответствует моим потребностям. Спасибо, парни!
jparanich
18

Другими словами, вы хотите направить 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, таких как dash, вы можете использовать подстановку процессов :

./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. В этом случае вместо этого используйте жонглирование дескриптора файла или ksh / zsh.

Жиль "ТАК - перестань быть злым"
источник
4
Это кажется единственным ответом, который позволяет сохранить потоки stdout / stderr как есть (например, не объединяя их). Сладкий!
user1338062
Самый простой подход sh, полезный для заданий cron, где замена процесса недоступна.
Роджер Дуек
13

Если вы используете 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

ChristopheD
источник
5

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

cmd > log 2>&1

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

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

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

haridsv
источник
4

Следующее будет работать для 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.

Шува
источник
Острота! Жаль, что это недооценивают, потому что это хороший вариант. У меня есть KSH, кстати :)
runlevel0
2

Если вы используете zsh , вы можете использовать несколько перенаправлений, поэтому вам даже не нужно tee:

./cmd 1>&1 2>&2 1>out_file 2>err_file

Здесь вы просто перенаправляете каждый поток на себя и целевой файл.


Полный пример

% (echo "out"; echo "err">/dev/stderr) 1>&1 2>&2 1>/tmp/out_file 2>/tmp/err_file
out
err
% cat /tmp/out_file
out
% cat /tmp/err_file
err

Обратите внимание, что для этого требуется установить MULTIOSпараметр (который используется по умолчанию).

MULTIOS

Выполнять неявные tees или cats при попытке нескольких перенаправлений (см. Перенаправление ).

Арминий
источник