
Вопрос или проблема
Я ищу способ визуально разделить stdout и stderr, чтобы они не смешивались и их было легко идентифицировать. В идеале, stdout и stderr должны отображаться в разных областях экрана, например, в разных столбцах. Например, вывод, который выглядел бы так:
~$ какая-то команда
какая-то полезная информация
ОШИБКА: ошибка
дополнительный вывод
ОШИБКА: произошло
еще одно сообщение
~$
вместо этого будет выглядеть примерно так:
~$ какая-то команда |какая-то полезная информация |дополнительный вывод | ОШИБКА: ошибкаеще одно сообщение | ОШИБКА: произошло
~$ |
Вы могли бы использовать вертикальное разделение в GNU screen:
#! /bin/bash -tmpdir=$(mktemp -d) || exittrap ‘rm -rf «$tmpdir»‘ EXIT INT TERM HUPFIFO=$tmpdir/FIFOmkfifo «$FIFO» || exitconf=$tmpdir/confcat > «$conf» << ‘EOF’ || exitsplit -vfocusscreen -t stderr sh -c ‘tty > «$FIFO»; read done < «$FIFO»‘focusscreen -t stdout sh -c ‘
read tty < «$FIFO»; eval «$CMD» 2> «$tty»;
echo «[Command exited with status $?, press enter to exit]»;
read prompt; echo done > «$FIFO»‘EOFCMD=»$*»
export FIFO CMDscreen -mc «$conf»
Использовать, например, можно так:
that-script ‘ls / /not-here’
Идея в том, что запускается screen с временным конфигурационным файлом, который запускает два окна screen в вертикальном режиме. В первом из них выполняется ваша команда, выводя stderr во второе.
Используется именованный канал для второго окна, чтобы передать устройство tty первому окну, а также для первого окна, чтобы сообщить второму окну, когда команда завершилась.
Другим преимуществом по сравнению с подходами, основанными на каналах, является то, что stdout и stderr команды по-прежнему соединены с устройствами tty, что не влияет на буферизацию. Оба окна также могут прокручиваться вверх и вниз отдельно (используя режим копирования screen).
Если вы запустите оболочку, такую как bash, в интерактивном режиме с этим скриптом, вы заметите, что приглашение будет отображаться во втором окне, в то время как оболочка будет считывать то, что вы печатаете, в первом окне, так как эти оболочки выводят свои приглашения на stderr.
В случае bash, echo того, что вы печатаете, также будет появляться во втором окне, так как это echo также выводится оболочкой (readline в случае bash) на stderr. С некоторыми другими оболочками, такими как ksh93, это будет отображаться в первом окне (echo выводится драйвером устройства терминала, а не оболочкой), если вы не установите оболочку в режим emacs или vi с set -o emacs или set -o vi.
Это не идеальное решение, основанное на сценарии annotate-output из Debian ANNOTATE-OUTPUT(1).
Не уверен, что это то, что вам нужно, но может стать началом:
#!/bin/bash
readonly col=150 # столбец для начала вывода ошибокa
dd_out (){ while IFS= read -r line;
do echo «$1: $line» done if [ ! -z «$line» ];
then echo -n «$1: $line» fi}
add_err (){ while IFS= read -r line;
do printf «%*s %s %s: %sn» $col «|» «$1» «$line» done if [ ! -z «$line» ];
then printf «%*s %s: %s» $col «$1» «$line» fi}
cleanup() { __st=$?; rm -rf «$tmp»; exit $__st; }
trap cleanup 0
trap ‘exit $?’ 1 2 13 15
tmp=$(mktemp -d —tmpdir annotate.XXXXXX) || exit 1
OUT=$tmp/outERR=$tmp/errmkfifo $OUT $ERR || exit 1
add_out OUTPUT < $OUT &add_err ERROR < $ERR &echo «I: Начало выполнения $@»»$@» > $OUT 2> $ERR ;
EXIT=$?
rm -f $OUT $ERRwaitecho «I: Завершено с кодом выхода $EXIT»exit $EXIT
Вы можете протестировать это, используя ./this_script another_script или command.
Я попробую проанализировать следующую часть вашего вопроса:
вместо этого будет выглядеть примерно так:
~$ какая-то команда какая-то полезная информация | дополнительный вывод | ОШИБКА: ошибка еще одно сообщение | ОШИБКА: произошло ~$
Если бы надо было разбить то, что вы хотите:
1) Поток stdout не завершал бы каждую строку на CR LF, а вместо этого заканчивал символом ‘|’. Конечно, это не выровняет два потока вместе, и выравнивание невозможно, так как это потребует прогнозирования длины будущих строк, добавляемых к stdout, что, конечно, невозможно.
2) Предполагая, что мы забыли о выравнивании, мы бы просто вывели stderr, предварительно обработав его через конвейер, добавляющий “ОШИБКА:” в начало каждой строки. Полагаю, это довольно просто, написав простой скрипт и убедившись, что stderr всегда проходит через этот скрипт.
Но это создало бы вывод вроде этого:
~$ какая-то команда какая-то полезная информация| дополнительный вывод| ОШИБКА: ошибка еще одно сообщение| ОШИБКА: произошло
Что на самом деле не очень полезно, не так ли?
Также я не думаю, что это то, чего вы хотите.
Проблема в изначальном вопросе заключается в том, что вы не учитываете последовательный характер каждой добавляемой строки в поток в связи с тем, что оба потока могут записываться асинхронно.
Я считаю, что наиболее близким возможным решением было бы использование ncurses.
См.
[http://www.tldp.org/HOWTO/html_single/NCURSES-Programming-HOWTO/]
[http://invisible-island.net/ncurses/ncurses-intro.html#updating]
Для того чтобы сделать то, что вы хотите, необходимо буферизовать оба потока и объединить их, чтобы создать третий буфер, который берет элементы из обоих буферов. Затем выводите третий буфер на экран терминала, стирая экран терминала и перерисовывая его каждый раз, когда изменяется третий буфер. Но так работает ncurses, так зачем изобретать колесо и не взять наработки оттуда?
В любом случае, вам придется взять на себя полный контроль за тем, как экран терминала рисуется! И повторно выровнять текст в пересобранной версии экрана так, как вам нравится. Как в видеоигре с символами терминала.
Надеюсь, мой ответ поможет понять ограничения того, что вы хотите…
Извините за повторение, но самая большая проблема с тем, что вы показывали, в том, как «обработчик» потоков stdout и stderr узнает заранее длину будущих строк, добавляемых в него для правильного выравнивания.
Я был поражён, что под Linux нет идеальной команды для этого, поэтому я написал небольшую утилиту на Python:
# output.py
import sysfrom rich
import printfrom rich.panel
import Panelimport subprocess
result = subprocess.run( sys.argv[ 1: ], capture_output = True, text = True )
print( Panel( result.stdout ) )
print( Panel( result.stderr ) )
Выглядит это так:
$ output.py какая-то команда
╭──────────────────────────────────────╮│ …stdout… │╰──────────────────────────────────────╯╭──────────────────────────────────────╮│ …stderr… │╰──────────────────────────────────────╯
Зависит от пакета rich.
Ответ или решение
Отображение потоков вывода (stdout) и ошибок (stderr) в двух отдельных потоках на экране может значительно повысить удобство чтения и анализа данных, особенно при работе с длинными и сложными программами. В типичном сценарии оба потока смешиваются, что затрудняет идентификацию и устранение ошибок в программном коде. В этом ответе мы рассмотрим несколько подходов к решению этой задачи, начиная с концептуального осмысления и заканчивая конкретными примерами использования технологий.
Теория
Главная идея заключается в том, чтобы визуально разделить потоки stdout и stderr. Решение этой задачи связано с пониманием того, как данные выводятся в терминале. Обычно, терминал смешивает оба потока, что может привести к неупорядоченному выводу, особенно если программа активно использует оба потока.
Разделяя выводящие потоки визуально, мы можем легко идентифицировать и устранить ошибки. Это достигается путем направления каждого потока в отдельную область экрана или в разные логические потоки в пользовательском интерфейсе. Два подхода, которые могут быть использованы для этого, включают использование программных оболочек, таких как GNU Screen, и написание пользовательских скриптов с использованием направляющих символов (pipes) и временных файлов.
Пример
Рассмотрим использование GNU Screen для разделения потоков. Это программа позволяет создаваться виртуальные терминальные сессии и управлять их распределением на экране. Следующая команда демонстрирует, как можно настроить временный конфигурационный файл для GNU Screen, чтобы организовать вертикальное разделение экранного пространства для потоков stdout и stderr:
#! /bin/bash
-tmpdir=$(mktemp -d) || exittrap ‘rm -rf «$tmpdir»‘ EXIT INT TERM HUPFIFO=$tmpdir/FIFO
mkfifo «$FIFO» || exitconf=$tmpdir/confcat > «$conf» << ‘EOF’ || exitsplit -vfocusscreen -t stderr sh -c ‘tty > «$FIFO»;
read done < «$FIFO»‘
focusscreen -t stdout sh -c ‘read tty < «$FIFO»; eval «$CMD» 2> «$tty»;
echo «[Command exited with status $?, press enter to exit]»;
read prompt;
echo done > «$FIFO»‘EOFCMD=»$*»
export FIFO CMD
screen -mc «$conf»
Данный скрипт создает временные файловые каналы и конфигурационный файл для screen. В конфигурационном файле настроено вертикальное разделение окна, в котором одна половина отображает stdout, а другая – stderr.
Применение
Для минимизации смешивания данных выходного потока и ошибок программное обеспечение или команды должны быть запущены через скрипт, предлагающий альтернативные пути вывода для каждого потока. Например, подключив данный скрипт к команде ls или другой команде, которая генерирует достаточно вывода и ошибок, можно четко увидеть распределение данных между двумя экранами.
Еще один подход заключается в написании скриптов на Python с использованием библиотек, таких как Rich. Пример на Python иллюстрирует возможность использования этого подхода:
# output.py
import sysfrom rich
import printfrom rich.panel
import Panelimport subprocess
result = subprocess.run( sys.argv[ 1: ], capture_output=True, text=True)
print(Panel(result.stdout))
print(Panel(result.stderr))
Этот скрипт на Python создает задачи и улавливает потоки stdout и stderr, после чего отображает их в виде панелей, благодаря чему можно добиться ясного разделения выводов.
Таким образом, применяя эти методы, вы сможете повысить производительность диагностики и исправления ошибок в программном обеспечении за счет улучшенного визуального управления выводом данных. В конечном итоге, это сократит время на анализ и устранение проблем, что приведет к более эффективной и продуктивной работе с программным обеспечением и операционными системами. Такой подход идеально подходит для использования в сценариях разработки, тестирования и администрирования систем, где прозрачность данных имеет первостепенное значение.