Як записати 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.