Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/bash/17.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Awk 计算每列中的非空条目数,例如comm输出_Awk_Bash_Unix - Fatal编程技术网

Awk 计算每列中的非空条目数,例如comm输出

Awk 计算每列中的非空条目数,例如comm输出,awk,bash,unix,Awk,Bash,Unix,Unix命令comm file1 file2有一个3列输出,其中第一列为file1独有的行,第二列为file2独有的行,第三列为file1和file2共享的行(假设file1和file2已排序)。它最终看起来像这样: $ echo -e "alpha\nbravo\ncharlie" > file1 $ echo -e "alpha\nbravo\ndelta" > file2 $ comm file1 file2 alpha

Unix命令
comm file1 file2
有一个3列输出,其中第一列为file1独有的行,第二列为file2独有的行,第三列为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

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

请注意,
comm
的输出由以下大小写分隔:

  • 公共行中的第一个和第二个空列
  • 文件2特有的行中的第一个空列
  • 文件1特有的行中的第一个非空列

另一个答案是关于如何使用
awk
将工作做好的问题,但值得一提的是,GNU版本的comm有一个
--total
选项,该选项将以类似的方式打印每列的总和。

显然,您可以在
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


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

这个问题很有趣,但并不像人们想象的那么简单,特别是如果您没有
--total
选项

关于
通信的几件事:

  • comm
    可用于已排序的文件
  • 如果一行在
    file1
    中出现n次,在
    file2
    中出现m次ncomm
  • 将在第2列中输出n-m个条目,在第3列中输出n个条目

    $ comm <(echo -e "1\n2\n3") <(echo "2\n2\n3\n4")
    1
                    2
            2
                    3
            4
    
    幸运的是,它有一个定义分隔符的选项(
    --output delimiter=STR

  • 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)
    
    该输出现在可以通过管道传输到极其简单的
    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] }'
    
    但是我没有
    --输出分隔符
    选项:这需要纯
    awk
    解决方案。我们跟踪3个阵列<对于
    file1
    b
    对于
    file2
    c
    组合,代码>a
    。(
    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
    
    使用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
    
    $
    

    在哪里
    col-1->第一个文件

    col-3->第二个文件


    col-2->两个文件

    ,如果任何输出行包含制表符(由于输入行包含制表符),则将失败。如果任何输入文件以制表符分隔或其中包含制表符,则仍将失败。@kvantour;不,只要行不以TabThreak@EdMorton开头,情况就不是这样。TabThreak@EdMorton更短,更容易阅读(编辑)。您的逻辑是正确的,但正如您所指出的,如果一行以TabThreak开头,则会失败。(+1表示逻辑的惊人性)您应该在示例输入中包含制表符,因为这对可能的解决方案有很大影响(我实际上不认为一个健壮的解决方案是可能的,但我还没有考虑清楚)。还包括预期的输出。当然,如果您只对总计感兴趣,可以禁用通常的输出:
    comm-123--total file1 file2
    。对于没有--total选项的用户,它是在8.26版中添加的:只有在文件中的行是唯一的情况下,此选项才能正常工作<代码>通信
    的操作方式不同。非常好地使用了
    --输出分隔符
    ,我想这在
    gnu
    版本上是可用的
    $ comm  --output-delimiter=$'\001' file1 file2 \
      | awk -F "\001" '{a[NF]++}END{print a[1],a[2],a[3] }'
    
    $ 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
    
    $ 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
    
    $  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
    
    $