我可以配置我的外壳以用不同的颜色打印STDERR和STDOUT吗?


62

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

有没有一种配置方式.bashrc?如果没有,那有可能吗?


注意:此问题已与另一个要求合并的问题stderrstdout 用户输入的回声将以3种不同的颜色输出。答案可能是解决这两个问题。


1
Stack Overflow上的同一个问题:stackoverflow.com/questions/6841143/...
斯特凡希门尼斯

有趣的问题+答案,但是,由于stderr不仅是
krookedking 2015年

Answers:


32

这是在屏幕上仅显示stderr的较硬版本,但是将stdout和stderr都写入文件

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

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

  • 合并后的输出可能没有完全相同的顺序,就好像没有重定向一样。这是因为在一个通道上进行的添加处理需要花费一点时间,因此彩色通道可能会延迟。如果进行了任何缓冲,则疾病将变得更糟。
  • 终端使用颜色变化的转义序列来确定显示颜色,例如,␛[31m意味着“切换到红色前景”。这意味着,如果某些输出到stdout的输出恰好在显示stderr的输出时到达,则该输出将被涂错颜色。(更糟糕的是,如果在转义序列的中间有一个频道切换,您将看到垃圾。)

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

另一种可能的方法是,通过将所有调用write系统调用的libc函数挂接在加载了的库中,使程序输出正确的颜色更改序列LD_PRELOAD。有关现有实现的信息,请参见sickill的答案;有关利用杠杆的混合方法,请参见StéphaneChazelas的答案strace

在实践中,如果适用,我建议将stderr重定向到stdout,然后将其管道传递到基于模式的着色器(例如colortailmultitail)或专用着色器(例如colorgcccolormake)

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


1
修补终端程序以着色stderr流可能并不困难。有人在ubuntu头脑风暴中提出了类似的建议。
直觉

@intuited:这需要将要使用的每个终端仿真器路径化。使用LD_PRELOAD技巧来拦截write呼叫似乎是最合适的,IMO(但同样,某些* nix风格可能会有所不同。)
alex

至少在Linux上,拦截write单独是行不通的,因为大多数应用程序并不直接调用,而是从一些共享库的另一个功能(像printf),这将调用原始write
斯特凡Chazelas

@StephaneChazelas我当时想挂在writesyscall包装器上。它是否内联到Glibc的其他函数中?
吉尔斯2012年

1
stderred项目似乎是挂钩的实现write通过LD_PRELOAD你描述。
Drew Noakes 2013年

36

结帐stderred。它用来LD_PRELOAD挂接到libcwrite()呼叫,为所有stderr输出到终端的输出着色。(默认为红色。)


8
很好,那个图书馆很棒。真正的问题是:为什么我的操作系统/终端没有预装此软件?;)
Naftuli Kay 2011年

5
我想你是作者,对吗?在这种情况下,您应披露您的隶属关系。
德米特里·格里戈里耶夫

15

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

然后,在另一种模式下,终端驱动程序被告知不要回显,但是这次应用程序会输出一些信息。该应用程序(如使用readline的gdb,bash等应用程序)可能会在其stdout或stderr上发送该消息,这将很难与它为回馈用户输入而输出的其他内容区分开。

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

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

  • 一旦stdout不再是终端(就像管道一样),许多应用程序便倾向于调整其行为以开始缓冲其输出,这意味着输出将以大块显示。
  • 即使是处理两个管道的相同过程,也无法保证应用程序在stdout和stderr上编写的文本的顺序将得以保留,因为读取过程无法知道(是否有从两者中读取的内容)是从“ stdout”管道还是“ stderr”管道开始读取。

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

然后,一个诀窍(对于动态链接的应用程序)可以是劫持($LD_PRELOAD病态回答使用)应用程序调用的输出函数以输出某些内容,并在其中包含代码,这些代码根据它们是否打算输出某些内容来设置前景色在stderr或stdout上。但是,这意味着从C库和其他任何write(2)直接由应用程序直接调用syscall的库中劫持所有可能的函数,这有可能最终在stdout或stderr上写一些东西(printf,puts,perror ...),甚至,可能会改变其行为。

另一种方法可能是使用PTRACE技巧stracegdb在每次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);
}

然后,我们将其编译为:

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-cmdbash,bash提示符和键入的内容将显示为红色(stderr),而zsh其显示为黑色(因为zsh将stderr复制到新的fd上以显示其提示和回显)。

即使对于您不希望的应用程序(例如使用颜色的应用程序),它的效果也确实令人惊讶地良好。

着色模式在strace假定为终端的stderr 上输出。如果应用程序重定向其stdout或stderr,则被劫持的strace将继续在终端上写入着色转义序列。

该解决方案有其局限性:

  • 这些固有的strace:性能问题,则不能运行其它PTRACE命令状stracegdb在其中,或的setuid / setgid的问题
  • 它是根据write每个单独进程的stdout / stderr 上的s 着色的。因此,例如,in sh -c 'echo error >&2'中的error将是绿色,因为echo stdout 上输出它(将sh重定向到sh的stderr,但strace看到的只是一个write(1, "error\n", 6))。和sh -c 'seq 1000000 | wc'seq做了很多或writes到标准输出,所以包装物最终会outputing很多(不可见)转义序列到终端的。

真好 对于重复的问题,有人提出了预先存在的包装器的建议。我已将问题标记为合并,以便在那里可以看到您的答案。
吉尔斯2012年

也许调整vim语法高亮?strace $CMD | vim -c ':set syntax=strace' -
Pablo A

4

这是我前一段时间做过的概念证明。

它仅适用于zsh。

# make standard error red
rederr()
{
    while read -r line
    do
        setcolor $errorcolor
        echo "$line"
        setcolor normal
    done
}

errorcolor=red

errfifo=${TMPDIR:-/tmp}/errfifo.$$
mkfifo $errfifo
# to silence the line telling us what job number the background job is
exec 2>/dev/null
rederr <$errfifo&
errpid=$!
disown %+
exec 2>$errfifo

它还假定您具有一个称为setcolor的函数。

简化版:

setcolor()
{
    case "$1" in
    red)
        tput setaf 1
        ;;
    normal)
        tput sgr0
        ;;
    esac
}

有一种更简单的方法可以执行此操作:exec 2> >(rederr)。这两个版本都会出现我在回答中提到的问题,即重新排列行并冒着输出混乱的风险(尤其是长行)。
吉尔斯

我试过了,但是没有用。
Mikel,

seterr将必须是独立脚本,而不是函数。
吉尔斯

4

请参阅Mike Schiraldi的Hilite,它一次只执行一项命令。我自己涌出做这行整个会话,但也有很多其他功能/ idiosyncracies,你可能不希望的。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.