Як записати stderr у файл під час використання "tee" з трубою?


541

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

./aaa.sh | tee bbb.out

Як би я тепер також писав STDERRу файл з назвою ccc.out, але все ще мав його відображати?


2
Для уточнення - чи хочете ви, щоб stderr перейшов на екран, а також на файл?
— Чарльз Даффі

Я це зробив, я відредагую свій пост, щоб уточнити це. Я вірю, що рішення Луната буде достатньо. Дякуємо за допомогу всім!
— jparanich

Відповіді:


784

Я припускаю, що ви хочете все ще бачити STDERR і STDOUT на терміналі. Ви можете попросити відповіді Джоша Келлі, але я знаходжу на tailзаднім плані, що виводить ваш файл журналу дуже хакерським і незграбним. Зверніть увагу, як вам потрібно тримати FD exra та робити очищення після цього, вбиваючи його, а технічно слід робити це в a 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, ми хочемо знову перенаправити teeSTDOUT на наш STDERR. Тоді ми використовуємо перенаправлення файлів, щоб перенаправити commandSTDERR на вхід FIFO ( tee's 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"

5
Я спробував це: $ echo "HANG" > >(tee stdout.log) 2> >(tee stderr.log >&2)що працює, але чекає на введення. Чи є проста причина, чому це відбувається?
— Джастін

1
@SillyFreak Я не розумію, що ти хочеш робити або в чому проблема. ехо-тест; Вихід не дає жодного виводу на stdout, тому помилка залишиться порожньою.
— lhunath

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

672

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

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

Це просто переспрямовується stderrна stdout, тому трійник перегукується як на вхід, так і на екран. Можливо, мені чогось не вистачає, тому що деякі інші рішення здаються справді складними.

Примітка: з версії 4 bash ви можете використовувати |&як абревіатуру для 2>&1 |:

./aaa.sh |& tee -a log

85
Це добре працює, якщо ви хочете, щоб і stdout (канал 1), і stderr (канал 2) були записані в один і той же файл (один файл, що містить суміш як stdout, так і sterr). Інше, більш складне рішення дозволяє розділити stdout та stderr на два різних файли (stdout.log та stderr.log відповідно). Іноді це важливо, іноді це не так.
— Тайлер Рік

19
Інші рішення набагато складніші, ніж це потрібно в багатьох випадках. Цей відмінно працює для мене.
— dkamins

13
Проблема цього методу полягає в тому, що ви втрачаєте код виходу / статусу в процесі aaa.sh, що може бути важливим (наприклад, під час використання у makefile). У вас немає проблеми з прийнятою відповіддю.
— Стефаан

9
якщо ви не проти об’єднаного stdout / stderr, тоді це ./aaa.sh |& tee aaa.logпрацює (в баш).
— jfs

5
@Stefaan Я вважаю, що ви можете зберегти статус виходу, якщо додати командний ланцюжок, 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 як на екрані, так і в окремих файлах - приголомшливий !!! Дуже дякую!
— Marcello de Sales

21

Щоб перенаправити stderr у файл, виведіть stdout на екран, а також збережіть stdout у файл:

./aaa.sh 2> ccc.out | трій ./bbb.out

EDIT : Щоб відобразити на екрані і 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
Мені повинно було бути зрозуміліше, я теж хотів жорсткішого до екрану. Я все ще насолоджувався рішенням Джоша Келлі, але знаходжу люнатів, щоб більше відповідати моїм потребам. Дякую, хлопці!
— 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, таких як тире, ви можете використовувати заміну процесу :

./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


5

У моєму випадку сценарій виконував команду під час перенаправлення як stdout, так і stderr у файл, наприклад:

cmd > log 2>&1

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

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

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


4

Наступне буде працювати для KornShell (кш), де заміна процесу недоступна,

# 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, btw :)
— 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 під час спроби декількох переадресацій (див. Перенаправлення ).

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