Сохранить stdout, stderr и stdout + stderr синхронно - PullRequest
9 голосов
/ 21 декабря 2010

В целях тестирования я хотел бы сохранить stdout и stderr отдельно для проверки последующим кодом. Например, тестовый запуск с ошибочным вводом должен привести к выводу в stderr, но ничего не выводить на stdout, в то время как тестовый запуск с правильным вводом должен привести к выводу на stdout, но ничего к stderr. Сохранение должно быть синхронным, чтобы избежать гонок в тестах (поэтому я не могу использовать подстановка процесса ).

Чтобы иметь возможность отладить тест после факта, я также должен видеть stdout и stderr в той последовательности, в которой они были выведены. Поэтому мне нужно либо сохранить их оба в один и тот же файл / переменную / что угодно, либо отправить их на терминал одновременно с сохранением их отдельно.

Чтобы проверить, какая ошибка произошла, мне также нужен код завершения команды.

Из соображений эффективности и точности я, конечно, не могу выполнить каждый тест дважды.

Возможно ли, например, перенаправить stdout в stdout.log, stderr в stderr.log, и оба в output.log одной и той же командой? Или использовать синхронную tee команду отдельно для stdout и stderr? Или сохранить копию stdout и stderr в отдельных переменных?

Обновление : Похоже, решение Тима почти работает (изменено для вывода на терминал вместо записи в all.log):

$ set -o pipefail
$ {
    {
        echo foo | tee stdout.log 2>&3 3>&-
    } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&-
} 3>&2 4>&1
foo
$ cat stdout.log
foo
$ cat stderr.log
$ {
    {
        echo foo >&2 | tee stdout.log 2>&3 3>&-
    } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&-
} 3>&2 4>&1
foo
$ cat stdout.log
$ cat stderr.log
foo
$ bar=$({
    {
        echo foo | tee stdout.log 2>&3 3>&-
    } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&-
} 3>&2 4>&1)
$ echo "$bar"
foo
$ cat stdout.log
foo
$ cat stderr.log
$ bar=$({
    {
        echo foo >&2 | tee stdout.log 2>&3 3>&-
    } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&-
} 3>&2 4>&1)
$ cat stdout.log
$ cat stderr.log
foo
$ echo "$bar"
foo

Кажется, это работает, за исключением последней итерации, где значение bar установлено на содержимое stderr. Любые предложения для всего этого, чтобы работать?

Ответы [ 3 ]

11 голосов
/ 21 декабря 2010

Пожалуйста, смотрите BashFAQ / 106 . Нелегко взять свой пирог и съесть его тоже.

С этой страницы:

Но некоторые люди не примут ни потерю разделения между stdout и stderr, ни десинхронизацию строк. Они пуристы, и поэтому они просят самую сложную форму из всех - я хочу объединить stdout и stderr в один файл, НО я также хочу, чтобы они сохранили свои оригинальные, отдельные пункты назначения.

Чтобы сделать это, сначала нужно сделать несколько заметок:

  • Если будет два отдельных потока stdout и stderr, то какой-то процесс должен написать каждый из них.
  • Невозможно написать процесс в сценарии оболочки, который считывает данные с двух отдельных FD, когда один из них имеет входные данные, поскольку оболочка не имеет интерфейса poll (2) или select (2).
  • Поэтому нам понадобятся два отдельных процесса записи.
  • Единственный способ не допустить уничтожения вывода двух разных авторов - это убедиться, что они оба открывают свои выходные данные в режиме добавления. FD, открываемый в режиме добавления, обладает гарантированным свойством, что каждый раз, когда в него записываются данные, он сначала переходит к концу.

Итак:

# Bash
> mylog
exec > >(tee -a mylog) 2> >(tee -a mylog >&2)

echo A >&2
cat file
echo B >&2

Это гарантирует, что файл журнала правильный. Это не гарантирует, что авторы завершат работу до следующего приглашения оболочки:

~$ ./foo
A
hi mom
B
~$ cat mylog
A
hi mom
B
~$ ./foo
A
hi mom
~$ B

Также см. BashFAQ / 002 и BashFAQ / 047 .

2 голосов
/ 21 декабря 2010

Это звучит как задача для нескольких пользователей.

Чтобы проверить синхронный вывод или запись на диск без замены процесса, вы можете поэкспериментировать с приемами перенаправления оболочки Bash и tee (1), как показано ниже:

echos() { echo "hello on stdout"; echo "hello on stderr" 1>&2; return 0; }

   { 
  { 
     echos 3>&- | 
        tee stdout.log 2>&3 3>&- 
  } 2>&1 >&4 4>&- | 
        tee  stderr.log 2>&3 3>&- 
} 3>&2 4>&1 2>&1 | tee /dev/stderr > all.log

open -e stdout.log stderr.log all.log   # Mac OS X

Для получения дополнительной информации см .: http://codesnippets.joyent.com/posts/show/8769

Чтобы получить код выхода команды, вы можете проверить pipefail или PIPESTATUS соответственно (в дополнение к последнему выходукодовая переменная "$?"):

help set | grep -i pipefail
man bash | less -p pipefail
man bash | less -p PIPESTATUS
1 голос
/ 11 мая 2011

Для последней итерации, где значение bar установлено на содержимое stderr, попробуйте:

# restore original stdout & stderr at the end by adding: 1>&2 2>&1
bar="$(
{
   {
      echo foo >&2 | tee stdout.log 2>&3 3>&-
   } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&-
} 3>&2 4>&1 1>&2 2>&1
)"

echo "$bar"
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...