我可以将 shell 配置为以不同颜色打印 STDERR 和 STDOUT 吗?

Naf*_*Kay 73 colors bash terminal stdout stderr

我想设置我的终端,以便stderr以不同的颜色打印stdout;也许是红色的。这样更容易区分两者。

有没有办法在中配置它.bashrc?如果没有,这甚至可能吗?


注意:这个问题是合并另一个是问stderrstdout 和用户输入回波是在输出3种不同颜色。答案可能正在解决任何一个问题。

小智 38

退房stderred。它用于LD_PRELOAD挂钩 tolibcwrite()调用,为所有stderr进入终端的输出着色。(默认为红色。)

  • 不错,那个图书馆_真棒_。真正的问题是:为什么我的操作系统/终端没有预装这个?;) (8认同)
  • 我假设你是作者,对吗?在这种情况下,您应该披露您的隶属关系。 (8认同)

Gil*_*il' 35

这是在屏幕上仅显示 stderr的更难版本,但将 stdout 和 stderr 都写入 file

终端中运行的应用程序使用单一通道与其通信;这些应用程序有两个输出端口,stdout 和 stderr,但它们都连接到同一个通道。

您可以将其中一个连接到不同的通道,为该通道添加颜色,然后合并两个通道,但这会导致两个问题:

  • 合并的输出可能与没有重定向的顺序完全相同。这是因为在其中一个通道上添加的处理需要(一点)时间,因此彩色通道可能会延迟。如果做任何缓冲,混乱会更严重。
  • 终端使用颜色变化的转义序列来确定显示颜色,例如?[31m表示“切换到红色前景”。这意味着,如果某些指定到 stdout 的输出在显示 stderr 的某些输出时到达,则输出将被错误着色。(更糟糕的是,如果在转义序列中间有一个频道开关,你会看到垃圾。)

原则上,可以编写一个同步侦听两个 ptys¹ 的程序(即在处理另一个通道上的输出时不接受一个通道上的输入),并立即使用适当的颜色更改指令输出到终端。您将失去运行与终端交互的程序的能力。我不知道这种方法的任何实现。

另一种可能的方法是使程序输出正确的颜色变化序列,方法是write在加载了LD_PRELOAD. 请参阅sickill对现有实现的回答,或Stéphane Chazelas对利用strace.

在实践中,如果这适用,我建议将 stderr 重定向到 stdout 并管道到基于模式的着色器(例如colortailmultitail)或特殊用途的着色器(例如colorgcccolormake )

¹伪终端。由于缓冲,管道将无法工作:源可以写入缓冲区,这会破坏与着色器的同步。


Sté*_*las 16

为用户输入着色很困难,因为在一半的情况下,它是由终端驱动程序输出的(带有本地回显),因此在这种情况下,在该终端中运行的应用程序可能不知道用户何时要键入文​​本并相应地更改输出颜色. 只有伪终端驱动程序(在内核中)知道(终端模拟器(如 xterm)在某些按键按下时向它发送一些字符,终端驱动程序可能会发回一些用于回显的字符,但 xterm 不知道这些是否来自本地回显或从应用程序输出到伪终端的从属端)。

然后,还有另一种模式,终端驱动程序被告知不要回显,但这次应用程序输出了一些东西。应用程序(就像那些使用像 gdb、bash... 这样的 readline 的应用程序)可能会在它的 stdout 或 stderr 上发送它,这将很难与它输出的东西区分开来,而不是回显用户输入。

然后为了区分应用程序的标准输出和标准错误,有几种方法。

其中许多涉及将命令 stdout 和 stderr 重定向到管道和应用程序读取的管道以对其进行着色。这有两个问题:

  • 一旦 stdout 不再是一个终端(而是像一个管道),许多应用程序倾向于调整它们的行为以开始缓冲它们的输出,这意味着输出将被显示为大块。
  • 即使处理这两个管道的进程是同一个进程,也不能保证应用程序在 stdout 和 stderr 上写入的文本的顺序会被保留,因为读取进程无法知道(如果有东西要从两者中读取)是从“stdout”管道还是“stderr”管道开始读取。

另一种方法是修改应用程序,使其为标准输出和标准输入着色。这通常是不可能或不现实的。

然后一个技巧(对于动态链接的应用程序)可以是劫持($LD_PRELOADsickill的回答中使用)应用程序调用的输出函数来输出一些东西,并在其中包含代码,根据它们是否打算输出一些东西来设置前景色在标准错误或标准输出上。但是,这意味着从 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-cmdbash,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 ,因此包装器最终将向终端输出大量(不可见的)转义序列。


小智 5

请参阅 Mike Schiraldi 的Hilite,它一次为一个命令执行此操作。我自己的热情在整个会话中都这样做,但也有许多您可能不想要的其他功能/特质。