Как захватить упорядоченный STDOUT / STDERR и добавить метки времени / префиксы?

25

Я изучил почти все доступные подобные вопросы , но безрезультатно.

Позвольте мне подробно описать проблему:

Я запускаю несколько автоматических сценариев, и они могут генерировать стандартный вывод и стандартные строки ошибок, я хочу записать их в точном порядке, отображаемом эмулятором терминала, а затем добавить к ним префикс, такой как «STDERR:» и «STDOUT:».

Я пытался использовать трубы и даже основанный на эполле подход к ним, но безрезультатно. Я думаю, что решение находится в pty использовании, хотя я не мастер в этом. Я также заглянул в исходный код VTE Gnome , но это не было очень продуктивным.

В идеале я бы использовал Go вместо Bash для этого, но я не смог. Похоже, что трубы автоматически запрещают поддерживать правильный порядок строк из-за буферизации.

Кто-нибудь смог сделать что-то подобное? Или это просто невозможно? Я думаю, что если эмулятор терминала может это сделать, то это не так - может быть, путем создания небольшой программы на C, которая по-разному обрабатывает PTY?

В идеале я хотел бы использовать асинхронный ввод для чтения этих двух потоков (STDOUT и STDERR), а затем перепечатывать их, как мне нужно, но порядок ввода имеет решающее значение!

ПРИМЕЧАНИЕ: я знаю о stderred, но он не работает для меня со сценариями Bash и не может быть легко отредактирован для добавления префикса (поскольку он в основном содержит множество системных вызовов).

Обновление: добавлено ниже двух гист

(случайные задержки в секунду могут быть добавлены в пример сценария, который я предоставил, чтобы доказать непротиворечивый результат)

Обновление: решение этого вопроса также решило бы этот другой вопрос , как отметил @Gilles. Однако я пришел к выводу, что невозможно делать то, что просили здесь и там. При использовании 2>&1оба потока корректно объединяются на уровне pty / pipe, но чтобы использовать потоки отдельно и в правильном порядке, действительно следует использовать подход stderred, который включает перехват системных вызовов и может рассматриваться как грязный во многих отношениях.

Я буду стремиться обновить этот вопрос, если кто-то может опровергнуть вышеизложенное.

Deim0s
источник
1
Разве это не то, что вы хотите? stackoverflow.com/questions/21564/…
slm
@slm, вероятно, нет, так как OP нужно добавлять разные строки в разные потоки.
Петер
Можете поделиться, почему заказ так важен? Может быть, может быть какой-то другой способ обойти вашу проблему ...
Петер
@peterph это обязательное условие, если у меня не может быть последовательного вывода, я бы лучше отправил его в / dev / null, чем прочитал и запутался в нем :) 2> & 1 сохраняет порядок, например, но не разрешает вид настроек, которые я задаю в этом вопросе
Deim0s

Ответы:

12

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

#!/bin/bash
exec 3>&1
coproc SEDo ( sed "s/^/STDOUT: /" >&3 )
exec 4>&2-
coproc SEDe ( sed "s/^/STDERR: /" >&4 )
eval $@ 2>&${SEDe[1]} 1>&${SEDo[1]}
eval exec "${SEDo[1]}>&-"
eval exec "${SEDe[1]}>&-"

Обратите внимание на несколько вещей:

  1. Это волшебное заклинание для многих людей (включая меня) - по причине (см. Связанный ответ ниже).

  2. Нет никакой гарантии, что он не поменяет местами пару строк - все зависит от планирования сопроцессов. На самом деле, почти гарантировано, что в какой-то момент времени это произойдет. Тем не менее, если придерживаться строго одинакового порядка, вы должны обрабатывать данные из обоих stderrи stdinв одном и том же процессе, иначе планировщик ядра может (и будет) испортить его.

    Если я правильно понимаю проблему, это означает, что вам нужно будет указать оболочке перенаправить оба потока на один процесс (что может быть сделано AFAIK). Проблема начинается, когда этот процесс начинает решать, что делать дальше: ему нужно будет опросить оба источника данных и в какой-то момент перейти в состояние, при котором он будет обрабатывать один поток, и данные поступят в оба потока до его завершения. И это именно то, где оно ломается. Это также означает, что упаковка выходных системных вызовов, как stderred, возможно, является единственным способом достижения желаемого результата (и даже в этом случае у вас могут возникнуть проблемы, когда что-то станет многопоточным в многопроцессорной системе).

