Bisakah saya mengonfigurasi shell saya untuk mencetak STDERR dan STDOUT dalam berbagai warna?

63

Saya ingin mengatur terminal saya sehingga stderrdicetak dalam warna yang berbeda dari stdout; mungkin merah. Ini akan membuatnya lebih mudah untuk membedakan keduanya.

Apakah ada cara untuk mengkonfigurasi ini .bashrc? Jika tidak, apakah ini mungkin?


Catatan : Pertanyaan ini digabungkan dengan pertanyaan lain yang diminta stderr, stdout dan input pengguna bergema menjadi output dalam 3 warna berbeda . Jawaban dapat menjawab salah satu pertanyaan.

Naftuli Kay
sumber
1
Pertanyaan yang sama pada Stack Overflow: stackoverflow.com/questions/6841143/…
Stéphane Gimenez
Pertanyaan + jawaban yang menarik, namun merah menonjol terlalu banyak IMO karena stderr bukan hanya untuk kesalahan
krookedking

Jawaban:

32

Ini adalah versi yang lebih sulit dari Tampilkan hanya stderr di layar tetapi tulis stdout dan stderr ke file .

Aplikasi yang berjalan di terminal menggunakan saluran tunggal untuk berkomunikasi dengannya; aplikasi memiliki dua port output, stdout dan stderr, tetapi keduanya terhubung ke saluran yang sama.

Anda dapat menghubungkan salah satunya ke saluran yang berbeda, menambah warna ke saluran itu, dan menggabungkan kedua saluran, tetapi ini akan menyebabkan dua masalah:

  • Output gabungan mungkin tidak persis dalam urutan yang sama seolah-olah tidak ada pengalihan. Ini karena pemrosesan tambahan pada salah satu saluran membutuhkan waktu (sedikit), sehingga saluran berwarna mungkin tertunda. Jika penyangga dilakukan, gangguan akan menjadi lebih buruk.
  • Terminal menggunakan urutan pelepasan warna yang berubah untuk menentukan warna tampilan, mis. ␛[31mBerarti “beralih ke latar depan merah”. Ini berarti bahwa jika beberapa output yang ditujukan ke stdout tiba seperti halnya beberapa output untuk stderr ditampilkan, outputnya akan berubah warna. (Lebih buruk lagi, jika ada saklar saluran di tengah urutan pelarian, Anda akan melihat sampah.)

Pada prinsipnya, adalah mungkin untuk menulis sebuah program yang mendengarkan pada dua ptys¹, secara serempak (yaitu tidak akan menerima input pada satu saluran saat sedang memproses output pada saluran lain), dan segera mengeluarkan ke terminal dengan instruksi pengubahan warna yang sesuai. Anda kehilangan kemampuan untuk menjalankan program yang berinteraksi dengan terminal. Saya tidak tahu penerapan metode ini.

Pendekatan lain yang mungkin akan menyebabkan program untuk menghasilkan urutan perubahan warna yang tepat, dengan mengaitkan semua fungsi libc yang memanggil writesystem call di perpustakaan yang dimuat LD_PRELOAD. Lihat jawaban sickill untuk implementasi yang ada, atau jawaban Stéphane Chazelas untuk pendekatan campuran yang memanfaatkan strace.

Dalam praktiknya, jika itu berlaku, saya sarankan mengarahkan stderr ke stdout dan menyalurkannya ke colorizer berbasis pola seperti colortail atau multitail , atau pewarnaan dengan tujuan khusus seperti colorgcc atau colormake .

¹ terminal palsu. Pipa tidak akan berfungsi karena buffering: sumbernya bisa menulis ke buffer, yang akan merusak sinkronisitas dengan colorizer.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
1
Mungkin tidak sulit untuk menambal program terminal untuk mewarnai aliran stderr. Seseorang menyarankan sesuatu seperti ini di brainstorm ubuntu .
intuited
@intuited: itu membutuhkan pathcing setiap terminal emulator yang Anda inginkan ini berfungsi. Menggunakan LD_PRELOADtrik untuk mencegat writepanggilan tampaknya yang paling tepat, IMO (tapi sekali lagi, mungkin ada perbedaan pada rasa * nix tertentu.)
alex
Setidaknya di Linux, mencegat writesaja tidak akan berfungsi karena sebagian besar aplikasi tidak memanggil secara langsung, tetapi fungsi lain dari beberapa pustaka bersama (seperti printf) yang akan memanggil yang asliwrite
Stéphane Chazelas
@StephaneChazelas Saya berpikir untuk mengaitkan writebungkus syscall. Apakah ini diuraikan dalam fungsi-fungsi lain di Glibc?
Gilles 'SO- stop being evil'
1
Proyek yang macet tampaknya merupakan implementasi hooking writevia LD_PRELOADseperti yang Anda gambarkan.
Drew Noakes
36

Lihat stderred. Menggunakan LD_PRELOADuntuk menghubungkan ke libc's write()panggilan, mewarnai semua stderrkeluaran akan terminal. (Merah secara default.)

sickill
sumber
8
Bagus, perpustakaan itu luar biasa . Pertanyaan sebenarnya adalah: mengapa sistem / terminal operasi saya tidak dilengkapi dengan prainstal ini? ;)
Naftuli Kay
5
Saya berasumsi Anda adalah penulisnya, benarkan? Anda harus mengungkapkan afiliasi Anda dalam kasus itu.
Dmitry Grigoryev
15

