Могу ли я настроить свою оболочку для печати STDERR и STDOUT разными цветами?

63

Я хочу настроить свой терминал так, чтобы stderrон печатался другим цветом, чем stdout; возможно красный. Это облегчит различие между ними.

Есть ли способ настроить это в .bashrc? Если нет, возможно ли это?


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

Нафтули Кей
источник
1
Тот же вопрос о переполнении стека: stackoverflow.com/questions/6841143/…
Стефан Гименес
Интересный вопрос + ответы, однако красный цвет выделяет слишком много IMO, поскольку stderr не только для ошибок
krookedking

Ответы:

32

Это более сложная версия Показать только stderr на экране, но записать как stdout, так и stderr в файл .

Приложения, работающие в терминале, используют один канал для связи с ним; приложения имеют два выходных порта, stdout и stderr, но оба они подключены к одному каналу.

Вы можете подключить один из них к другому каналу, добавить цвет к этому каналу и объединить два канала, но это вызовет две проблемы:

  • Объединенный вывод не может быть точно в том же порядке, как если бы не было перенаправления. Это связано с тем, что дополнительная обработка на одном из каналов занимает (немного) время, поэтому цветной канал может быть задержан. Если буферизация выполнена, расстройство будет хуже.
  • Терминалы используют изменяющие цвет escape-последовательности для определения цвета дисплея, например, ␛[31mозначает «переключиться на красный передний план». Это означает, что если какой-то вывод, предназначенный для stdout, поступает так же, как выводится какой-то вывод для stderr, вывод будет окрашен в неверный цвет. (Еще хуже, если в середине escape-последовательности есть переключатель каналов, вы увидите мусор.)

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

Другой возможный подход заключается в том, чтобы заставить программу выводить правильные последовательности изменения цвета, подключая все функции libc, которые вызывают writeсистемный вызов в библиотеке, загруженной LD_PRELOAD. См . Ответ Седилл для существующей реализации или ответ Стефана Шазеласа для смешанного подхода, который использует strace.

На практике, если это применимо, я предлагаю перенаправлять поток ошибок на стандартный вывод и трубопроводов в основе шаблонов Colorizer такой , как colortail или multitail или colorizers специального назначения , такие как colorgcc или colormake .

¹ псевдо-терминалы. Каналы не будут работать из-за буферизации: источник может записывать в буфер, что нарушит синхронность с колоризатором.

Жиль "ТАК - перестань быть злым"
источник
1
Это может быть не сложно исправить программу терминала для окрашивания потока stderr. Кто-то предложил что-то подобное на брейнстоуме Ubuntu .
интуитивно
@intuited: для этого потребуется указать путь к каждому эмулятору терминала, с которым вы хотите работать. Использование LD_PRELOADтрюка для перехвата writeвызовов кажется наиболее подходящим, IMO (но, опять же, могут быть различия в некоторых разновидностях * nix.)
alex
По крайней мере, в Linux, перехват в writeодиночку не будет работать, так как большинство приложений не вызывают напрямую, но другая функция из некоторой общей библиотеки (например printf), которая будет вызывать оригиналwrite
Стефан Шазелас
@StephaneChazelas Я думал о том, чтобы writeперехватить оболочку системного вызова. Это встроено в другие функции в Glibc?
Жиль "ТАК ... перестать быть злым"
1
Проект stderred, кажется, является реализацией перехвата writeчерез, LD_PRELOADкак вы описываете.
Дрю Ноакс
36

Проверьте stderred. Он использует LD_PRELOADдля подключения к libc«s write()вызовов, раскрашивание всех stderrвыходных собирается терминал. (По умолчанию красным)

sickill
источник
8
Хорошо, эта библиотека потрясающая . Реальный вопрос: почему моя операционная система / терминал не поставляется с этим предустановленным? ;)
Нафтули Кей
5
Я предполагаю, что вы автор, это верно? Вы должны раскрыть свою принадлежность в этом случае.
Дмитрий Григорьев
15

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

И затем, есть другой режим, в котором драйвер терминала получает указание не отображать эхо, но приложение на этот раз что-то выводит. Приложение (например, использующее readline, такое как gdb, bash ...) может отправлять это на свой стандартный вывод или стандартный поток ошибок, что будет трудно отличить от чего-то, что оно выводит для других вещей, чем вывод пользовательского ввода.

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

Многие из них включают в себя перенаправление команд stdout и stderr на каналы, и эти каналы читаются приложением для его раскрашивания. Есть две проблемы с этим:

  • Когда stdout больше не является терминалом (например, конвейером), многие приложения стремятся адаптировать свое поведение, чтобы начать буферизовать свои выходные данные, что означает, что выходные данные будут отображаться большими кусками.
  • Даже если один и тот же процесс обрабатывает два канала, нет гарантии, что порядок текста, написанный приложением на stdout и stderr, будет сохранен, поскольку процесс чтения не может знать (есть ли что-то, что можно прочитать из обоих) начать ли чтение с канала "stdout" или канала "stderr".

Другой подход состоит в том, чтобы изменить приложение так, чтобы оно окрашивало свои stdout и stdin. Это часто невозможно или реально сделать.