Что касается сопроцессов, обязательно прочитайте превосходный ответ Стефана в разделе Как вы используете команду coproc в Bash? для глубокого понимания.

peterph
источник
Спасибо @peterph за ваш ответ, однако я специально ищу способы сохранить порядок. Примечание: я думаю, что ваш интерпретатор должен быть bash из-за подмены процесса, которую вы используете (я получаю ./test1.sh: 3: ./test1.sh: Syntax error: "(" unexpectedпутем копирования / вставки вашего скрипта)
Deim0s
Скорее всего, я столкнулся bashс этим /bin/sh(не уверен, почему он у меня там был).
Петер
Я немного обновил вопрос о том, где может происходить перепутывание потоков.
Петер
1
eval $@довольно глючит Используйте, "$@"если вы хотите, чтобы ваши аргументы были точной командной строкой - добавление слоя evalинтерпретации добавляет кучу трудно предсказуемых (и потенциально вредоносных, если вы передаете имена файлов или другой контент, который вы не контролируете как аргументы) поведения и неспособность кавычки еще больше (разбивает имена с пробелами на несколько слов, расширяет глобусы, даже если они ранее цитировались как буквальные и т. д.).
Чарльз Даффи
1
Кроме того, в bash достаточно современного, чтобы иметь сопроцессы, вам не нужно eval закрывать файловые дескрипторы, названные в переменной. exec {SEDo[1]}>&-будет работать как есть (да, отсутствие $до того {было преднамеренным).
Чарльз Даффи
5

Способ № 1. Использование файловых дескрипторов и awk

Как насчет этого, используя решения из этой SO Q & A под названием: Существует ли утилита Unix для добавления меток времени к строкам текста? и это SO Q & A под названием: pipe STDOUT и STDERR для двух разных процессов в сценарии оболочки? ,

Подход

Шаг 1, мы создаем 2 функции в Bash, которые при вызове будут выполнять отметку времени:

$ msgOut () {  awk '{ print strftime("STDOUT: %Y-%m-%d %H:%M:%S"), $0; fflush(); }'; }
$ msgErr () {  awk '{ print strftime("STDERR: %Y-%m-%d %H:%M:%S"), $0; fflush(); }'; }

Шаг 2: вы можете использовать вышеуказанные функции, например, чтобы получить желаемое сообщение:

$ { { { ...command/script... } 2>&3; } 2>&3 | msgErr; } 3>&1 1>&2 | msgOut

пример

Здесь я придумал пример, который будет писать aв STDOUT, спит в течение 10 секунд, а затем записывает вывод в STDERR. Когда мы помещаем эту последовательность команд в нашу конструкцию выше, мы получаем сообщения, как вы указали.

$ { { echo a; sleep 10; echo >&2 b; } 2>&3 | \
    msgErr; } 3>&1 1>&2 | msgOut
STDERR: 2014-09-26 09:22:12 a
STDOUT: 2014-09-26 09:22:22 b

Способ № 2. Использование аннотированного вывода

Существует инструмент под названием, annotate-outputкоторый является частью devscriptsпакета, который будет делать то, что вы хотите. Единственное ограничение заключается в том, что он должен запускать сценарии для вас.

пример

Если мы поместим наш пример последовательности команд в скрипт, который называется mycmds.bashтак:

$ cat mycmds.bash 
#!/bin/bash

echo a
sleep 10
echo >&2 b

Затем мы можем запустить его так:

$ annotate-output ./mycmds.bash 
09:48:00 I: Started ./mycmds.bash
09:48:00 O: a
09:48:10 E: b
09:48:10 I: Finished with exitcode 0

Формат вывода можно контролировать для части временной отметки, но не более того. Но это похоже на то, что вы ищете, так что это может соответствовать всем требованиям.

SLM
источник
1
к сожалению, это также не решает проблему возможного обмена некоторыми строками.
Петер
в точку. Я думаю, что ответ на мой вопрос "не возможен". Событие с stderredвами не может легко определить границы линий (попытка так была бы хакерской). Я хотел посмотреть, сможет ли кто-нибудь помочь мне с этой проблемой, но, очевидно, все хотят отказаться от единственного ограничения ( порядка ), которое является основой для вопроса
Deim0s
Шаг 2 метода 1 требует другого {на передней панели для правильной работы.
Остин Хэнсон