【问题标题】:Count number of nonempty entries in each column of, e.g., comm output计算每列中非空条目的数量,例如 comm 输出
【发布时间】:2019-01-31 17:24:12
【问题描述】:

Unix 命令comm file1 file2 有一个 3 列输出,第一列是 file1 唯一的行,第二列是 file2 唯一的行,第三列是两者共享的行(假设 file1 和 file2 已排序)。它最终看起来像这样:

$ echo -e "alpha\nbravo\ncharlie" > file1
$ echo -e "alpha\nbravo\ndelta" > file2
$ comm file1 file2
                alpha
                bravo
charlie
        delta

如果我想要每列中非空行的数量,有没有一种通用的方法来解析comm 的输出并计算这些?

我知道对于 comm 尤其是我可以运行

for i in {12,23,31}; do comm -$i file1 file2 | wc -l; done

但我很好奇以comm 输出文件为起点的解决方案,以便更好地使用 Unix 命令行。我添加了 标签,因为我预感有一个很好的 awk 解决方案。

【问题讨论】:

  • 您应该在示例输入中包含选项卡,因为这对可能的解决方案有很大的影响(我实际上不认为一个强大的解决方案是可能的,但我还没有考虑过)。还包括预期的输出。

标签: awk bash unix awk


【解决方案1】:

另一个答案涵盖了您使用awk 很好地完成工作的问题,但还值得一提的是,GNU 版本的 comm 有一个--total 选项,它将以类似的方式打印每列的总和方式。

【讨论】:

【解决方案2】:

你可以使用这个awk:

comm file1 file2 |
awk -F '\t' -v OFS='\n' '{ if ($1=="") if ($2=="") c3++; else c2++; else c1++ }
END { print c3, c2, c1 }'

2
1
1

请注意,comm 的输出是用制表符分隔的:

  • 公共行中的第一和第二个空列
  • file2 独有的行中的第一个空列
  • file1 独有的行中的第一个非空列

【讨论】:

  • 如果任何输出行包含制表符(由于输入行包含制表符),这将失败。
  • 如果任何输入文件是制表符分隔或其中有一个制表符,这仍然会失败。
  • @kvantour;不,只要行不以制表符开头,情况就不是这样
  • 感谢@EdMorton,它更短且更易于阅读(已编辑)
  • 您的逻辑是合理的,但正如您所指出的,如果一行以 开头,则会失败。 (+1 的逻辑真棒)
【解决方案3】:

这个问题很有趣,但并不像人们想象的那么简单,尤其是在您没有--total 选项的情况下。

关于comm的几点说明:

  1. comm 适用于排序的文件
  2. 如果一行在file1 中出现n 次,在file2 中出现mn 次,comm 将在第 2 列输出 n-m 个条目,在第 3 列输出 n 个条目。

    $ comm <(echo -e "1\n2\n3") <(echo "2\n2\n3\n4")
    1
                    2
            2
                    3
            4
    
  3. comm 使用 -character 作为默认分隔符,如果您的输入包含此字符,则处理其输出会出现问题。

    $ comm  <(echo -e "1\t2\n3") <(echo "2\n3\n4")
    1       2             << this is the weird line
            2
                    3
            4
    

    幸运的是,它有一个定义分隔符的选项 (--output-delimiter=STR)

  4. comm 仅在其他非空字段后添加分隔符

    $ comm  --output-delimiter=SEP <(echo -e "1\n2\n3") <(echo "2\n3\n4")
    1             << NO SEP (1 field)
    SEPSEP2       << TWO SEP (3 fields)
    SEPSEP3       << TWO SEP (3 fields)
    SEP4          << ONE SEP (2 fields)
    

我们现在该如何解决:

我们显然不应该使用 ASCII 符号作为分隔符,这在处理 ASCII 文件时会出现问题,所以您可以做的是使用不可打印的字符作为分隔符。例如,您可以使用带有八进制值 \001 字符(它不接受 字符)。这通常可以解决您可能因第 (3) 点而遇到的问题

$ comm  --output-delimiter=$'\001' <(echo -e "1\t2\n3") <(echo "2\n3\n4")

这个输出现在可以通过管道传输到一个非常简单的awk

$ awk -F "\001" '{a[NF]++}END{print a[1],a[2],a[3] }'

由于第 (4) 点,上述工作有效。

所以你可以这样做:

$ comm  --output-delimiter=$'\001' file1 file2 \
  | awk -F "\001" '{a[NF]++}END{print a[1],a[2],a[3] }'

但我没有 --output-delimiter 选项: 这需要纯 awk 解决方案。我们跟踪 3 个数组。 a 用于 file1 b 用于 file2c 用于组合。 (c 跟踪所有条目)。我们确保将第 (2) 点考虑在内。

$ awk '(NR==FNR) { a[$0]++; c[$0]++ }
       (NR!=FNR) { b[$0]++; c[$0]-- }
       END { for(i in c) {
                if      (c[i] <  0) { countb+=-c[i]; countc+=a[i] }
                else if (c[i] == 0) {                countc+=a[i] }
                else                { counta+= c[i]; countc+=b[i] }
             }
             print counta, countb, countc
       }' file1 file2

我们基本上可以去掉数组b,因为它可以派生自ac,但我想让它更清楚一点,它是如何工作的;另一个版本是:

$ awk '(NR==FNR) { a[$0]++; c[$0]++; next } { c[$0]-- }
       END { for(i in c) {
               counta+=(c[i]>0 ? c[i] : 0)
               countb-=(c[i]<0 ? c[i] : 0)
               countc+=a[i] - (c[i]>0 ? c[i] : 0)
             }
             print counta, countb, countc
       }' file1 file2

【讨论】:

  • 很好地使用--output-delimiter,我猜gnu 版本上可以使用
【解决方案4】:

使用 Perl

$  comm file1 file2 | perl -lne ' /^\t\t/ and $kv{2}++; /^\t\S+/ and $kv{1}++; /^\S+/ and $kv{3}++; END { print "col-$_:$kv{$_}" for(keys %kv) } '
col-3:1
col-1:1
col-2:2

$

$ comm file1 file2 | perl -lne ' /(^\t\t)|(^\t\S+)|(^.)/ and $x=$+[0]>2?3:$+[0]; $kv{$x}++; END { print "col-$_:$kv{$_}" for(keys %kv) } '
col-3:1
col-1:1
col-2:2

$

在哪里 col-1 -&gt; first file

col-3 -&gt; second file

col-2 -&gt; both file

【讨论】:

    【解决方案5】:

    显然,您可以在 awk 中完成所有操作,而无需 comm 或需要排序输入。

    $ awk 'NR==FNR {a[$1]; next} 
                   {if($1 in a) {c3++; delete a[$1]} 
                    else c2++} 
               END {print length(a),c2,c3}' file1 file2
    
    1 1 2
    

    这仅适用于 file1、仅 file2 和 common。

    注意,这要求每个文件中的记录都是唯一的。

    【讨论】:

    • 这只有在文件中的行是唯一的时才能正常工作。 comm 的运作方式不同。
    猜你喜欢
    • 2015-07-10
    • 2016-02-27
    • 2011-05-24
    • 2021-09-18
    • 2020-08-27
    • 1970-01-01
    • 2020-12-06
    • 2011-11-05
    相关资源
    最近更新 更多