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_PRELOAD
seperti 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 strace
atau gdb
lakukan 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 strace
dirinya 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 strace
kode sumber, kita dapat melihat bahwa semua outputnya dilakukan melalui vfprintf
fungsi. 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-cmd
dengan bash
, bash prompt dan apa yang Anda ketik muncul dengan warna merah (stderr) sementara dengan zsh
itu 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 strace
stderr 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 strace
atau gdb
di dalamnya, atau masalah setuid / setgid
- Ini mewarnai berdasarkan
write
pada stdout / stderr dari setiap proses individu. Jadi misalnya, in sh -c 'echo error >&2'
, error
akan berwarna hijau karena echo
menampilkannya 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'
, seq
tidak banyak atau write
s untuk yang stdout, sehingga bungkusnya akan berakhir outputing banyak (tak terlihat) melarikan diri urutan ke terminal.