Gil*_*il' 35
这是在屏幕上仅显示 stderr的更难版本,但将 stdout 和 stderr 都写入 file。
终端中运行的应用程序使用单一通道与其通信;这些应用程序有两个输出端口,stdout 和 stderr,但它们都连接到同一个通道。
您可以将其中一个连接到不同的通道,为该通道添加颜色,然后合并两个通道,但这会导致两个问题:
?[31m表示“切换到红色前景”。这意味着,如果某些指定到 stdout 的输出在显示 stderr 的某些输出时到达,则输出将被错误着色。(更糟糕的是,如果在转义序列中间有一个频道开关,你会看到垃圾。)原则上,可以编写一个同步侦听两个 ptys¹ 的程序(即在处理另一个通道上的输出时不接受一个通道上的输入),并立即使用适当的颜色更改指令输出到终端。您将失去运行与终端交互的程序的能力。我不知道这种方法的任何实现。
另一种可能的方法是使程序输出正确的颜色变化序列,方法是write在加载了LD_PRELOAD. 请参阅sickill对现有实现的回答,或Stéphane Chazelas对利用strace.
在实践中,如果这适用,我建议将 stderr 重定向到 stdout 并管道到基于模式的着色器(例如colortail或multitail)或特殊用途的着色器(例如colorgcc或colormake )。
¹伪终端。由于缓冲,管道将无法工作:源可以写入缓冲区,这会破坏与着色器的同步。
Sté*_*las 16
为用户输入着色很困难,因为在一半的情况下,它是由终端驱动程序输出的(带有本地回显),因此在这种情况下,在该终端中运行的应用程序可能不知道用户何时要键入文本并相应地更改输出颜色. 只有伪终端驱动程序(在内核中)知道(终端模拟器(如 xterm)在某些按键按下时向它发送一些字符,终端驱动程序可能会发回一些用于回显的字符,但 xterm 不知道这些是否来自本地回显或从应用程序输出到伪终端的从属端)。
然后,还有另一种模式,终端驱动程序被告知不要回显,但这次应用程序输出了一些东西。应用程序(就像那些使用像 gdb、bash... 这样的 readline 的应用程序)可能会在它的 stdout 或 stderr 上发送它,这将很难与它输出的东西区分开来,而不是回显用户输入。
然后为了区分应用程序的标准输出和标准错误,有几种方法。
其中许多涉及将命令 stdout 和 stderr 重定向到管道和应用程序读取的管道以对其进行着色。这有两个问题:
另一种方法是修改应用程序,使其为标准输出和标准输入着色。这通常是不可能或不现实的。
然后一个技巧(对于动态链接的应用程序)可以是劫持($LD_PRELOAD在sickill的回答中使用)应用程序调用的输出函数来输出一些东西,并在其中包含代码,根据它们是否打算输出一些东西来设置前景色在标准错误或标准输出上。但是,这意味着从 C 库和任何其他执行write(2)由应用程序直接调用的系统调用的库中劫持所有可能的函数,这些函数可能最终会在 stdout 或 stderr(printf、puts、perror...)上写一些东西,即使这样,这可能会改变其行为。
另一种方法可能是使用 PTRACE 技巧 as straceor gdbdo 在每次write(2)调用系统调用时钩住我们自己,并根据write(2)文件描述符 1 还是 2设置输出颜色。
然而,这是一件相当大的事情。
我刚刚玩的一个技巧是strace使用 LD_PRELOAD劫持自身(它在每次系统调用之前执行钩子本身的肮脏工作),告诉它根据是否write(2)在 fd 1 上检测到 a或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);
}
Run Code Online (Sandbox Code Playgroud)
然后,我们编译它:
cc -Wall -fpic -shared -o wrap.so wrap.c -ldl
Run Code Online (Sandbox Code Playgroud)
并将其用作:
LD_PRELOAD=/path/to/wrap.so strace -qfo /dev/null -e write -s 0 env -u LD_PRELOAD some-cmd
Run Code Online (Sandbox Code Playgroud)
您会注意到如果替换some-cmd为bash,bash 提示和您键入的内容显示为红色 (stderr),而 withzsh显示为黑色(因为 zsh 将 stderr 复制到新的 fd 上以显示其提示和回显)。
即使对于您不期望的应用程序(例如那些确实使用颜色的应用程序),它似乎也能很好地工作。
着色模式在strace被假定为终端的 stderr上输出。如果应用程序重定向其 stdout 或 stderr,我们被劫持的 strace 将继续在终端上写入着色转义序列。
该解决方案有其局限性:
strace:性能问题,您不能运行其他类似strace或其中的PTRACE 命令gdb,或 setuid/setgid 问题write每个进程的 stdout/stderr上的s 进行着色。因此,例如,在sh -c 'echo error >&2',error会因为是绿色的echo输出在其标准输出(其中SH重定向到SH的标准错误,但所有strace的看到的是一个write(1, "error\n", 6))。在sh -c 'seq 1000000 | wc',对其标准输出seq做了很多或writes ,因此包装器最终将向终端输出大量(不可见的)转义序列。