题 如何设置STDOUT和STDERR的字体颜色


我想区分终端中的STDOUT和STDERR消息。 如果脚本或命令在终端中打印消息,我想按颜色区分;可能吗?

(例如,stderr字体颜色为红色,stdout字体颜色为蓝色。)

示例(使用粗体):

$date
Wed Jul 27 12:36:50 IST 2011

$datee
bash: datee: command not found

$alias ls
alias ls='ls --color=auto -F'

$aliass ls
bash: aliass: command not found


32
2017-07-27 07:57


起源


我怀疑这是可能的。也许修补bash源..但即使这不是微不足道的。 - Karoly Horvath
不,本身不可能。 这里 虽然是一个服务器故障问题的链接,它解决了它与黑客。 - TC1
-1因为你接受了hack,它有问题,而不是russ的优秀解决方案。 - Heath Hunnicutt
这里有更多颜色选择: stackoverflow.com/a/12827233/1120248 - Aaron J Lang
@HeathHunnicutt - 谁是russ?那怎么能接受一个不属于他/她的问题的答案呢? - jmort253


答案:


这是我想到的一个黑客,它似乎工作:

鉴于以下别名的可读性:

alias blue='echo -en "\033[36m"'
alias red='echo -en "\033[31m"'
alias formatOutput='while read line; do blue; echo $line; red; done'

现在,您需要首先将终端中的字体颜色设置为红色(默认情况下,将用于stderr)。 然后,运行您的命令并通过stdout管道 formatOutput 上面定义的(它只是将每一行打印为蓝色,然后将字体颜色重置为红色):

shell$ red
shell$ ls / somenonexistingfile | formatOutput

上面的命令将在stderr和stdout中打印,你会看到线条的颜色不同。

希望这可以帮助


更新:

为了使这个可重用,我把它全部放在一个小脚本中:

$ cat bin/run 
#!/bin/bash
echo -en "\033[31m"  ## red
eval $* | while read line; do
    echo -en "\033[36m"  ## blue
    echo $line
    echo -en "\033[31m"  ## red
done
echo -en "\033[0m"  ## reset color

现在您可以在任何命令中使用它:

$ run yourCommand

22
2017-07-27 11:52



使用管道时,如果第二个命令错误,则意味着它以蓝色打印,而不是红色。示例运行ps |格雷普跑 - ungalnanban
您需要调用每个命令 run <cmd>。在你的例子中, grepp 不是通过调用 run。尝试这个: run ps | run grepp run。 - Costi Ciudatu
@ungalnanban:如果你真的想要执行 ps | grepp run通过 run 脚本,你只需要将它放在引号中: run "ps | grepp run"。 - Costi Ciudatu
请注意此示例中的颜色不匹配 run "while true; do (echo a; echo b 1>&2) done"。另请参阅C解决方案下面的注释。 - Stéphane Gimenez
@StéphaneGimenez:是的,你是对的。我确实说过这只是一个黑客攻击。另外,我发现的另一个缺点是,与没有使用相同的命令相比,首先会显示错误消息 run 脚本。但是,您有我的upvote解决方案。 - Costi Ciudatu


在bash shell或脚本中创建一个函数:

color()(set -o pipefail;"$@" 2>&1>&3|sed $'s,.*,\e[31m&\e[m,'>&2)3>&1

像这样用它:

$ color command -program -args

它将显示命令 stderr 红色的。