Тогда уловка (для динамически связанных приложений) может заключаться в том, чтобы захватить (используя, $LD_PRELOADкак в ответе sickill ) функции вывода, вызываемые приложением для вывода чего-либо, и включать в них код, который устанавливает цвет переднего плана на основе того, предназначены ли они для вывода чего-либо на стандартный вывод или стандартный вывод. Тем не менее, это означает угон каждой возможной функции из библиотеки C и любой другой библиотеки, которая выполняет write(2)системный вызов, вызываемый напрямую приложением, который может в конечном итоге записать что-то на stdout или stderr (printf, put, perror ...) и даже тогда , что может изменить его поведение.

Другой подход может состоять в том, чтобы использовать приемы PTRACE как straceили gdbсделать, чтобы перехватывать себя каждый раз, когда write(2)вызывается системный вызов, и устанавливать выходной цвет в зависимости от того, включен ли write(2)дескриптор файла 1 или 2.

Тем не менее, это довольно большая вещь, чтобы сделать.

Уловка, с которой я только что играл, состоит в том, чтобы straceперехватить саму себя (которая делает грязную работу перехвата перед каждым системным вызовом), используя LD_PRELOAD, чтобы сказать ему, чтобы он изменял выходной цвет в зависимости от того, обнаружил ли он write(2)на fd 1 или 2.

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

Оболочка LD_PRELOAD будет выглядеть так:

#define _GNU_SOURCE
#include <dlfcn.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>

int vfprintf(FILE *outf, const char *fmt, va_list ap)
{
  static int (*orig_vfprintf) (FILE*, const char *, va_list) = 0;
  static int c = 0;
  va_list ap_orig;
  va_copy(ap_orig, ap);
  if (!orig_vfprintf) {
    orig_vfprintf = (int (*) (FILE*, const char *, va_list))
      dlsym (RTLD_NEXT, "vfprintf");
  }

  if (strcmp(fmt, "%ld, ") == 0) {
    int fd = va_arg(ap, long);
    switch (fd) {
    case 2:
      write(2, "\e[31m", 5);
      c = 1;
      break;
    case 1:
      write(2, "\e[32m", 5);
      c = 1;
      break;
    }
  } else if (strcmp(fmt, ") ") == 0) {
    if (c) write(2, "\e[m", 3);
    c = 0;
  }
  return orig_vfprintf(outf, fmt, ap_orig);
}

Затем мы компилируем это:

cc -Wall -fpic -shared -o wrap.so wrap.c -ldl

И использовать его как:

LD_PRELOAD=/path/to/wrap.so strace -qfo /dev/null -e write -s 0 env -u LD_PRELOAD some-cmd

Вы заметите, как, если вы замените some-cmdна bash, приглашение bash и то, что вы zshнабираете, отображаются красным (stderr), а с ним - черным (потому что zsh переводит stderr на новый fd, чтобы отобразить его приглашение и эхо).

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

Режим окраски выводится на strace's stderr, который считается терминалом. Если приложение перенаправляет свой stdout или stderr, наш угнанный страйс будет продолжать записывать escape-последовательности окраски на терминале.

Это решение имеет свои ограничения:

  • Они присущи strace: проблемам с производительностью, вы не можете запускать другие команды PTRACE, такие как straceили gdbвнутри него, или проблемы с setuid / setgid
  • Это цвет, основанный на writes на stdout / stderr каждого отдельного процесса. Так, например, in sh -c 'echo error >&2', errorбудет зеленым, потому что echoвыводит его на свой стандартный вывод (который sh перенаправляется на stderr sh, но все стрисы видят a write(1, "error\n", 6)). И, in sh -c 'seq 1000000 | wc', seqделает много или writes для своего стандартного вывода, так что обертка в конечном итоге будет выводить много (невидимых) escape-последовательностей в терминал.
Стефан Шазелас
источник
Приятно. Были предложения по существующим оберткам на дубликате вопроса . Я пометил вопрос для слияния, чтобы ваш ответ был там виден.
Жиль "ТАК - перестань быть злым"
Может быть, настройка подсветки синтаксиса vim? strace $CMD | vim -c ':set syntax=strace' -,
Пабло А
4

Вот подтверждение концепции, которую я сделал некоторое время назад.

Это работает только в Zsh.

# make standard error red
rederr()
{
    while read -r line
    do
        setcolor $errorcolor
        echo "$line"
        setcolor normal
    done
}

errorcolor=red

errfifo=${TMPDIR:-/tmp}/errfifo.$$
mkfifo $errfifo
# to silence the line telling us what job number the background job is
exec 2>/dev/null
rederr <$errfifo&
errpid=$!
disown %+
exec 2>$errfifo

Также предполагается, что у вас есть функция с именем setcolor.

Упрощенная версия:

setcolor()
{
    case "$1" in
    red)
        tput setaf 1
        ;;
    normal)
        tput sgr0
        ;;
    esac
}
Mikel
источник
Там это гораздо более простой способ сделать это: exec 2> >(rederr). Обе версии будут иметь проблемы, о которых я упоминал в своем ответе, о переупорядочении строк и риске искаженного вывода (особенно с длинными линиями).
Жиль "ТАК - перестань быть злым"
Я попробовал это, и это не сработало.
Микель
seterrдолжен быть автономный скрипт, а не функция.
Жиль "ТАК ... перестать быть злым"
4

См Майк Schiraldi в HILITE , который делает это для одной команды в то время. Моя собственная радость делает это для всей сессии, но также имеет много других особенностей / особенностей, которые вы, возможно, не захотите.

Колин Маклеод
источник