Как мне написать stderr в файл при использовании "тройника" с каналом?
Я умею пользоваться tee
написать вывод (STDOUT
) из aaa.sh
в bbb.out
пока все еще отображается в терминале:
./aaa.sh | tee bbb.out
Как бы я сейчас тоже написал STDERR
в файл с именем ccc.out
пока он еще отображается?
12 ответов
Я предполагаю, что вы все еще хотите видеть 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, мы хотим перенаправить tee
снова на наш STDERR. Затем мы используем перенаправление файлов для перенаправления command
STDERR на вход FIFO (tee
STDIN).
Смотрите http://mywiki.wooledge.org/BashGuide/InputAndOutput
Замена процесса - одна из тех действительно приятных вещей, которые вы получаете в качестве бонуса выбора. bash
как ваша оболочка, в отличие от sh
(POSIX или Борн).
В 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"
Почему не просто
./aaa.sh 2>&1 | tee -a log
Это просто перенаправляет stderr
в stdout
, так что эхо как для входа, так и для экрана. Может быть, я что-то упустил, потому что некоторые другие решения кажутся действительно сложными.
Примечание: начиная с bash версии 4, вы можете использовать |&
как сокращение для 2>&1 |
:
./aaa.sh |& tee -a log
Это может быть полезно для людей, которые находят это через 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}
Чтобы перенаправить 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
Другими словами, вы хотите направить стандартный вывод в один фильтр (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.
Если вы используете 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
Если вы используете 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
Неявно выполнять
tee
s илиcat
s при попытке нескольких перенаправлений (см. Перенаправление).
Как и принятый ответ, хорошо объясненный лхунатхом, вы можете использовать
command > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)
Остерегайтесь, если вы используете bash, у вас могут возникнуть проблемы.
Позвольте мне взять Matthew Wilcoxson.
А для тех, кто "видит - значит верит", быстрый тест:
(echo "Test Out";>&2 echo "Test Err") > >(tee stdout.log) 2> >(tee stderr.log >&2)
Лично при попытке получить такой результат:
user@computer:~$ (echo "Test Out";>&2 echo "Test Err") > >(tee stdout.log) 2> >(tee stderr.log >&2)
user@computer:~$ Test Out
Test Err
Оба сообщения не появляются на одном уровне. Почему
Test Out
вроде поставить вроде бы это моя предыдущая команда?
Подсказка находится в пустой строке, позвольте мне подумать, что процесс не завершен, и когда я нажимаю
Enter
это исправить.
Когда я проверяю содержимое файлов, все в порядке, перенаправление работает.
Давай возьмем еще один тест.
function outerr() {
echo "out" # stdout
echo >&2 "err" # stderr
}
user@computer:~$ outerr
out
err
user@computer:~$ outerr >/dev/null
err
user@computer:~$ outerr 2>/dev/null
out
Пробуем еще раз перенаправление, но уже с этой функцией.
function test_redirect() {
fout="stdout.log"
ferr="stderr.log"
echo "$ outerr"
(outerr) > >(tee "$fout") 2> >(tee "$ferr" >&2)
echo "# $fout content :"
cat "$fout"
echo "# $ferr content :"
cat "$ferr"
}
Лично у меня такой результат:
user@computer:~$ test_redirect
$ outerr
# stdout.log content :
out
out
err
# stderr.log content :
err
user@computer:~$
Нет запроса в пустой строке, но я не вижу нормального вывода, содержимое stdout.log кажется неправильным, только stderr.log кажется в порядке. Если я его перезапущу, вывод может быть другим...
Итак, почему?
Потому что, как объясняется здесь:
Помните, что в bash эта команда возвращается, как только [первая команда] завершается, даже если команды tee все еще выполняются (ksh и zsh действительно ждут подпроцессов)
Итак, если вы используете bash, предпочитайте использовать лучший пример, приведенный в этом другом ответе:
{ { outerr | tee "$fout"; } 2>&1 1>&3 | tee "$ferr"; } 3>&1 1>&2
Это исправит предыдущие проблемы.
Теперь возникает вопрос, как получить код статуса выхода?$?
не работает.
Я не нашел лучшего решения, чем включить pipefail с
set -o pipefail
(set +o pipefail
выключить) и использовать
${PIPESTATUS[0]}
как это
function outerr() {
echo "out"
echo >&2 "err"
return 11
}
function test_outerr() {
local - # To preserve set option
! [[ -o pipefail ]] && set -o pipefail; # Or use second part directly
local fout="stdout.log"
local ferr="stderr.log"
echo "$ outerr"
{ { outerr | tee "$fout"; } 2>&1 1>&3 | tee "$ferr"; } 3>&1 1>&2
# First save the status or it will be lost
local status="${PIPESTATUS[0]}" # Save first, the second is 0, perhaps tee status code.
echo "==="
echo "# $fout content :"
echo "<==="
cat "$fout"
echo "===>"
echo "# $ferr content :"
echo "<==="
cat "$ferr"
echo "===>"
if (( status > 0 )); then
echo "Fail $status > 0"
return "$status" # or whatever
fi
}
user@computer:~$ test_outerr
$ outerr
err
out
===
# stdout.log content :
<===
out
===>
# stderr.log content :
<===
err
===>
Fail 11 > 0
Следующее будет работать для 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
(как stdin
) передается tee
куда он входит stderr.log
а также перенаправляет на дескриптор 3.
И дескриптор 3
регистрирует это combined.log
все время. Итак combined.log
содержит как stdout
а также stderr
,
В моем случае скрипт выполнял команду, перенаправляя как stdout, так и stderr в файл, что-то вроде:
cmd > log 2>&1
Мне нужно было обновить его так, чтобы в случае сбоя предпринять некоторые действия на основе сообщений об ошибках. Я мог бы, конечно, удалить дуп 2>&1
и захватить stderr из скрипта, но тогда сообщения об ошибках не попадут в файл журнала для справки. Хотя принятый ответ от @lhunath должен делать то же самое, он перенаправляет stdout
а также stderr
к другим файлам, что не то, что я хочу, но это помогло мне найти точное решение, которое мне нужно:
(cmd 2> >(tee /dev/stderr)) > log
С учетом вышесказанного, журнал будет иметь копию обоих stdout
а также stderr
и я могу захватить stderr
из моего сценария, не беспокоясь о stdout
,
Спасибо @lhunath за ответ в posix. Вот более сложная ситуация, которая мне нужна в posix с правильным исправлением:
# Start script main() function
# - We redirect stdout to file_out AND terminal
# - We redirect stderr to file_err, file_out AND terminal
# - Terminal and file_out have both stdout and stderr, while file_err only holds stderr
main() {
# my main function
}
log_path="/my_temp_dir"
pfout_fifo="${log_path:-/tmp}/pfout_fifo.$$"
pferr_fifo="${log_path:-/tmp}/pferr_fifo.$$"
mkfifo "$pfout_fifo" "$pferr_fifo"
trap 'rm "$pfout_fifo" "$pferr_fifo"' EXIT
tee -a "file_out" < "$pfout_fifo" &
tee -a "file_err" < "$pferr_fifo" >>"$pfout_fifo" &
main "$@" >"$pfout_fifo" 2>"$pferr_fifo"; exit
Вы можете использовать:
bash:$ gcc temp.c &> error.log
csh:% gcc temp.c | & tee error.log