Czy mogę skonfigurować moją powłokę do drukowania STDERR i STDOUT w różnych kolorach?


62

Chcę skonfigurować mój terminal, aby stderrbył drukowany w innym kolorze niż stdout; może czerwony. Ułatwiłoby to rozróżnienie między nimi.

Czy istnieje sposób, aby to skonfigurować .bashrc? Jeśli nie, czy to w ogóle możliwe?


Uwaga : To pytanie zostało połączone z innym , o które proszono stderr, stdout a echo wejściowe użytkownika ma być wyprowadzane w 3 różnych kolorach . Odpowiedzi mogą dotyczyć obu pytań.


1
To samo pytanie na temat przepełnienia stosu: stackoverflow.com/questions/6841143/…
Stéphane Gimenez

Ciekawe pytanie + odpowiedzi, jednak czerwony wyróżnia się za bardzo IMO, ponieważ stderr to nie tylko błędy
krzyknął

Odpowiedzi:


32

To jest trudniejsza wersja Pokaż tylko stderr na ekranie, ale zapisz zarówno stdout, jak i stderr do pliku .

Aplikacje działające w terminalu używają jednego kanału do komunikacji z nim; aplikacje mają dwa porty wyjściowe, stdout i stderr, ale oba są podłączone do tego samego kanału.

Możesz podłączyć jeden z nich do innego kanału, dodać kolor do tego kanału i połączyć dwa kanały, ale spowoduje to dwa problemy:

  • Połączone dane wyjściowe mogą nie być dokładnie w tej samej kolejności, jak gdyby nie było przekierowania. Wynika to z faktu, że dodatkowe przetwarzanie na jednym z kanałów zajmuje (trochę) czasu, więc kolorowy kanał może być opóźniony. Jeśli zostanie wykonane buforowanie, zaburzenie będzie gorsze.
  • Terminale używają zmieniających kolor sekwencji ucieczki w celu określenia koloru wyświetlacza, np. ␛[31mOznacza „przełącz na czerwony pierwszy plan”. Oznacza to, że jeśli część danych wyjściowych przeznaczonych do wyjścia standardowego pojawi się w momencie wyświetlenia niektórych danych wyjściowych dla standardu wyjściowego, dane wyjściowe będą błędne. (Co gorsza, jeśli w środku sekwencji ucieczki znajduje się przełącznik kanałów, zobaczysz śmieci).

Zasadniczo byłoby możliwe napisanie programu, który nasłuchuje na dwóch ptys¹, synchronicznie (tj. Nie przyjmie danych wejściowych na jednym kanale, gdy przetwarza dane wyjściowe na drugim kanale), i natychmiast wyśle ​​dane wyjściowe do terminala z odpowiednimi instrukcjami zmiany kolorów. Straciłbyś możliwość uruchamiania programów, które współdziałają z terminalem. Nie znam żadnej implementacji tej metody.

Innym możliwym podejściem byłoby spowodowanie, aby program wyświetlał prawidłowe sekwencje zmieniające kolory, przechwytując wszystkie funkcje libc, które wywołują wywołanie writesystemowe w załadowanej bibliotece LD_PRELOAD. Zobacz odpowiedź chorego na istniejące wdrożenie lub odpowiedź Stéphane Chazelas na mieszane podejście, które wykorzystuje strace.

W praktyce, jeśli ma to zastosowanie, sugeruję przekierowanie stderr na stdout i podłączenie do koloratora opartego na wzorach , takiego jak colortail lub multitail , lub specjalnych barwników , takich jak colorgcc lub colormake .

¹ pseudo-terminale. Rurki nie działałyby z powodu buforowania: źródło mogłoby zapisać do bufora, co przerwałoby synchroniczność z koloryzatorem.


1
Łatanie programu terminalowego w celu pokolorowania strumienia stderr może nie być trudne. Ktoś zasugerował coś takiego podczas burzy mózgów ubuntu .
intuicyjnie

@intuited: wymagałoby to śledzenia każdego emulatora terminala, z którym chcesz to współpracować. Używanie LD_PRELOADsztuczki do przechwytywania writepołączeń wydaje się być najbardziej odpowiednie, IMO (ale z drugiej strony mogą występować różnice w niektórych smakach * nix.)
alex

Przynajmniej w Linuksie przechwytywanie w writepojedynkę nie działałoby, ponieważ większość aplikacji nie wywołuje bezpośrednio, ale inna funkcja z niektórych bibliotek współdzielonych (jak printf), które wywoływałyby oryginałwrite
Stéphane Chazelas

@StephaneChazelas Myślałem o zahaczeniu o writeopakowanie syscall. Czy jest wbudowany w inne funkcje w Glibc?
Gilles

1
Ten projekt wydaje się być implementacją przechwytywaniawrite za pośrednictwem, LD_PRELOADjak opisano.
Drew Noakes,

36

Sprawdź stderred. Wykorzystuje LD_PRELOADdo haka libc„s write()połączeń, kolorowanie wszystkie stderrwyjścia przechodząc do terminalu. (Domyślnie na czerwono.)


8
Fajnie, ta biblioteka jest niesamowita . Prawdziwe pytanie brzmi: dlaczego mój system operacyjny / terminal nie jest fabrycznie zainstalowany? ;)
Naftuli Kay

