如何隐藏 fmt 中的 ANSI 颜色转义码

6
我使用(GNU) fmt 格式化较长的文本,使其具有漂亮的(“最优”)断行方式。然而,如果文本包含任何 ANSI 颜色转义序列(这些序列从不显示,只用于在显示文本时着色),fmt 将将其视为普通字符,并计算错误的行长度。
我不确定在此处字面转义字符的效果如何,因此这里使用 grep 生成 ANSI 序列的简单示例。让我们从一个长字符串开始格式化。
string="Here’s an example of a rather long \
string with quite a few words in the middle \
that grep chooses to colour red."

如果我们不突出显示 grep 匹配项,一切都运行良好:
echo $string | grep --color=no i | fmt -w 50

但是如果我们对它们进行高亮/着色,fmt 会认为包含字母'i'的行比实际更长,在终端中显示时会显示为相当短的行。

echo $string | grep --color=yes i | fmt -w 50

有没有办法避免这种情况?对于这个例子,当然可以在grep之前使用fmt,但是当搜索字符串跨越多个单词时,这种方法就不起作用了。

4个回答

5

使用grep和fmt似乎没有一个好的解决方法。我建议您先运行fmt,然后使用sed而不是grep进行搜索。例如:

echo 搜索字符串将会被突出显示为红色。 | fmt -w 50 | sed ":a;$!N;$!ba;s/search[ \n]string/\x1b\[1;31m&\x1b\[0m/g"

谢谢。这就是我最终使用的解决方案。我的实际应用实际上涉及到 sed 而不是 grep。基本上,我将标签(如 this is an |example| string 中的 |)转换为 ANSI 颜色序列以着色它们跨越的单词。在替换之前使用 fmt 并不是最佳选择,因为标签会占用一些空间,影响了自动换行,但除非一行包含很多这样的标签,否则情况并不太糟糕,而且比替换后运行 fmt 好(因为 ANSI 转义序列占用更多字符)。 - Karl Ove Hufthammer

1
你可以使用hmt来实现这一点。它正是为这种情况而设计的。
注意:我是hmt的作者。

0

使用 `grep --colour=auto' 可以解决这个问题。


不会。在这个例子中使用grep --colour=auto与使用grep --colour=no的结果相同。 - Karl Ove Hufthammer

0
放弃使用fmt,改用GNU的sed脚本代替:

afmt.sed

#!/usr/bin/env -S sed -Enf

:0
# insert break after 79 visible characters (skipping ANSI sequences)
s/^((\x1B\[[ -?]*[@-~])*[^\x1B]){79}(\x1B\[[ -?]*[@-~]|[[:blank:]])*/\0\n/
# if line isn't long enough to be broken, skip to end
Tx

# if break was inserted mid-word, move it to preceding opportunity   
s/^(((\x1B\[[ -?]*[@-~])*.)+([[:blank:]]|[^[:blank:]]-))(.*)\n/\1\n\5/m

# trim trailing blanks, moving embedded ANSI sequences onto continuation line
:1
s/[[:blank:]]+((\x1B\[[ -?]*[@-~])*)\n/\n\1/
t1

# print finished line
P

# scrub ANSI sequences from leading indentation
:2
s/^([[:blank:]]*)(\x1B\[[ -?]*[@-~])+/\1/
t2

# delete finished line, keeping indentation for continuation line
s/^([[:blank:]]*).*\n/\1/m

# loop to process continuation line
t0

:x
p

特点

  • 在可能的情况下,在空格或连字符后换行,但如果不可避免,会在一行上换行一个长的第一个单词。
  • 修剪换行后的尾随空格,将嵌入在尾随空格中的ANSI序列移动到连续行。
  • 将前导缩进(去除任何嵌入的ANSI序列)复制到连续行。
  • 将制表符字符视为宽度为1,因此如果可能包含制表符,您可能需要先通过expand将文本传递。

Bash别名

您可以在~/.bashrc中将其设置为别名,利用Bash的自动$COLUMNS变量来适应终端的宽度:

alias afmt='sed -Ene '\'':0;s/^((\x1B\[[ -?]*[@-~])*[^\x1B]){'\''"$((${COLUMNS:-80}-1))"'\''}(\x1B\[[ -?]*[@-~]|[[:blank:]])*/\0\n/;Tx;s/^(((\x1B\[[ -?]*[@-~])*.)+([[:blank:]]|[^[:blank:]]-))(.*)\n/\1\n\5/m;:1;s/[[:blank:]]+((\x1B\[[ -?]*[@-~])*)\n/\n\1/;t1;P;:2;s/^([[:blank:]]*)(\x1B\[[ -?]*[@-~])+/\1/;t2;s/^([[:blank:]]*).*\n/\1/m;t0;:x;p'\'

例子

在上面所示的设置Bash别名之后,

string="Here’s an example of a rather long \
string with quite a few words in the middle \
that grep chooses to colour red."

echo $string | grep --color=no  i | ( COLUMNS=50 ; afmt )
echo $string | grep --color=yes i | ( COLUMNS=50 ; afmt )

afmt无论grep的着色如何,都会产生相同的换行效果。

更高级的演示 ;)

N=( {seven,six,fif,for,thir,twen}ty{-{nine,eight,seven,six,five,four,three,two,one},}$'\e[m columns of chars' )
x=' on the screen'
for (( i = 0 ; i < 59 ; ++i )) ; do
  { printf "\n%${i}s"
    echo -e "\e[92;1m${N[i]@u}$x, \e[93;1m${N[i]}. " \
      "You splice one off and pipe it to groff; \e[91;1m${N[i+1]}$x."
  } | sed -Enf afmt.sed
done

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接