【问题标题】:Color escape codes in pretty printed columns漂亮打印列中的颜色转义码
【发布时间】:2013-12-07 17:32:53
【问题描述】:

我有一个制表符分隔的文本文件,我将它发送到column 以“漂亮地打印”一个表格。

原始文件:

1<TAB>blablablabla<TAB>aaaa bbb ccc
2<TAB>blabla<TAB>xxxxxx
34<TAB>okokokok<TAB>zzz yyy

使用column -s$'\t' -t &lt;original file&gt;,我得到

1  blablablabla aaaa bbb xxx
2  blabla       xxxxxx
34 okokokok     zzz yyy

根据需要。现在我想为列添加颜色。我尝试在原始文件中的每个制表符分隔字段周围添加转义码。 column 成功打印彩色,但列不再对齐。相反,它只是逐字打印 TAB 分隔符。

问题是:我怎样才能让列对齐,同时还具有独特的颜色?

我想了两种方法来实现这一点:

  1. 调整 column 参数以使对齐与颜色代码一起使用
  2. 将列的输出重定向到另一个文件,并对前两个以空格分隔的字段进行搜索+替换(前两列保证包含空格;第三列很可能将包含空格,但不包含 TAB 字符)

问题是,我不知道如何做这两个...

作为参考,这是我传递给column的内容:

请注意,这些字段确实由 TAB 字符分隔。我已经通过od 确认了这一点。

编辑:

着色似乎没有问题。我已经有了上面显示的文件,并且颜色代码可以正常工作。问题是column 在我发送带有转义码的输入后将无法对齐。我正在考虑将没有颜色代码的字段传递给column,然后在每个字段之间复制column输出的确切空格数,并在漂亮的打印方案中使用它。

【问题讨论】:

  • 我只使用“column -t my_file”进行了尝试,没有发现对齐问题。
  • 如果 FIELD3 中有空格,除非您指定不同的分隔符,否则它将不起作用。即便如此,我的前两列没有对齐,即使它们不包含空格。这是column -t my_file的结果:i.imgur.com/w6i1aGn.png
  • 今天必须离开。明天会更深入地研究这个..必须有一个解决方案。 (当然,您可以使用更多 mighty 语言,例如 python 或 perl。但是 awk + ​​column 应该可以工作.. 不知道为什么)
  • 我认为它可能只是我的机器——它似乎对其他人有用。我有一个相当旧的系统:Linux version 2.6.18-238.el5 (mockbuild@x86-012.build.bos.redhat.com) (gcc version 4.1.2 20080704 (Red Hat 4.1.2-50)) #1 SMP Sun Dec 19 14:22:44 EST 2010

标签: bash unix escaping multiple-columns


【解决方案1】:

2021 年更新的 BASH 答案

TL;DR

我真的很喜欢@NORMAN GEIST's answer,但是对于我需要的东西来说太慢了......所以我编写了我自己的脚本版本,这次是用 Perl(stdin 循环和格式化)+ Bash(用于演示/帮助)编写的.

你可以找到完整的代码here

它是综合的:

  • 类似 Bash column 的命令界面(与 -t-s-o 等参数相同)
  • column_ansi --helpcolumn_ansi -h 的详尽帮助

实际的“核心”代码可以分解为only the Perl part

