Як захопити замовлені STDOUT / STDERR та додати часові позначки / префікси?


25

Я дослідив майже всі наявні подібні питання , безрезультатно.

Докладно опишу проблему:

Я запускаю кілька без нагляду сценаріїв, і вони можуть створювати стандартні вихідні та стандартні рядки помилок, я хочу захопити їх у їх точному порядку, як показано емулятором терміналу, а потім додати до них префікси типу "STDERR:" та "STDOUT:".

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

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

Хтось зміг зробити щось подібне? Або просто неможливо? Я думаю, що якщо емулятор терміналу може це зробити, то це не так - можливо, створивши невелику програму C, що обробляє PTY (и) інакше?

В ідеалі я б використовував асинхронний ввід, щоб прочитати ці 2 потоки (STDOUT і STDERR), а потім повторно роздрукувати їх друге мої потреби, але порядок введення є вирішальним!

ПРИМІТКА. Я знаю, що посилюється, але це не працює для мене зі сценаріями Bash і не може бути легко відредагований, щоб додати префікс (оскільки він в основному обшиває багато системних дзвінків).

Оновлення: додано нижче двох груп

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

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

Я буду радий оновити це питання, якщо хтось може спростувати вищезазначене.


1
Це не те, чого ти хочеш? stackoverflow.com/questions/21564/…
slm

@slm, ймовірно, ні, оскільки OP потрібно додавати різні рядки до різних потоків.
петерф

Чи можете ви поділитися, чому замовлення настільки важливе? Можливо, може бути якийсь інший спосіб вирішити вашу проблему ...
peterph

@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, мабуть, є єдиним способом досягти бажаного результату (і навіть тоді у вас можуть виникнути проблеми, коли щось стане багатопоточним у багатопроцесорній системі).

Що стосується копроцесів, обов'язково читайте відмінну відповідь Стефана в « Як ви використовуєте командний копрок у Bash? для глибокого розуміння.


Дякую @peterph за вашу відповідь, проте я шукаю спеціально способи зберегти замовлення. Примітка: Я думаю, що ваш перекладач повинен бути збитим через підстановку, яку ви використовуєте (я отримую ./test1.sh: 3: ./test1.sh: Syntax error: "(" unexpectedкопію / вставлення вашого сценарію)
Deim0s

Дуже ймовірно, що я зіткнувся bashз цим /bin/sh(не впевнений, чому я його там).
петерф

Я трохи оновив питання про те, де може відбуватися змішування потоку.
петерф

1
eval $@є досить баггі. Використовуйте, "$@"якщо ви хочете запускати свої аргументи як точний командний рядок - додавання шару evalінтерпретації включає в себе важко передбачувані (і потенційно шкідливі, якщо ви передаєте назви файлів або інший вміст, яким ви не керуєте як аргументи) поведінка і не в змозі цитувати ще більше (розбиває назви з пробілами на кілька слів, розширює глобуси, навіть якщо вони раніше були цитовані як буквальні тощо).
Чарльз Даффі

1
Крім того, у сучасному файлі, що має достатню кількість сучасних файлів, не потрібно eval закривати дескриптори файлів, названі змінною. exec {SEDo[1]}>&-буде працювати як є (так, відсутність a $до того, як {було навмисно).
Чарльз Даффі

5

Спосіб №1. Використання дескрипторів файлів та awk

Що щодо чогось подібного, використовуючи рішення з цього питання запитань і відповідей під назвою: Чи є утиліта Unix для додавання часових позначок до рядків тексту? і це SO Q&A під назвою: передача 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

Формат виводу можна керувати для частини часової позначки, але не вище цього. Але це аналогічно тому, що ви шукаєте, тому може відповідати рахунку.


1
на жаль, це також не вирішує проблему можливої ​​заміни рядків.
петерф

точно. Я думаю, що відповідь на це моє питання "неможлива". Подія, з якою stderredви не можете легко визначити межі рядків (намагатися так було б шалено). Я хотів побачити, чи може хтось допомогти мені з цією проблемою, але, мабуть, всі хочуть відмовитись від єдиного обмеження ( розпорядження ), яке є основою для запитання
Deim0s

Етап 2 кроку 1 вимагає іншого {спереду для належної роботи.
Остін Гансон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.