Как мне написать 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. Затем мы используем перенаправление файлов для перенаправления commandSTDERR на вход FIFO (teeSTDIN).

Смотрите 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

Неявно выполнять tees или cats при попытке нескольких перенаправлений (см. Перенаправление).

Как и принятый ответ, хорошо объясненный лхунатхом, вы можете использовать

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

См .: https://www.linuxquestions.org/questions/linux-kernel-70/how-to-redirect-compilation-build-error-to-a-file-831099/

Другие вопросы по тегам