你如何制作一个 shell 函数,你可以通过管道将其输出为红色?

How do you make a shell function that you can pipe into to make the output red?

我想要一些我可以做的事情 {some command} | red 它会输出 {some command} 会输出的任何内容,但红色。我能做的最好的是

function red()
{
    while read text
    do
        echo -e '3[31m'$text'3[0m'
    done
}

但这会删除所有缩进。

这似乎应该很容易,但我似乎无法弄清楚。我只是为了好玩而尝试这样做,但现在我只需要知道,因为必须有一种我所缺少的简单方法来做到这一点。

编辑:

我也在 C 中这样尝试过:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    char* buffer = malloc(8);
    int size     = 8;
    int index    = 0;

    while ((buffer[index++] = getchar()) != EOF)
    {
        if (index == size)
        {
            size *= 2;
            buffer = realloc(buffer, size);
        }
    }
    buffer[index] = 0;

    printf("%s%s%s", "\033[31m", buffer, "\033[0m");
}

但是 shell 不解释转义字符,因此不会使输出变为红色。

它只缺少一些东西才能工作:

  • 首先你要中和I内部FS分离器IFS 环境变量,因为它是你丢弃缩进的原因。此变量导致读取操作将前导制表符和空格作为字段分隔符。
  • 接下来您要read -r-r 标志阻止转义字符的解释,而是读取 raw。
  • 最后,千万不要用echo -e。这是打印 \ 转义字符的可怕的非标准方式。请改用 printf
  • 此外,read 上的循环将不会处理文本的最后一行,如果它不包含换行符 \n。本例中的 shell read returns false 在读取剩余文本后,跳出 while 循环而不处理最后一行不以 a 结尾的行换行符。
    如果 line 包含最后一行文本并在没有换行符的情况下打印它,用于测试这种特殊的最后一种情况。

现在一切都已修复并按您的预期工作:

#!/usr/bin/env bash

function red() {
  while IFS= read -r line; do
    printf '\e[31m%s\e[0m\n' "$line"
  done
  [ -n "$line" ] && printf '\e[31m%s\e[0m' "$line"
}

red

编辑:

一样,您可以在red函数中使用cat只在整个文本前后插入ANSI CSI序列,而不是每行文本。它不需要 while 循环,因此无疑会更高效。尽管请注意,如果文本流本身包含更改终端颜色的 ANSI CSI 序列,则每行方法可能会提供更好的结果。

red() {
  printf '\e[31m'
  cat
  printf '\e[0m'
}

显式编写转义码在我看来很麻烦。我会做类似

的事情
red() {
  # Send stdin to stdout, but coloured in red, if the
  # terminal supports it.
  if tput setaf 196 2>/dev/null
  then
    cat
    tput sgr0 # reset the attribute
  else
    # No support for changing the colour. Just display it
    # using the default foreground colour
    cat
  fi
}

也适用于未配置颜色的终端。

神奇的数字196来自thistable.