Das Färben von Benutzereingaben ist schwierig, da sie in der Hälfte der Fälle vom Terminaltreiber (mit lokalem Echo) ausgegeben werden. In diesem Fall weiß möglicherweise keine Anwendung, die in diesem Terminal ausgeführt wird, wann der Benutzer Text eingeben und die Ausgabefarbe entsprechend ändern wird . Nur der Pseudo-Terminal-Treiber (im Kernel) weiß, dass der Terminal-Emulator (wie xterm) bei einem Tastendruck einige Zeichen sendet, und der Terminal-Treiber sendet möglicherweise einige Zeichen für das Echo zurück, aber xterm kann nicht wissen, ob diese vom stammen lokales Echo oder was die Anwendung an die Slave-Seite des Pseudoterminals ausgibt).
Und dann gibt es noch den anderen Modus, in dem der Terminal-Treiber angewiesen wird, nicht zu echo, sondern die Anwendung diesmal etwas ausgibt. Die Anwendung (wie die, die readline wie gdb, bash ... verwenden) sendet möglicherweise das auf stdout oder stderr, was schwierig von etwas zu unterscheiden ist, das sie für andere Zwecke ausgibt, als die Benutzereingabe zurückzugeben.
Es gibt verschiedene Ansätze, um den Standardwert einer Anwendung von ihrem Standardwert zu unterscheiden.
Bei vielen von ihnen werden die Befehle stdout und stderr an Pipes umgeleitet, und diese Pipes werden von einer Anwendung gelesen, um sie einzufärben. Damit sind zwei Probleme verbunden:
- Sobald stdout kein Terminal mehr ist (wie eine Pipe), passen viele Anwendungen ihr Verhalten an, um die Ausgabe zu puffern. Dies bedeutet, dass die Ausgabe in großen Blöcken angezeigt wird.
- Auch wenn es derselbe Prozess ist, der die beiden Pipes verarbeitet, gibt es keine Garantie, dass die Reihenfolge, in der der von der Anwendung auf stdout und stderr geschriebene Text gespeichert wird, beibehalten wird, da der Lesevorgang nicht wissen kann (ob von beiden etwas zu lesen ist). ob mit dem Lesen von der "stdout" -Pipe oder der "stderr" -Pipe begonnen werden soll.
Ein weiterer Ansatz besteht darin, die Anwendung so zu ändern, dass sie ihre Standard- und Standardfarben einfärbt. Dies ist oft nicht möglich oder realistisch.
Dann kann ein Trick (für dynamisch verknüpfte Anwendungen) darin bestehen, die von der Anwendung aufgerufenen Ausgabefunktionen zu missbrauchen ( $LD_PRELOAD
wie in der Antwort von sickill ), um etwas auszugeben, und darin Code aufzunehmen, der die Vordergrundfarbe basierend darauf festlegt, ob sie etwas ausgeben sollen auf stderr oder stdout. Dies bedeutet jedoch, dass jede mögliche Funktion aus der C-Bibliothek und jeder anderen Bibliothek entführt wird, die einen write(2)
Systemaufruf ausführt, der direkt von der Anwendung aufgerufen wird und möglicherweise etwas auf stdout oder stderr (printf, puts, perror ...) schreibt, und sogar dann kann sein Verhalten ändern.
Ein anderer Ansatz könnte darin bestehen, PTRACE-Tricks bei jedem Aufruf des Systemaufrufs zu verwenden strace
oder gdb
sich selbst zu verbinden write(2)
und die Ausgabefarbe basierend darauf festzulegen, ob sich der write(2)
in Dateideskriptor 1 oder 2 befindet.
Das ist jedoch eine ziemlich große Sache.
Ein Trick, mit dem ich gerade gespielt habe, ist, strace
sich mit LD_PRELOAD selbst zu hijacken (was die Drecksarbeit macht, sich vor jedem Systemaufruf einzuhängen), um ihm mitzuteilen, dass er die Ausgabefarbe ändern soll, je nachdem, ob er write(2)
auf fd 1 oder a erkannt hat 2.
Wenn strace
wir uns den Quellcode ansehen, können wir sehen, dass alles, was er ausgibt, über die vfprintf
Funktion erfolgt. Alles was wir tun müssen, ist diese Funktion zu hijacken.
Der LD_PRELOAD-Wrapper würde folgendermaßen aussehen:
#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);
}
Dann kompilieren wir es mit:
cc -Wall -fpic -shared -o wrap.so wrap.c -ldl
Und benutze es als:
LD_PRELOAD=/path/to/wrap.so strace -qfo /dev/null -e write -s 0 env -u LD_PRELOAD some-cmd
Sie werden feststellen, dass beim Ersetzen some-cmd
durch bash
die Bash-Eingabeaufforderung und die zsh
Eingabe in Rot (stderr) angezeigt wird, während sie in Schwarz angezeigt wird (da zsh stderr auf einen neuen fd kopiert, um die Eingabeaufforderung und das Echo anzuzeigen).
Es scheint überraschend gut zu funktionieren, auch für Anwendungen, die Sie nicht erwarten würden (wie diejenigen, die Farben verwenden).
Der Farbmodus wird auf strace
dem stderr ausgegeben , der als Terminal angenommen wird. Wenn die Anwendung ihre stdout oder stderr umleitet, schreibt unsere entführte Strace die farbigen Escape-Sequenzen weiterhin auf das Terminal.
Diese Lösung hat ihre Grenzen:
- Jene, die inhärent sind mit
strace
: Leistungsproblemen, Sie können keine anderen PTRACE-Befehle wie strace
oder gdb
darin oder Setuid / Setgid-Probleme ausführen
- Die Färbung basiert auf den
write
Standard- / Standardfarben jedes einzelnen Prozesses. So zum Beispiel in sh -c 'echo error >&2'
, error
wäre grün , weil echo
gibt sie auf seine stdout (die sh des stderr umgeleitet sh, aber alle Strace sieht ein write(1, "error\n", 6)
). Und in sh -c 'seq 1000000 | wc'
, seq
hat viel oder wenig write
mit seiner Standardausgabe zu tun, sodass der Wrapper am Ende viele (unsichtbare) Escape-Sequenzen auf dem Terminal ausgibt.