我想设置我的终端机,使它stderr
以不同于的颜色打印stdout
。也许是红色。这样可以更容易区分两者。
有没有一种配置方式.bashrc
?如果没有,那有可能吗?
注意:此问题已与另一个要求合并的问题stderr
,stdout
用户输入的回声将以3种不同的颜色输出。答案可能是解决这两个问题。
我想设置我的终端机,使它stderr
以不同于的颜色打印stdout
。也许是红色。这样可以更容易区分两者。
有没有一种配置方式.bashrc
?如果没有,那有可能吗?
注意:此问题已与另一个要求合并的问题stderr
,stdout
用户输入的回声将以3种不同的颜色输出。答案可能是解决这两个问题。
Answers:
这是在屏幕上仅显示stderr的较硬版本,但是将stdout和stderr都写入文件。
终端中运行的应用程序使用单个通道与之通信;应用程序有两个输出端口,stdout和stderr,但是它们都连接到同一通道。
您可以将其中一个连接到另一个通道,为该通道添加颜色,然后合并两个通道,但这将导致两个问题:
␛[31m
意味着“切换到红色前景”。这意味着,如果某些输出到stdout的输出恰好在显示stderr的输出时到达,则该输出将被涂错颜色。(更糟糕的是,如果在转义序列的中间有一个频道切换,您将看到垃圾。)原则上,可以编写一个程序,该程序可以同步侦听两个pty¹(即在处理另一个通道的输出时不接受一个通道的输入),并立即通过适当的变色指令输出到终端。您将失去运行与终端交互的程序的能力。我不知道此方法的任何实现。
另一种可能的方法是,通过将所有调用write
系统调用的libc函数挂接在加载了的库中,使程序输出正确的颜色更改序列LD_PRELOAD
。有关现有实现的信息,请参见sickill的答案;有关利用杠杆的混合方法,请参见StéphaneChazelas的答案strace
。
在实践中,如果适用,我建议将stderr重定向到stdout,然后将其管道传递到基于模式的着色器(例如colortail或multitail)或专用着色器(例如colorgcc或colormake)。
¹ 伪终端。管道由于缓冲而无法工作:源可以将其写入缓冲区,这将破坏与着色器的同步性。
LD_PRELOAD
技巧来拦截write
呼叫似乎是最合适的,IMO(但同样,某些* nix风格可能会有所不同。)
write
单独是行不通的,因为大多数应用程序并不直接调用,而是从一些共享库的另一个功能(像printf
),这将调用原始write
write
syscall包装器上。它是否内联到Glibc的其他函数中?
结帐stderred
。它用来LD_PRELOAD
挂接到libc
的write()
呼叫,为所有stderr
输出到终端的输出着色。(默认为红色。)
为用户输入上色很困难,因为在一半情况下,它是由终端驱动程序输出的(带有本地回显),因此在那种情况下,在该终端上运行的应用程序可能不知道用户何时键入文本并相应地更改输出颜色。只有伪终端驱动程序(在内核中)知道(终端仿真器(如xterm)在某些按键上向它发送一些字符,而终端驱动程序可能会向回发送一些字符以进行回显,但是xterm无法知道这些字符是否来自于本地回显或从应用程序输出到伪终端的从属端)。
然后,在另一种模式下,终端驱动程序被告知不要回显,但是这次应用程序会输出一些信息。该应用程序(如使用readline的gdb,bash等应用程序)可能会在其stdout或stderr上发送该消息,这将很难与它为回馈用户输入而输出的其他内容区分开。
然后,要区分应用程序的标准输出和标准错误,有几种方法。
其中许多涉及将命令stdout和stderr重定向到管道,应用程序读取这些管道以对其着色。这有两个问题:
另一种方法是修改应用程序,以便为标准输出和标准输入着色。这通常是不可能或不现实的。
然后,一个诀窍(对于动态链接的应用程序)可以是劫持($LD_PRELOAD
按病态回答使用)应用程序调用的输出函数以输出某些内容,并在其中包含代码,这些代码根据它们是否打算输出某些内容来设置前景色在stderr或stdout上。但是,这意味着从C库和其他任何write(2)
直接由应用程序直接调用syscall的库中劫持所有可能的函数,这有可能最终在stdout或stderr上写一些东西(printf,puts,perror ...),甚至,可能会改变其行为。
另一种方法可能是使用PTRACE技巧strace
或gdb
在每次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-cmd
为bash
,bash提示符和键入的内容将显示为红色(stderr),而zsh
其显示为黑色(因为zsh将stderr复制到新的fd上以显示其提示和回显)。
即使对于您不希望的应用程序(例如使用颜色的应用程序),它的效果也确实令人惊讶地良好。
着色模式在strace
假定为终端的stderr 上输出。如果应用程序重定向其stdout或stderr,则被劫持的strace将继续在终端上写入着色转义序列。
该解决方案有其局限性:
strace
:性能问题,则不能运行其它PTRACE命令状strace
或gdb
在其中,或的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
做了很多或write
s到其标准输出,所以包装物最终会outputing很多(不可见)转义序列到终端的。strace $CMD | vim -c ':set syntax=strace' -
。
这是我前一段时间做过的概念证明。
它仅适用于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)
。这两个版本都会出现我在回答中提到的问题,即重新排列行并冒着输出混乱的风险(尤其是长行)。
seterr
将必须是独立脚本,而不是函数。