5
Zakładam, że jesteś autorem, prawda? W takim przypadku należy ujawnić swoje powiązanie.
Dmitrij Grigoryev,

15

Kolorowanie danych wejściowych użytkownika jest trudne, ponieważ w połowie przypadków jest ono wysyłane przez sterownik terminalu (z lokalnym echem), więc w takim przypadku żadna aplikacja działająca w tym terminalu może nie wiedzieć, kiedy użytkownik zamierza wpisać tekst i odpowiednio zmienić kolor wyjściowy . Tylko pseudo-terminalowy sterownik (w jądrze) wie (emulator terminala (jak xterm) wysyła mu pewne znaki po pewnym naciśnięciu klawisza, a sterownik terminala może odsyłać niektóre znaki dla echa, ale xterm nie może wiedzieć, czy są one z lokalne echo lub dane wyjściowe aplikacji na stronie slave pseudo terminala).

A potem jest inny tryb, w którym sterownikowi terminalu mówi się, żeby nie echo, ale aplikacja tym razem coś wypisuje. Aplikacja (podobnie jak te używające readline, takie jak gdb, bash ...) może wysłać to na swoim standardzie lub stderr, co będzie trudne do odróżnienia od czegoś, co wyprowadza dla innych rzeczy niż echo danych wejściowych użytkownika.

Następnie, aby odróżnić stdout aplikacji od stderr, istnieje kilka podejść.

Wiele z nich polega na przekierowaniu poleceń stdout i stderr do potoków i potoków odczytanych przez aplikację w celu pokolorowania. Są z tym dwa problemy:

  • Kiedy stdout nie jest już terminalem (jak potok zamiast), wiele aplikacji dostosowuje swoje zachowanie, aby rozpocząć buforowanie danych wyjściowych, co oznacza, że ​​dane wyjściowe będą wyświetlane w dużych porcjach.
  • Nawet jeśli jest to ten sam proces, który przetwarza dwie potoki, nie ma gwarancji, że kolejność tekstu napisanego przez aplikację na stdout i stderr zostanie zachowana, ponieważ proces odczytu nie może wiedzieć (czy jest coś do odczytania z obu) czy rozpocząć czytanie z rury „stdout” czy „stderr”.

Innym podejściem jest zmodyfikowanie aplikacji tak, aby kolorowała swoje standardowe i standardowe. Często nie jest to możliwe ani realistyczne.

Następnie sztuczką (w przypadku dynamicznie połączonych aplikacji) może być przejęcie (użycie $LD_PRELOADjak w odpowiedzi Illilla ) funkcji wyjściowych wywoływanych przez aplikację w celu wypisania czegoś i umieszczenia w nich kodu określającego kolor pierwszego planu na podstawie tego, czy mają one coś wypisać na stderr lub stdout. Oznacza to jednak przejęcie każdej możliwej funkcji z biblioteki C i dowolnej innej biblioteki wykonującej write(2)syscall bezpośrednio wywołanej przez aplikację, która może potencjalnie skończyć pisaniem czegoś na stdout lub stderr (printf, puts, perror ...), a nawet wtedy , co może zmienić jego zachowanie.