Mewarnai input pengguna sulit karena dalam setengah kasus, itu adalah output oleh driver terminal (dengan gema lokal) sehingga dalam kasus itu, tidak ada aplikasi yang berjalan di terminal yang mungkin tahu kapan pengguna akan mengetik teks dan mengubah warna output sesuai . Hanya driver pseudo-terminal (di kernel) yang tahu (emulator terminal (seperti xterm) mengirimkannya beberapa karakter pada beberapa penekanan tombol dan driver terminal dapat mengirim kembali beberapa karakter untuk gema, tetapi xterm tidak dapat mengetahui apakah itu berasal dari gema lokal atau dari apa output aplikasi ke sisi budak dari terminal semu).

Dan kemudian, ada mode lain di mana driver terminal diberitahu untuk tidak menggema, tetapi aplikasi kali ini mengeluarkan sesuatu. Aplikasi (seperti yang menggunakan readline seperti gdb, bash ...) dapat mengirimkannya pada stdout atau stderr yang akan sulit dibedakan dari sesuatu yang di-output untuk hal-hal lain selain menggemakan kembali input pengguna.

Kemudian untuk membedakan stdout aplikasi dari stderr, ada beberapa pendekatan.

Banyak dari mereka melibatkan mengarahkan perintah stdout dan stderr ke pipa dan pipa-pipa itu dibaca oleh aplikasi untuk mewarnainya. Ada dua masalah dengan itu:

  • Setelah stdout bukan lagi terminal (seperti pipa), banyak aplikasi cenderung menyesuaikan perilaku mereka untuk mulai buffering output mereka yang berarti bahwa output akan ditampilkan dalam potongan besar.
  • Bahkan jika itu adalah proses yang sama yang memproses dua pipa, tidak ada jaminan bahwa urutan teks yang ditulis oleh aplikasi pada stdout dan stderr akan dipertahankan, karena proses membaca tidak dapat mengetahui (jika ada sesuatu yang bisa dibaca dari keduanya) apakah akan mulai membaca dari pipa "stdout" atau pipa "stderr".

Pendekatan lain adalah memodifikasi aplikasi sehingga warna stdout dan stdin. Seringkali tidak mungkin atau realistis untuk dilakukan.

Maka trik (untuk aplikasi yang terhubung secara dinamis) dapat dengan membajak (menggunakan $LD_PRELOADseperti dalam jawaban sickill ) fungsi keluaran yang disebut oleh aplikasi untuk menampilkan sesuatu dan memasukkan kode di dalamnya yang menetapkan warna latar depan berdasarkan apakah mereka dimaksudkan untuk menghasilkan sesuatu pada stderr atau stdout. Namun, itu berarti membajak setiap fungsi yang mungkin dari pustaka C dan pustaka lain yang melakukan write(2)syscall secara langsung dipanggil oleh aplikasi yang berpotensi berakhir menulis sesuatu di stdout atau stderr (printf, put, perror ...), dan bahkan kemudian , yang dapat mengubah perilakunya.

