Розфарбувати введення користувача складно, оскільки в половині випадків він виводиться драйвером терміналу (з локальним відлунням), тому в цьому випадку жодна програма, що працює в цьому терміналі, може не знати, коли користувач збирається вводити текст і відповідно змінювати колір виводу. . Тільки драйвер псевдотерміналу (в ядрі) знає (емулятор термінала (як 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 стандартного висновок на новий дескриптор , щоб відобразити його швидке і відлуння).
Схоже, це працює напрочуд добре навіть для програм, яких ви не очікуєте (як, наприклад, для кольорів).
Режим забарвлення виводиться на strace
sderder, який передбачається терміналом. Якщо програма переспрямовує stdout або stderr, наша викрадена страйка продовжуватиме писати послідовності втечі розмальовки на терміналі.
Це рішення має свої обмеження:
- Ті, які притаманні
strace
: проблемам із продуктивністю, ви не можете запускати інші команди PTRACE, як-от strace
або gdb
в ній, або встановлені / встановлені жорсткі проблеми
- Це забарвлення на основі
write
s на 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, так що обгортка в кінцевому підсумку виведе багато (невидимих) послідовностей втечі до терміналу.