继续阅读以了解其工作原理。此命令演示了一些有趣的功能。

  • color()...  - 创建一个名为color的bash函数。
  • set -o pipefail  - 这是一个shell选项,用于保存输出通过管道输入另一个命令的命令的错误返回码。这是在子shell中完成的,子shell由括号创建,以便不更改外壳中的pipefail选项。
  • "$@"  - 作为新命令执行函数的参数。 "$@" 相当于 "$1" "$2" ...
  • 2>&1  - 重定向 stderr 的命令 stdout 这样它就变成了 sedstdin
  • >&3  - 简写 1>&3,这重定向 stdout 到一个新的临时文件描述符 33 被送回去了 stdout 后来。
  • sed ...  - 由于上面的重定向, sedstdin 是个 stderr 执行的命令。它的功能是用颜色代码包围每一行。
  • $'...' 一个bash结构,使它能理解反斜杠转义的字符
  • .*  - 匹配整条线。
  • \e[31m  - ANSI转义序列,导致以下字符为红色
  • &  - sed 替换扩展为整个匹配字符串的字符(在本例中为整行)。
  • \e[m  - 重置颜色的ANSI转义序列。
  • >&2  - 简写 1>&2,这重定向 sedstdout 至 stderr
  • 3>&1  - 重定向临时文件描述符 3 回到 stdout

45
2018-04-23 20:54



我希望我可以给你一个巨大的赏金。 +1 - Heath Hunnicutt
+1我必须承认,你的答案比我的好(但我不能转让它的接受)。你的传奇是我穿上T恤的东西。 :) - Costi Ciudatu
@ killdash9 - 感谢您分享脚本。我发现它在交错时始终交换一些stdout和stderr的顺序。对此有简单的解决方法吗?如果我找到一个,我会在这里张贴一个。 - Davorak
@Davorak - 简短的回答是否定的。如果你解决了这个问题,请告诉我。 - killdash9
镰刀似乎在下面解决了它 - P i


我通过将文件描述符链接到自定义函数来为stderr red着色,该函数为通过它的所有内容添加颜色。添加到您的关注 .bashrc

export COLOR_RED="$(tput setaf 1)"
export COLOR_RESET="$(tput sgr0)"

exec 9>&2
exec 8> >(
    perl -e '$|=1; while(sysread STDIN,$a,9999) {print 
"$ENV{COLOR_RED}$a$ENV{COLOR_RESET}"}'
)
function undirect(){ exec 2>&9; }
function redirect(){ exec 2>&8; } 
trap "redirect;" DEBUG
PROMPT_COMMAND='undirect;'

那么发生了什么?调试陷阱在执行命令之前和之后立即执行。 标准错误 因此在执行命令之前重定向以启用红色输出。 PROMPT_COMMAND 在显示提示之前评估并使用此我恢复 标准错误到正常状态。这是必要的,因为 PS1 和 PS2 (你的提示)打印出来 标准错误 我不想要红色提示。瞧,红色输出结束了 标准错误


11
2018-01-23 22:28



感谢您发布此内容。我找到了 serverfault.com/questions/59262/bash-print-stderr-in-red-color 。 - phyatt
关于非换行终止线的问题,不幸的是我不明白为什么会发生这种情况。我开了一个新问题 unix.stackexchange.com/questions/367636/... - Blauhirn
@Blauhirn我刚看完那个帖子,很难跟踪这些答案如何融入你的上述功能。如果您能够解决换行问题,您是否介意更新此答案? - Luke Davis
@LukeDavis在这里,由于链接问题的答案,我想出了两个可能的解决方案,其中第二个更容易阅读: pastebin.com/QQQFTP4X 和 pastebin.com/n1n9PcDD。我没有彻底测试过他们中的任何一个。在这个帖子中看到病人的答案。 stderrred可能是要走的路。 - Blauhirn
@Blauhirn感谢您创建功能!我已经更新了我的答案。 - gospes


你应该看看stderred: https://github.com/sickill/stderred


7
2017-12-13 21:47



mywiki.wooledge.org/BashFAQ/037 建议使用tput而不是明确地编写转义序列...而且我不能在OS X Mavericks上使用它,可能会出错?它只有两条线......还有:干得好!我迫不及待想要让它发挥作用 - P i
我更新了自述文件,thx。 - sickill
echo -e不是必需的: stackoverflow.com/questions/21367236/... - P i


