Чи можу я налаштувати свою оболонку для друку STDERR та STDOUT різними кольорами?


62

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

Чи є спосіб налаштувати це .bashrc? Якщо ні, чи це можливо навіть?


Примітка . Це запитання було об'єднано з іншим, який було запропоновано stderr, stdout і користувацьке введення відлуння повинно виводитися у 3 різних кольорах . Відповіді можуть стосуватися будь-якого питання.


1
Те саме питання щодо переповнення стека: stackoverflow.com/questions/6841143/…
Stéphane Gimenez

Цікаве питання + відповіді, проте червоний колір виділяється занадто сильно ІМО, оскільки stderr не лише для помилок
krookedking

Відповіді:


32

Це більш складна версія Показувати лише stderr на екрані, але записувати як файл stdout, так і stderr .

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

Ви можете підключити один з них до іншого каналу, додати колір до цього каналу та об'єднати два канали, але це спричинить дві проблеми:

  • Об'єднаний вихід може бути не в тому ж порядку, як якщо б не було перенаправлення. Це пояснюється тим, що додана обробка на одному з каналів займає (трохи) час, тому кольоровий канал може затримуватися. Якщо будь-яка буферизація буде зроблена, розлад буде гірше.
  • Термінали використовують кольори, що змінюють колір, щоб визначити колір відображення, наприклад, ␛[31mозначає «перейти на червоний передній план». Це означає, що якщо деякий вихід, призначений для stdout, надходить так само, як відображається деякий вихід для stderr, вихід буде неправильним кольором. (Ще гірше, якщо посеред послідовності втечі є перемикач каналів, ви побачите сміття.)

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

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

На практиці, якщо це застосовно, я пропоную перенаправити stderr на stdout і перетворити на колоризатор на основі шаблону, такий як colortail або multitail , або колоризатори спеціального призначення, такі як colorgcc або colormake .

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


1
Неможливо буде виправити термінальну програму, щоб розфарбувати потік stderr. Хтось запропонував щось подібне під час мозкового штурму ubuntu .
інтуїтоване

@intuited: це вимагатиме простеження кожного емулятора терміналу, з яким ви хочете, щоб це працювало. Використання LD_PRELOADхитрості для перехоплення writeдзвінків видається найбільш підходящим, IMO (але знову ж таки, можуть бути відмінності щодо певних * nix смаків.)
alex

Принаймні в Linux, перехоплення в writeпоодинці не буде працювати, оскільки більшість програм не дзвонить безпосередньо, але ще одна функція з деякої спільної бібліотеки (на зразок printf), яка викликала б оригіналwrite
Stéphane Chazelas

@StephaneChazelas я думав writeпричепитися до обгортки системного виклику. Чи вона вписана в інші функції в Glibc?
Жиль

1
Stderred проект , як видається , реалізація закріплювати з writeдопомогою , LD_PRELOADяк ви описуєте.
Дрю Ноакс

36

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


8
Приємно, що бібліотека є приголомшливою . Справжнє питання: чому моя операційна система / термінал не поставляється з цим попередньо встановленим? ;)
Нафтулі Кей

5
Я припускаю, що ви автор, правда? Ви повинні розкрити свою приналежність у такому випадку.
Дмитро Григор’єв

15

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

І тоді, є інший режим, коли драйверу терміналу кажуть, щоб не лунало, але додаток цього разу щось виводить. Додаток (як, наприклад, такі, що використовують readline, як gdb, bash ...), може надіслати це в його stdout або stderr, що буде важко відрізнити від чогось, що виводить для інших речей, ніж повторення даних користувача.

Тоді для того, щоб відрізнити stdout програми від його stderr, існує кілька підходів.

Багато з них передбачають перенаправлення команд stdout та stderr на труби, а ті труби, які читаються програмою для її забарвлення. З цим є дві проблеми:

  • Після того, як stdout більше не є терміналом (як натомість труба), багато додатків прагнуть адаптувати свою поведінку, щоб почати буферизацію свого виходу, що означає, що вихід буде відображатися великими шматками.
  • Навіть якщо це той самий процес, який обробляє дві труби, немає гарантії, що впорядкованість тексту, написаного додатком на stdout та stderr, буде збережена, оскільки процес читання не може знати (якщо з обох є що читати) чи слід починати читання з труби "stdout" або "stderr".

Інший підхід полягає в тому, щоб змінити додаток таким чином, щоб воно забарвлювало його stdout та stdin. Це часто неможливо чи реально зробити.

Тоді хитрістю (для динамічно пов'язаних додатків) може бути викрадення (використовуючи $LD_PRELOADяк у відповіді на серп ) функції виводу, викликані програмою, щоб щось вивести і включити в них код, який встановлює колір переднього плану, залежно від того, чи призначені вони для того, щоб щось вивести на stderr або stdout. Однак це означає викрадення будь-якої можливої ​​функції з бібліотеки С та будь-якої іншої бібліотеки, яка виконує 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, на БАШЕЄВ рядку і що ви друкуєте в той час як з з'являється в червоному (STDERR) zshз'являється в чорному (бо ЗШ Dups стандартного висновок на новий дескриптор , щоб відобразити його швидке і відлуння).

Схоже, це працює напрочуд добре навіть для програм, яких ви не очікуєте (як, наприклад, для кольорів).

Режим забарвлення виводиться на stracesderder, який передбачається терміналом. Якщо програма переспрямовує stdout або stderr, наша викрадена страйка продовжуватиме писати послідовності втечі розмальовки на терміналі.

Це рішення має свої обмеження:

  • Ті, які притаманні strace: проблемам із продуктивністю, ви не можете запускати інші команди PTRACE, як-от straceабо gdbв ній, або встановлені / встановлені жорсткі проблеми
  • Це забарвлення на основі writes на stdout / stderr кожного окремого процесу. Так, наприклад, в sh -c 'echo error >&2', errorвін буде зеленим, тому що echoвиводить його на свій stdout (який sh перенаправлений на stderr sh, але всі стрази бачать a write(1, "error\n", 6)). І в sh -c 'seq 1000000 | wc', seqробить багато або writeз його stdout, так що обгортка в кінцевому підсумку виведе багато (невидимих) послідовностей втечі до терміналу.

Приємно. Були пропозиції щодо існуючих обгортків щодо дублюючого питання . Я позначив це питання об'єднанням, щоб його можна було побачити.
Жиль

Може бути налаштування синтаксису 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
}

Там це набагато більш простий спосіб зробити це: exec 2> >(rederr). Обидві версії матимуть проблеми, про які я згадую у своїй відповіді, щодо переупорядкування рядків та ризику вичерпаного виводу (особливо з довгими рядками).
Жиль

Я спробував це, і це не вийшло.
Мікель

seterrповинен був бути окремим сценарієм, а не функцією.
Жиль

4

Див Майк Schiraldi в HILITE , який робить це для однієї команди в той час. Мій власний гуш робить це протягом цілого сеансу, але також має безліч інших особливостей / ідіосинкратій, які ви, можливо, не хочете.


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