perl -e '
    use strict;
    use warnings;
    
    sub trim_ansi {
        my $__temp_string = $_[0];
        
        # Remove all Control Characters (color codes, carriage returns, bells and other characters)
        $__temp_string =~ s/ \e[ #%()*+\-.\/]. |
            \r | # Remove extra carriage returns also
            (?:\e\[|\x9b) [ -?]* [@-~] | # CSI ... Cmd
            (?:\e\]|\x9d) .*? (?:\e\\|[\a\x9c]) | # OSC ... (ST|BEL)
            (?:\e[P^_]|[\x90\x9e\x9f]) .*? (?:\e\\|\x9c) | # (DCS|PM|APC) ... ST
            \e.|[\x80-\x9f] //xg;
        1 while $__temp_string =~ s/[^\b][\b]//g;  # remove all non-backspace followed by backspace
        
        return $__temp_string
    }
    
    # Environment variables used by the program
    my $INPUT_SEPARATOR = $ENV{"PCOLUMN_INPUT_SEPARATOR"};
    my $OUTPUT_SEPARATOR = $ENV{"PCOLUMN_OUTPUT_SEPARATOR"};
    my $ALIGN_RIGHT = $ENV{"PCOLUMN_ALIGN_RIGHT"};
    
    # Default values for INPUT_SEPARATOR and OUTPUT_SEPARATOR
    if ($INPUT_SEPARATOR eq ""){
        $INPUT_SEPARATOR = " "
    }
    if ($OUTPUT_SEPARATOR eq ""){
        $OUTPUT_SEPARATOR = "  "
    }
    
    # ALIGN_RIGHT must be a single number or a comma-separated list of numbers
    $ALIGN_RIGHT =~ s/^\s+|\s+$//g;
    if ($ALIGN_RIGHT ne "" && not $ALIGN_RIGHT =~ /^[0-9]+(,[0-9]+)*$/) {
        print STDERR "error: undefined column name '\''$ALIGN_RIGHT'\''\n";
        exit 1;
    }
    my @ALIGN_RIGHT = split(/,/, $ALIGN_RIGHT);
    my %ALIGN_RIGHT_HASH = map {$_ - 1 => 1} @ALIGN_RIGHT;
    my @stdin = <STDIN>;
    my $column_widths = [];
    foreach my $line (@stdin) {
        $line =~ s/\r?\n?$//;
        my @columns = split(/\Q$INPUT_SEPARATOR/, $line);
        my $column_index = 0;
        
        foreach my $column (@columns) {
            $column = trim_ansi($column);
            
            if (!defined $column_widths->[$column_index]) {
                $column_widths->[$column_index] = 0;
            }
            
            if ($column_widths->[$column_index] < length($column)) {
                $column_widths->[$column_index] = length($column);
            }
            $column_index++;
        }
    }
    foreach my $line (@stdin) {
        $line =~ s/\r?\n?$//;
        my @columns = split (/\Q$INPUT_SEPARATOR/, $line);
        my $column_index = 0;
        foreach my $column (@columns) {
            my $__current_column_length = length(trim_ansi($column));
            my $__current_column_padding = $column_widths->[$column_index] - $__current_column_length;
            if (exists $ALIGN_RIGHT_HASH{$column_index}) {
                print(" " x $__current_column_padding);
                print($column);
            } else {
                print($column);
                print(" " x $__current_column_padding);
            }
            if ($column_index != $#columns) {
                print($OUTPUT_SEPARATOR);
            }
            $column_index++;
        }
        print("\n");
    }
';

背景

我需要将 非常长 awk 生成的彩色输出(超过 300 行)格式化为一张漂亮的表格。 我首先想到使用column,但我发现它没有考虑ANSI 字符,因为输出会不对齐

在 Google 上搜索了一下后,我在 SO 上找到了 @NORMAN GEIST's interesting answer,它在删除 ANSI 字符后动态计算输出中每一列的宽度,然后构建表格。

一切都很好,但是加载时间太长了(正如有人在 cmets 中指出的那样)......

所以我尝试转换@NORMAN GEIST 的column2bashperl,我的上帝,如果有变化!

在我的生产脚本中试用此版本后,用于显示数据的时间从 30 秒下降到 !!

享受吧!

【讨论】:

  • 非常棒,感谢您的解决方案!
【解决方案2】:

在我的例子中,我想根据列的值选择性地着色列中的值。假设我希望 okokokok 为绿色,blabla 为红色。

我可以这样做(想法是在列 列化之后为列的值着色):

GREEN_SED='\\033[0;32m'
RED_SED='\\033[0;31m'
NC_SED='\\033[0m' # No Color

column -s$'\t' -t <original file> | echo -e "$(sed -e "s/okokokok/${GREEN_SED}okokokok${NC_SED}/g" -e "s/blabla/${RED_SED}blabla${NC_SED}/g")"

或者,使用变量:

DATA=$(column -s$'\t' -t <original file>)

GREEN_SED='\\033[0;32m'
RED_SED='\\033[0;31m'
NC_SED='\\033[0m' # No Color
echo -e "$(sed -e "s/okokokok/${GREEN_SED}okokokok${NC_SED}/g" -e "s/blabla/${RED_SED}blabla${NC_SED}/g" <<< "$DATA")"

记下颜色定义值中的附加反斜杠。它是为 sed 不解释原始反斜杠而设计的。

这是一个结果:

【讨论】:

    【解决方案3】:

    我写了一个 bash 版本的专栏(类似于 util-linux 的专栏),它使用颜色代码:

    #!/bin/bash
    which sed >> /dev/null || exit 1
    
    version=1.0b
    editor="Norman Geist"
    last="04 Jul 2016"
    
    # NOTE: Brilliant pipeable tool to format input text into a table by 
    # NOTE: an configurable seperation string, similar to column
    # NOTE: from util-linux, but we are smart enough to ignore 
    # NOTE: ANSI escape codes in our column width computation
    # NOTE: means we handle colors properly ;-)
    
    # BUG : none
    
    addspace=1
    seperator=$(echo -e " ")
    columnW=()
    columnT=()
    
    while getopts "s:hp:v" opt; do
      case $opt in
    s ) seperator=$OPTARG;;
    p ) addspace=$OPTARG;;
    v ) echo "Version $version last edited by $editor ($last)"; exit 0;;
    h ) echo "column2 [-s seperator] [-p padding] [-v]"; exit 0;;
    * ) echo "Unknow comandline switch \"$opt\""; exit 1
      esac
    done
    shift $(($OPTIND-1))
    
    if [ ${#seperator} -lt 1 ]; then
      echo "Error) Please enter valid seperation string!"
      exit 1
    fi
    
    if [ ${#addspace} -lt 1 ]; then
      echo "Error) Please enter number of addional padding spaces!"
      exit 1
    fi
    
    #args: string
    function trimANSI()
    {
      TRIM=$1
      TRIM=$(sed 's/\x1b\[[0-9;]*m//g' <<< $TRIM); #trim color codes
      TRIM=$(sed 's/\x1b(B//g'         <<< $TRIM); #trim sgr0 directive
      echo $TRIM
    }
    
    #args: len
    function pad()
    {
      for ((i=0; i<$1; i++))
      do 
    echo -n " "
      done
    }
    
    #read and measure cols
    while read ROW
    do
      while IFS=$seperator read -ra COLS; do
    ITEMC=0
    for ITEM in "${COLS[@]}"; do
      SITEM=$(trimANSI "$ITEM"); #quotes matter O_o
      [ ${#columnW[$ITEMC]} -gt 0 ] || columnW[$ITEMC]=0
      [ ${columnW[$ITEMC]} -lt ${#SITEM} ] && columnW[$ITEMC]=${#SITEM}
      ((ITEMC++))
    done
    columnT[${#columnT[@]}]="$ROW"
      done <<< "$ROW"
    done
    
    #print formatted output
    for ROW in "${columnT[@]}"
    do
      while IFS=$seperator read -ra COLS; do
    ITEMC=0
    for ITEM in "${COLS[@]}"; do
      WIDTH=$(( ${columnW[$ITEMC]} + $addspace ))
      SITEM=$(trimANSI "$ITEM"); #quotes matter O_o
      PAD=$(($WIDTH-${#SITEM}))
    
      if [ $ITEMC -ne 0 ]; then
        pad $PAD
      fi
    
      echo -n "$ITEM"
    
      if [ $ITEMC -eq 0 ]; then
        pad $PAD
      fi
    
      ((ITEMC++))
    done
      done <<< "$ROW"
      echo ""
    done
    

    示例用法:

    bold=$(tput bold)
    normal=$(tput sgr0)
    green=$(tput setaf 2)
    
    column2 -s § << END
    ${bold}First Name§Last Name§City${normal}
    ${green}John§Wick${normal}§New York
    ${green}Max§Pattern${normal}§Denver
    END
    

    输出示例:

    【讨论】:

    • 我试过 column2 并且它有效!不幸的是没有列那么快。时间到了:column2 执行了 0.706s/0.599s/0.612s/0.622s,而原始列执行了 0.012s/0.009s/0.009s/0.010s。当用户等待显示信息时,它会有所不同。但是....由于列不适用于颜色转义码,我将继续使用 color2 和 +1 给作者。
    • 太棒了!我可以在我正在从事的开源项目中使用它吗?我刚刚给你发了一封邮件,里面有更多信息:)
    • 这在 GitHub 上可用吗?如果是这样,您应该提供一个链接,我很乐意给它一个星 :)
    • @HudsonSantos 我制作了一个基于 Perl 的 column2 版本,速度更快(列:0m0,059s/0m0,015s/0m0,015s,而我的 0m0,092s/0m0,000s/0m0,000s)。我会把它发布在 Github 上(显然归功于这个答案)并与你们分享
    【解决方案4】:

    使用printf 格式化输出的解决方案:

    while IFS=$'\t' read -r c1 c2 c3; do
        tput setaf 1; printf '%-10s' "$c1"
        tput setaf 2; printf '%-30s' "$c2"
        tput setaf 3; printf '%-30s' "$c3"
        tput sgr0; echo
    done < file
    

    【讨论】:

    • +1 用于tput 命令用法:) .. 但是对齐还不行。你应该通过管道发送到column
    • 仍然无法通过管道连接到 column... 我认为这里的问题是 column 出现颜色代码,而不是颜色本身。
    【解决方案5】:

    我会使用awk 进行着色(sed 也可以使用):

    awk '{printf "\033[1;32m%s\t\033[00m\033[1;33m%s\t\033[00m\033[1;34m%s\033[00m\n", $1, $2, $3;}' a.txt 
    

    并将其通过管道传送到column 以进行对齐:

    ... | column -s$'\t' -t
    

    输出:

    【讨论】:

    • 如何保留column 的间距?从您的 awk 命令来看,它看起来像是一个静态的空格数。
    • 如果我理解,awk 命令将产生与我在上面发布的文件相同的输出...但我已经尝试将其发送到column,但它与转义不正确代码。
    • 不...仍然打印 TAB 而不是对齐。此外,第三个字段中有空格,这只会打印它的第一个单词。
    • 怎么可能?我已经用 your 输入进行了测试?您使用的是哪种终端类型?
    • 你有&lt;tab&gt;这个词作为分隔符,而不是真正的制表符吗?
    猜你喜欢
    • 1970-01-01
    • 2014-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-22
    • 1970-01-01
    相关资源
    最近更新 更多