Показывать только stderr на экране, но записывать как stdout, так и stderr в файл

24

Как я могу использовать магию BASH для достижения этой цели?
Я хочу видеть только вывод stderr на экране,
но я хочу, чтобы и stdout, и stderr были записаны в файл.

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

Andreas
источник
2
Новое решение той же фундаментальной проблемы: как перехватить упорядоченный STDOUT / STDERR и добавить временные метки / префиксы?
Жиль "ТАК - перестань быть злым"

Ответы:

14

Даже без какого-либо перенаправления или только с чем >logfile 2>&1, вы не гарантированно увидите выходные данные в порядке генерации.

Для начала stdout из приложения будет буферизован строкой (в tty) или буферизован (в конвейер), но stderr не буферизован, поэтому отношения между порядком вывода нарушены, насколько это касается читателя. Последующие этапы в любом конвейере, который вы могли бы придумать, не получат детерминированно упорядоченный доступ к двум потокам (концептуально они происходят параллельно, и вы всегда подчиняетесь планировщику - если к тому времени, когда ваш читатель получит фрагмент, автор уже записано в обе трубы, вы не можете сказать, что было первым).

«[T] он приказывает им случиться» только действительно известно приложению. Упорядочение вывода через stdout / stderr - хорошо известная, возможно, классическая проблема.

JMB
источник
7

Когда вы используете конструкцию: 1>stdout.log 2>&1и stderr, и stdout перенаправляются в файл, потому что перенаправление stdout настроено до перенаправления stderr .

Если вы измените порядок, вы можете перенаправить stdout в файл, а затем скопировать stderr в stdout, чтобы вы могли передать его tee.

$ cat test
#!/bin/sh
echo OUT! >&1
echo ERR! >&2

$ ./test 2>&1 1>stdout.log | tee stderr.log
ERR!

$ cat stdout.log
OUT!

$ cat stderr.log
ERR!
mmoya
источник
Это не позволяет вам записывать два потока в один файл.
artistoex
1
@artistoex: Вы можете использовать один tee -aи тот же файл, чтобы использовать 1>для суммирования обоих выходных данных в одном файле. Что-то вроде:./test 2>&1 1>out+err.log | tee -a out+err.log
mmoya
Я понятия не имею, почему, но это не работает wget -O - www.google.deдля меня. (сравните с решением ниже)
artistoex
4
Использование tee -aне будет работать надежно, чтобы иметь тот же вывод, который был бы на консоли, если бы ничего не было перенаправлено. Вывод, который проходит через, teeможет быть немного задержан по сравнению с выводом, полученным непосредственно из команды, и поэтому может появиться позже в журнале.
Жиля SO- перестать быть злым »
Это не работает для меня, out + err.log пуст. И да, я хочу стандартный и стандартный вывод в одном файле
Andreas
7

Я смог сделать это, поместив следующую строку в верхней части сценария bash:

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

Это перенаправит stdout в file log( 1>>log), затем переместит stderr в file log( 2> >(tee -a log) и перенаправит его обратно в stderr ( >&2). Таким образом, я получаю один файл log, который показывает и stdout, и stderr по порядку, и stderr также отображается на экране как обычно.

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

bjg222
источник
Любой способ заставить это «модифицировать» строки STDERR, чтобы включить пользовательскую строку, чтобы вы могли легко найти ее?
IMTheNachoMan
1

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

{ { echo out; echo err 1>&2; } 2>&1 >&3 | tee /dev/tty; } >log 3>&1
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^   ^^^^^^^^^^^^    ^^^^^^^^^
  command produces output      stdout3                    log
  command produces error       stderr1   dup to terminal  log
Жиль "ТАК - перестань быть злым"
источник
Это также, не совсем у меня работает, сначала я получаю строки stdout, а затем все строки stderr в файле 'log'. пример: {{эхо; эхо err 1> & 2; эхо out2; эхо err2 1> & 2; } 2> & 1> & 3 | тройник / dev / tty; }> log 3> & 1
Andreas
1
@ Андреас: О, да, teeздесь тоже вводится задержка. Я не думаю, что есть чистое решение для оболочки. На самом деле мне интересно, есть ли решение, не модифицируя приложение каким-либо образом.
Жиль "ТАК - перестань быть злым"
1

Чтобы записать stderr на экран и записать ОБА stderr и stdout в файл - И получить строки для stderr и stdout в той же последовательности, в которой они были бы, если бы оба были записаны на экран:

Оказывается, это сложная проблема, особенно та часть, которая касается «той же последовательности», которую можно ожидать, если вы просто напишите их на экран. Проще говоря: запишите каждый в свой файл, сделайте некоторую магию фонового процесса, чтобы пометить каждую строку (в каждом файле) точным временем, когда была произведена строка, а затем: «tail --follow» файл stderr на экран , но чтобы увидеть оба «stderr» и «stdout» вместе - по порядку - отсортируйте два файла (с отметками точного времени на каждой строке) вместе.

Код:

# Set the location of output and the "first name" of the log file(s)
pth=$HOME
ffn=my_log_filename_with_no_extension
date >>$pth/$ffn.out
date >>$pth/$ffn.err
# Start background processes to handle 2 files, by rewriting each one line-by-line as each line is added, putting a label at front of line
tail -f $pth/$ffn.out | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|1|".$_' >>$pth/$ffn.out.txt &
tail -f $pth/$ffn.err | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|2|".$_' >>$pth/$ffn.err.txt &
sleep 1
# Remember the process id of each of 2 background processes
export idout=`ps -ef | grep "tail -f $pth/$ffn.out" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
export iderr=`ps -ef | grep "tail -f $pth/$ffn.err" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
# Run the command, sending stdout to one file, and stderr to a 2nd file
bash mycommand.sh 1>>$pth/$ffn.out 2>>$pth/$ffn.err
# Remember the exit code of the command
myexit=$?
# Kill the two background processes
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"idout"}/'
echo kill $idout
kill $idout
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"iderr"}/'
echo kill $iderr
kill $iderr
date
echo "Exit code: $myexit for '$listname', list item# '$ix', bookcode '$bookcode'"

