Показувати лише stderr на екрані, але записувати у файл і stdout, і stderr


24

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

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


Відповіді:


14

Навіть без перенаправлення чи нічого, але >logfile 2>&1ви не гарантовано побачите вихід у порядку покоління.

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

"[T] він замовляє, що вони відбудуться" відомий лише додатку. Впорядкування виводу через stdout / stderr - це добре відома - класична, можливо - проблема.


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!

Це не дозволяє записати два потоки в один і той же файл.
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може бути дещо затриманий порівняно з результатом, який надходить безпосередньо з команди, і так може з’явитися пізніше в журналі.
Жил 'ТАК - перестань бути злим'

Це не працює для мене, out + err.log порожній. І так, я хочу, щоб стридер і стандарт вийшли в одному файлі
Андреас

7

Я зміг досягти цього, розмістивши наступний рядок у верхній частині баш-сценарію:

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

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

Злочин полягає в тому, що він, здається, працює лише тоді, коли я додаю файл. Якщо я не додаю, виявляється, що два перенаправлення клобують один до одного, і я отримую лише той, який триває один з результатів.


Будь-який спосіб змусити це "змінити" рядки 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". приклад: {{відлуння; echo err 1> & 2; echo out2; echo err2 1> & 2; } 2> & 1> & 3 | tee / dev / tty; }> журнал 3> & 1
Андреас

1
@Andreas: О, да, teeтут також вводиться затримка. Я не думаю, що існує чиста оболонка. Насправді мені цікаво, чи є рішення, не змінюючи додаток якимось чином.
Жил "ТАК - перестань бути злим"

1

Щоб записати stderr на екран і записати BOTH stderr та stdout у файл - І рядки для stderr та stdout вийдуть у тій самій послідовності, що і в тому випадку, якби обидва були записані на екран:

Виявляється, це складна проблема, особливо частина про те, що мають "ту саму послідовність", яку б ви очікували, якби просто записали їх на екран. Простими словами: запишіть кожен у свій власний файл, виконайте деяку магію фонового процесу, щоб позначити кожен рядок (у кожному файлі) з точним часом створення лінії, а потім: "хвіст - наступний" файл stderr на екран , але щоб побачити BOTH "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 з яких можна видалити). Здається, це важко вирішити проблему, тому було потрібно декілька механізмів.

Зрештою, щоб побачити результати BOTH 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 )

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" від інструментів "очікувати", яка імітує tty та зберігає stdout / err для того, як це було б показано на терміналі:

unbuffer ./script.sh > outputfile
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.