Хронологически захват STDOUT и STDERR - PullRequest
0 голосов
/ 21 мая 2018

Это очень хорошо может подпадать под принцип KISS (будь проще), но мне все еще любопытно и я хочу узнать, почему я не получил ожидаемых результатов.Итак, поехали ...

У меня есть сценарий оболочки для захвата STDOUT и STDERR без нарушения исходных файловых дескрипторов.Это в надежде сохранить исходный порядок вывода (см. test.pl ниже), как видно пользователю на терминале.

К сожалению, я ограничен использованием sh вместо bash (но я приветствую примеры), так как я вызываю это из другого набора, и я могу захотеть использовать его в cron в будущем (я знаю, что cron имеет переменную окружения SHELL).

wrapper.sh содержит:

#!/bin/sh
stdout_and_stderr=$1
shift
command=$@

out="${TMPDIR:-/tmp}/out.$$"
err="${TMPDIR:-/tmp}/err.$$"
mkfifo ${out} ${err}
trap 'rm ${out} ${err}' EXIT
> ${stdout_and_stderr}
tee -a ${stdout_and_stderr} < ${out} &
tee -a ${stdout_and_stderr} < ${err} >&2 &
${command} >${out} 2>${err}

test.pl содержит:

#!/usr/bin/perl

print "1: stdout1\n";
print STDERR "2: stderr1\n";
print "3: stdout2\n";

В сценарии:

sh wrapper.sh /tmp/xxx perl test.pl

STDOUT содержит:

1: stdout1
3: stdout2

STDERR содержит:

2: stderr1

Пока все хорошо ...

/tmp/xxx содержит:

2: stderr1
1: stdout1
3: stdout2

Однако я ожидал, что /tmp/xxx будет содержать:

1: stdout1
2: stderr1
3: stdout2

Может кто-нибудь объяснить мне, почему STDOUT и STDERR не добавляют /tmp/xxx в порядке, который я ожидал?Я предполагаю, что фоновые процессы tee блокируют ресурс /tmp/xxx друг от друга, так как они имеют одно и то же «назначение».Как бы вы решили это?

related: Как записать stderr в файл при использовании "tee" с каналом?

Ответы [ 2 ]

0 голосов
/ 21 мая 2018

После немного большего поиска, вдохновленного @wallyk, я сделал следующую модификацию wrapper.sh:

#!/bin/sh
stdout_and_stderr=$1
shift
command=$@

out="${TMPDIR:-/tmp}/out.$$"
err="${TMPDIR:-/tmp}/err.$$"
mkfifo ${out} ${err}
trap 'rm ${out} ${err}' EXIT
> ${stdout_and_stderr}
tee -a ${stdout_and_stderr} < ${out} &
tee -a ${stdout_and_stderr} < ${err} >&2 &
script -q -F 2 ${command} >${out} 2>${err}

, которая теперь производит ожидаемое:

1: stdout1
2: stderr1
3: stdout2

Решение было префикс $command с script -q -F 2, что делает script вполне (-q), а затем принудительно немедленно очищает дескриптор файла 2 (STDOUT) (-F 2).

Я сейчасисследуя, чтобы определить, насколько это портативно.Я думаю, что -F pipe может быть Mac и FreeBSD, а -f или --flush могут быть другими дистрибутивами ...

related: Как сделать вывод любой буферизованной команды небуферизованным?

0 голосов
/ 21 мая 2018

Это особенность библиотеки времени выполнения C (и, вероятно, имитируется другими библиотеками времени выполнения), что stderr не буферизуется.Как только он записывается, stderr передает все свои символы на целевое устройство.

По умолчанию stdout имеет 512-байтовый буфер.

Буферизация для обоих stderrи stdout можно изменить с помощью вызовов setbuf или setvbuf.

На справочной странице Linux для stdout:

ПРИМЕЧАНИЯ: поток stderr небуферизован.Поток stdout буферизуется в строке, когда он указывает на терминал.Частичные строки не будут отображаться до тех пор, пока не будет вызвана функция fflush (3) или exit (3) или не будет напечатана новая строка.Это может привести к неожиданным результатам, особенно при отладке.Режим буферизации стандартных потоков (или любого другого потока) можно изменить с помощью вызовов setbuf (3) или setvbuf (3).Обратите внимание, что в случае, когда stdin связан с терминалом, в драйвере терминала также может быть входная буферизация, совершенно не связанная с буферизацией stdio.(Действительно, обычно терминальный ввод - это буферизованная строка в ядре.) Эта обработка ввода в ядре может быть изменена с помощью вызовов типа tcsetattr (3);см. также stty (1) и termios (3).

...