Да, это кажется сложным, и это приводит к 4 выходным файлам (2 из которых вы можете удалить). Похоже, что эту проблему трудно решить, поэтому потребовалось несколько механизмов.

В конце, чтобы увидеть результаты ОБА stdout и stderr в последовательности, которую вы ожидаете, запустите это:

cat $pth/$ffn.out.txt $pth/$ffn.err.txt | sort

Единственная причина, по которой последовательность, по крайней мере, очень близка к тому, что было бы, если бы stdout и stderr просто были выведены на экран, заключается в следующем: каждая строка помечена меткой времени до миллисекунды.

Чтобы увидеть stderr на экране во время процесса, используйте это:

tail -f $pth/$ffn.out

Надеюсь, что это поможет кому-то, кто прибыл сюда намного позже, чем был задан первоначальный вопрос.

Дана Форсберг
источник
+1 за само усилие, у меня была похожая идея (временные метки обоих потоков через perl), но я изо всех сил пытался ее реализовать. Я выясню, работает ли он для меня (т. Е. Действительно ли он сохраняет порядок выходных данных, поскольку другие решения здесь немного меняют ситуацию из-за асинхронности между тегированием stderr и stdout)
Оливье Дюлак
0

Позвольте fбыть команда, которую вы хотели бы выполнить, то это

( exec 3>/tmp/log; f 2>&1 1>&3 |tee >(cat)>&3 )

должен дать вам то, что вы хотите. Например, wget -O - www.google.deбудет выглядеть так:

( exec 3>/tmp/googlelog; wget -O - www.google.de 2>&1 1>&3 |tee >(cat)>&3 )
artistoex
источник
1
Это почти работает для меня, но в файле журнала я сначала получаю все строки stdout, а затем все строки stderror. Я хочу, чтобы они чередовались в естественном порядке по мере их появления.
Андреас
0

Учитывая то, что я прочитал здесь, единственное решение, которое я вижу, - это добавлять каждую строку, STOUT или STERR, к временному интервалу / метке времени. date +% s будет в секундах, но это будет медленно, чтобы поддерживать порядок. Также журнал должен был бы быть как-то отсортирован. Больше работы, чем я способен.

усталый старый парень
источник
0

Я боролся с этим точным требованием, и в конце концов не смог найти простое решение. Вместо этого я закончил тем, что сделал следующее:

TMPFILE=/tmp/$$.log
myCommand > $TMPFILE 2>&1
[ $? != 0 ] && cat $TMPFILE
date >> myCommand.log
cat $TMPFILE >> myCommand.log
rm -f $TMPFILE

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

myCommand > myCommand.log 2>&1
[ $? != 0 ] && cat myCommand.log
Кит Помакис
источник
0

stderr для замены команд tee -aи добавление stdout в тот же файл:

./script.sh 2> >(tee -a outputfile) >>outputfile

и обратите внимание: также убедитесь, что в правильном порядке (но без stderr-show) есть команда 'unbuffer' из инструментов 'wait', которая имитирует tty и сохраняет порядок stdout / err, как это будет показано на терминале:

unbuffer ./script.sh > outputfile
Асаин Куйович
источник