Linux

Как записать stderr в файл при использовании «tee» с пайпом

Я знаю, как использовать «tee» для записи вывода (STDOUT) aaa.sh в bbb.out, при этом отображая его в терминале:

./aaa.sh | tee bbb.out

 Как бы мне теперь также записать STDERR в файл с именем ccc.out, сохранив при этом его отображение?

 

Ответ 1

Я предполагаю, что вы хотите по-прежнему видеть STDERR и STDOUT в терминале. Вы можете воспользоваться решением, которое выводит в ваш файл журнала, но это небезопасно и неудобно. Обратите внимание, как вам нужно держать exra FD и выполнять очистку, завершая его, и технически вы должны делать это в ловушке '...'. EXIT. Есть лучший способ сделать это, и вы его уже обнаружили: tee. Только вместо того, чтобы использовать это решение только для stdout, заведите «tee» для stdout и для stderr. Как этого добиться? Заменой процессов и перенаправлением файлов:

command  >  >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)

 Объяснение:

> >(..)

«>(...)» (подстановка процесса) создает FIFO и позволяет «tee» прослушивать его. Затем она использует > (перенаправление файлов), чтобы перенаправить STDOUT команды в FIFO, который прослушивает ваш первый «tee». То же самое для второго:

2> >(tee -a stderr.log >&2)

 Мы снова используем подстановку процессов, чтобы создать процесс «tee», который читает с STDIN и сбрасывает данные в stderr.log. «tee» выводит свои входные данные обратно на STDOUT, но поскольку ее входные данные – это наш STDERR, нам надо снова перенаправить STDOUT «tee» на наш STDERR. Затем мы используем перенаправление файлов, чтобы перенаправить STDERR команды на вход FIFO (STDIN команды «tee»).

Подстановка процессов это одна из тех замечательных вещей, которые вы получаете в качестве бонуса при выборе 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"

 

Ответ 2

Это может быть полезно для людей, которые ищут ответы через Google. Просто раскомментируйте пример, который вы хотите опробовать. Конечно, необходимо переименовывать выходные файлы.

#!/bin/bash

STATUSFILE=x.out

LOGFILE=x.log

### Весь вывод на экран

### Ничего не делать, это значение по умолчанию

### Весь вывод в один файл, на экран не выводить

#exec > ${LOGFILE} 2>&1

### Весь вывод в один файл и весь вывод на экран

#exec > >(tee ${LOGFILE}) 2>&1

### Весь вывод в один файл, STDOUT на экран

#exec > >(tee -a ${LOGFILE}) 2> >(tee -a ${LOGFILE} >/dev/null)

### Весь вывод в один файл, STDERR на экран

### Обратите внимание, что вам нужны обе эти строки, чтобы это работало

#exec 3>&1

#exec > >(tee -a ${LOGFILE} >/dev/null) 2> >(tee -a ${LOGFILE} >&3)

### STDOUT в STATUSFILE, stderr в LOGFILE, на экран ничего не выводится.

#exec > ${STATUSFILE} 2>${LOGFILE}

### STDOUT в STATUSFILE, stderr в LOGFILE и весь вывод на экран

#exec > >(tee ${STATUSFILE}) 2> >(tee ${LOGFILE} >&2)

### STDOUT в STATUSFILE и на экран, STDERR в LOGFILE

#exec > >(tee ${STATUSFILE}) 2>${LOGFILE}

### STDOUT в STATUSFILE, STDERR в LOGFILE и на экран

#exec > ${STATUSFILE} 2> >(tee ${LOGFILE} >&2)

echo "Для теста"

ls -l sdgshgswogswghthb_this_file_will_not_exist_so_we_get_output_to_stderr_aronkjegralhfaff

ls -l ${0}

 

Ответ 3

Чтобы перенаправить stderr в файл, вывести stdout на экран, а также сохранить stdout в файл:

./aaa.sh 2>ccc.out | tee ./bbb.out

Чтобы вывести на экран и в stderr, и в stdout, а также сохранить их в файл, вы можете использовать перенаправление ввода-вывода bash:

#!/bin/bash

# Создайте новый файловый дескриптор 4, указывающий на файл.

# который будет принимать stderr.

exec 4<>ccc.out

# Также выведите содержимое этого файла на экран.

tail -f ccc.out &

# Выполните команду; tee stdout как обычно, и отправьте stderr

# на наш файловый дескриптор 4.

./aaa.sh 2>&4 | tee bbb.out

# Очистка: Закройте файловый дескриптор 4 и завершите tail -f.

exec 4>&-

kill %1

 

Ответ 4

Если вы хотите направить 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

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

 

Ответ 5

Вы можете использовать следующее:

command > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)

Остерегайтесь, если вы используете bash, у вас могут возникнуть проблемы.

Первый тест:

(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 - # Для сохранения установленной опции

  ! && set -o pipefail; # Или используйте вторую часть напрямую

  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]}" # Сохраните первый ответ, второй - 0, возможно, код состояния tee.

  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" # или что-то еще

  fi

}

 

На выходе:

user@computer:~$ test_outerr

$ outerr

err

out

===

# содержимое stdout.log:

<===

out

===>

# содержимое stderr.log:

<===

err

===>

Fail 11 > 0

 

Схожие статьи

Linux

Как завершить дочерний процесс по истечении заданного времени ожидания в Bash

Linux

Gentoo GNU/Linux — установка и настройка. Пошаговая инструкция

Linux

Как найти расположение MySQL my.cnf

Linux

Поиск файла по содержимому в Linux. Поиск текста в файлах Linux

×