Innym podejściem może być użycie sztuczek PTRACE jako stracelub gdbzrobić, aby zaczepić się za każdym razem, gdy write(2)wywoływane jest wywołanie systemowe, i ustawić kolor wyjściowy na podstawie tego, czy write(2)jest on w deskryptorze pliku 1 czy 2.

Jest to jednak dość duża rzecz.

Sztuką, z którą właśnie bawiłem stracesię, jest przechwycenie samego siebie (co powoduje brudną robotę zaczepienia się przed każdym wywołaniem systemowym) za pomocą LD_PRELOAD, aby kazać mu zmienić kolor wyjściowy w zależności od tego, czy wykrył write(2)na fd 1 czy 2)

Patrząc na stracekod źródłowy, możemy zobaczyć, że wszystkie dane wyjściowe są wykonywane przez vfprintffunkcję. Wszystko, co musimy zrobić, to przejąć tę funkcję.

Opakowanie LD_PRELOAD wyglądałoby następująco:

#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);
}

Następnie kompilujemy to z:

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

I użyj go jako:

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

Zauważysz, jak w przypadku wymiany some-cmdz bash, wierszu bash i co piszesz pojawia się na czerwono (stderr), natomiast z zshwydaje się na czarno (bo zsh dups stderr na nowy fd, aby wyświetlić jego szybka i echo).

Wygląda na to, że działa zaskakująco dobrze nawet w aplikacjach, których nie spodziewałbyś się (takich jak te, które używają kolorów).

Tryb kolorowania jest wyprowadzany na stracestderr, który jest uważany za terminal. Jeśli aplikacja przekieruje swoje standardowe wyjście lub standardowe polecenie, nasz przejęty ciąg będzie nadal zapisywał sekwencje specjalne koloru na terminalu.

To rozwiązanie ma swoje ograniczenia:

  • Te związane z strace: problemami z wydajnością, nie można uruchamiać innych poleceń PTRACE, takich jak stracelub gdbw nim, ani problemów z setuid / setgid
  • Kolorystyka oparta na writes na stdout / stderr każdego indywidualnego procesu. Tak na przykład, w sh -c 'echo error >&2', errorbyłby zielony, ponieważ echowyjścia na IT jej stdout (które sh przekierowany do stderr SH, ale wszystko strace widzi to write(1, "error\n", 6)). I in sh -c 'seq 1000000 | wc', seqrobi wiele lub więcej writedo swojego standardu , więc opakowanie ostatecznie wyśle ​​wiele (niewidocznych) sekwencji ucieczki do terminala.

Miły. Były sugestie wcześniejszych opakowań na duplikat pytania . Oflagowałem pytanie do połączenia, aby Twoja odpowiedź była tam widoczna.
Gilles,

Może podkręcenie składni vima? strace $CMD | vim -c ':set syntax=strace' -.
Pablo A

4

Oto dowód koncepcji, który zrobiłem jakiś czas temu.

Działa tylko w 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

Zakłada również, że masz funkcję o nazwie setcolor.

Uproszczona wersja:

setcolor()
{
    case "$1" in
    red)
        tput setaf 1
        ;;
    normal)
        tput sgr0
        ;;
    esac
}

Jest znacznie prostszy sposób, aby to zrobić: exec 2> >(rederr). Obie wersje będą miały problemy, o których wspomniałem w mojej odpowiedzi, dotyczące zmiany kolejności linii i ryzyka zniekształconego wyjścia (szczególnie w przypadku długich linii).
Gilles

Próbowałem tego i to nie działało.
Mikel

seterrmusiałby być samodzielnym skryptem, a nie funkcją.
Gilles

4

Zobacz Hilite Mike'a Schiraldiego, który robi to dla jednego polecenia na raz. Mój własny wytrysk robi to przez całą sesję, ale ma też wiele innych funkcji / osobliwości, których możesz nie chcieć.


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.