Pendekatan lain bisa menggunakan trik PTRACE seperti straceatau gdblakukan untuk mengaitkan diri kita sendiri setiap kali write(2)panggilan sistem dipanggil dan mengatur warna output berdasarkan pada apakah write(2)ada pada file deskriptor 1 atau 2.

Namun, itu hal yang cukup besar untuk dilakukan.

Trik yang baru saja saya mainkan adalah dengan membajak stracedirinya sendiri (yang melakukan pekerjaan kotor dengan mengaitkan dirinya sendiri sebelum setiap panggilan sistem) menggunakan LD_PRELOAD, untuk mengatakannya untuk mengubah warna output berdasarkan apakah ia telah mendeteksi write(2)pada fd 1 atau 2.

Dari melihat stracekode sumber, kita dapat melihat bahwa semua outputnya dilakukan melalui vfprintffungsi. Yang perlu kita lakukan adalah membajak fungsi itu.

Pembungkus LD_PRELOAD akan terlihat seperti:

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

Lalu, kami kompilasi dengan:

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

Dan gunakan sebagai:

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

Anda akan melihat bagaimana jika Anda menggantinya some-cmddengan bash, bash prompt dan apa yang Anda ketik muncul dengan warna merah (stderr) sementara dengan zshitu muncul dalam warna hitam (karena zsh dups stderr ke fd baru untuk menampilkan prompt dan gema).

Tampaknya bekerja dengan sangat baik bahkan untuk aplikasi yang tidak Anda harapkan (seperti yang menggunakan warna).

Mode pewarnaan adalah keluaran pada stracestderr yang dianggap terminal. Jika aplikasi mengarahkan ulang stdout atau stderr, strace kami yang dibajak akan terus menulis urutan pewarnaan escape pada terminal.

Solusi itu memiliki keterbatasan:

  • Yang melekat pada strace: masalah kinerja, Anda tidak dapat menjalankan perintah PTRACE lainnya seperti straceatau gdbdi dalamnya, atau masalah setuid / setgid
  • Ini mewarnai berdasarkan writepada stdout / stderr dari setiap proses individu. Jadi misalnya, in sh -c 'echo error >&2', errorakan berwarna hijau karena echomenampilkannya pada stdout -nya (yang harus diarahkan ke sh's stderr, tetapi semua strace saw adalah a write(1, "error\n", 6)). Dan dalam sh -c 'seq 1000000 | wc', seqtidak banyak atau writes untuk yang stdout, sehingga bungkusnya akan berakhir outputing banyak (tak terlihat) melarikan diri urutan ke terminal.
Stéphane Chazelas
sumber
Bagus. Ada saran dari pembungkus yang sudah ada sebelumnya pada pertanyaan duplikat . Saya telah menandai pertanyaan untuk digabungkan sehingga jawaban Anda dapat dilihat di sana.
Gilles 'SANGAT berhenti menjadi jahat'
Mungkin mengutak atik sintaks vim? strace $CMD | vim -c ':set syntax=strace' -.
Pablo A
4

Inilah bukti konsep yang saya lakukan beberapa waktu lalu.

Ini hanya berfungsi di 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

Ini juga mengasumsikan Anda memiliki fungsi yang disebut setcolor.

Versi yang disederhanakan:

setcolor()
{
    case "$1" in
    red)
        tput setaf 1
        ;;
    normal)
        tput sgr0
        ;;
    esac
}
Mikel
sumber
Ada cara yang lebih sederhana untuk melakukan ini: exec 2> >(rederr). Kedua versi akan memiliki masalah yang saya sebutkan dalam jawaban saya, tentang penataan ulang baris dan mempertaruhkan output hancur (terutama dengan garis panjang).
Gilles 'SO- stop being evil'
Saya mencobanya, dan itu tidak berhasil.
Mikel
seterrharus menjadi skrip mandiri, bukan fungsi.
Gilles 'SO- berhenti menjadi jahat'
4

Lihat Mike Schiraldi ini Hilite yang melakukan ini untuk satu perintah pada satu waktu. Sapuan saya sendiri melakukan ini untuk seluruh sesi, tetapi juga memiliki banyak fitur / keanehan lain yang mungkin tidak Anda inginkan.

Colin Macleod
sumber