是的,这本身是不可能的。你必须破解tty管理(在内核中)。

在看到其他答案之前,我以某种方式完成了一些小C包装:-) 可能是马车,并且值是硬编码的,除了测试之外不要使用它。

#include "unistd.h"
#include "stdio.h"
#include <sys/select.h>

int main(int argc, char **argv)
{

char buf[1024];
int pout[2], perr[2];
pipe(pout); pipe(perr);

if (fork()!=0)
{
    close(1); close(2);
    dup2(pout[1],1); dup2(perr[1],2);
    close(pout[1]); close(perr[1]);
    execvp(argv[1], argv+1);
    fprintf(stderr,"exec failed\n");
    return 0;
}

close(pout[1]); close(perr[1]);

while (1)
{
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(pout[0], &fds);
    FD_SET(perr[0], &fds);
    int max = pout[0] > perr[0] ? pout[0] : perr[0];
    int v = select(max+1, &fds, NULL, NULL, NULL);
    if (FD_ISSET(pout[0], &fds))
    {
        int r;
        r = read(pout[0], buf, 1024);
        if (!r) {close(pout[0]); continue;}
        write(1, "\033[33m", 5);
        write(1, buf, r);
        write(1, "\033[0m", 4);
    }
    if (FD_ISSET(perr[0], &fds))
    {
        int r;
        r = read(perr[0], buf, 1024);
        if (!r) {close(perr[0]); continue;}
        write(2, "\033[31m", 5);
        write(2, buf, r);
        write(2, "\033[0m", 4);
    }

    if (v <= 0) break;
}

return 0;
}

编辑: 与shell解决方案相比,这个解决方案将更频繁地保留行/字符的顺序。 (它不可能像直接读取一样准确。)点击^ C不会显示丑陋的错误消息,并且在此示例中它的行为正确:

./c_color_script sh -c "while true; do (echo -n a; echo -n b 1>&2) done"

4
2017-07-27 12:40





我很惊讶没有人真正想出如何为stdio流着色。这将为整个(子)shell为stderr red着色:

exec 3>&2
exec 2> >(sed -u 's/^\(.*\)$/'$'\e''[31m\1'$'\e''[m/' >&3)

在这种情况下, &3 将保留原始的stderr流。

你不应该传递任何命令 exec,只有重定向。这种特殊情况导致 exec 用它接收的那些替换当前(子)shell的stdio流。

有几点需要注意:

  • 以来 sed 将在并行子shell中持续运行,在写入彩色stdio之后立即输出任何直接输出都可能会失败 sed 到了 tty
  • 该方法使用FIFO文件描述符; FIFO节点只处理行。如果您没有为流写入换行符,则会缓冲输出,直到遇到换行符。这不是缓冲 sed部分:这些文件类型的功能如何。

警告中最麻烦的是第一个,但即使使用默认颜色,也可以通过对所有输出应用类似处理来或多或少地避免竞争条件。

您可以通过管道连接到单个命令来执行类似的处理 sed 命令与普通管道操作员(|)。管道链是同步执行的,因此不会发生竞争条件,但管道链中的最后一个命令默认接收自己的子shell。


3
2017-07-08 15:45



我真的很喜欢这种不需要调用特殊命令的方式,它只是强制所有输出都是正确的颜色。不幸的是,缓冲问题使我甚至无法看到我在bash提示符下输入的内容(直到我点击Enter后才会出现)。有什么方法可以解决这个问题吗? - robru
@Robru您可能能够使用不逐行读取的程序,但假设每个字符都发送EOD信号,我不确定它是否会这样做。你可以创建一个输入脚本,如果你有野心,但你将失去readline支持。 - Zenexer
至少在我的系统上,行缓冲 是 赛德的错。尝试 exec 2> >(cat >&3),它应该是无缓冲的。该 stdbuf 命令也可以在这里发挥作用。 - Score_Under