【问题标题】:How do I filter tab-separated input by the count of fields with a given value?如何通过具有给定值的字段计数过滤制表符分隔的输入?
【发布时间】:2016-09-23 20:51:36
【问题描述】:

我的数据(标签分隔):

1   0   0   1   0   1   1   0   1
1   1   0   1   0   1   0   1   1
1   1   1   1   1   1   1   1   1
0   0   0   0   0   0   0   0   0
...

我怎样才能准确地 grep 行,例如 5 '1', 理想输出:

1   0   0   1   0   1   1   0   1

另外,我如何 grep 等于或多于 (>=) 5 '1's 的行, 理想输出:

1   0   0   1   0   1   1   0   1
1   1   0   1   0   1   0   1   1
1   1   1   1   1   1   1   1   1

我试过了,

grep 1$'\t'1$'\t'1$'\t'1$'\t'1

但是这只会输出连续的'1',这不是我想要的。

不知道有没有什么简单的方法可以实现,谢谢!

【问题讨论】:

  • 正则表达式用于匹配模式,而不是用于计数。
  • awk '{for(i=1;i<=NF;i++) {if(i==5{sum+=$i}}}if(sum==5)print $0}' file 未经测试,但接近解决方案。祝你好运。
  • ^^ (i==5) 在这里是错误的。加上一个缺少的括号,但我猜是一个错字。

标签: bash csv awk sed grep


【解决方案1】:

John Bollinger's helpful answeranishane's answer 表明它可以grep 完成,但是,正如已经指出的那样,这非常麻烦,因为常规表达式不是为计数而设计的。

相比之下,

awk 是为基于字段的 解析和计数 构建的(通常结合 与正则表达式来识别字段分隔符,或者,如下所示,字段本身)。

假设您有 GNU awk,您可以使用以下内容:

正好 5 个1s:

awk -v FPAT='\\<1\\>' 'NF==5' file

5 个或更多1s:

awk -v FPAT='\\<1\\>' 'NF>=5' file
  • 特殊变量FPAT 是一个GNU awk 扩展,与标准方法相比,它允许您通过描述字段本身的正则表达式来识别字段使用正则表达式定义字段之间的分隔符(通过特殊变量FS 或选项-F):

    • '\\&lt;1\\&gt;' 根据字边界断言\&lt;\&gt; 将任何“孤立的”1(由非单词字符包围)标识为一个字段; \ 必须在此处加倍,以便 awk 执行的初始 string 解析不会“吃掉”单个 \s。
  • 标准变量NF 包含当前行中输入字段的计数,这样可以轻松进行数值比较。如果条件计算结果为真,则将隐式打印手头的输入行(换句话说:NF==5 隐式与 NF==5 { print } 相同,更详细地说,NF==5 { print $0 })。


符合 POSIX 的 awk 解决方案稍微复杂一点:

正好 5 个1s:

awk '{ l=$0; gsub("[\t0]", "") }; length($0)==5 { print l }' file

5 个或更多1s:

awk '{ l=$0; gsub("[\t0]", "") }; length($0)>=5 { print l }' file
  • l=$0 将输入行 ($0) 以其原始形式保存在变量 l 中。

  • gsub("[\t0]", "") 替换所有 \t0 字符。在带有空字符串的输入行中,即有效地删除它们,并且只留下(直接连接)1 实例(如果有)。

  • length($0)==5 { print l } 然后打印原始输入行 (l),仅当 1s 的结果字符串(即 1s 的计数现在存储在 modified em> 输入行 ($0)) 匹配指定计数。

【讨论】:

    【解决方案2】:

    您可以使用grep。但这将是对正则表达式的滥用。

    $ cat countme
    1   0   0   1   0   1   1   0   1
    1   1   0   1   0   1   0   1   1
    1   1   1   1   1   1   1   1   1
    0   0   0   0   0   0   0   0   0
    
    $ grep -P '^[0\t]*(1[0\t]*){5}[0\t]*$' countme # Match exactly 5
    1   0   0   1   0   1   1   0   1
    
    $ grep -P '^[0\t]*(1[0\t]*){5,}[0\t]*$' countme # Match >=5
    1   0   0   1   0   1   1   0   1
    1   1   0   1   0   1   0   1   1
    1   1   1   1   1   1   1   1   1
    

    【讨论】:

    • 应该算数吗?
    【解决方案3】:

    您可以这样做以获得正好有五个“1”的行:

    grep '^[^1]*\(1[^1]*\)\{5,5\}[^1]*$'
    

    您可以将其简化为至少五个“1”:

    grep '\(1[^1]*\)\{5,\}'
    

    枚举量词 (\{n,m\}) 使您可以方便地为子模式指定连续匹配的特定数量或数量范围。但是,为避免将带有额外匹配项的行与此类模式匹配,您还必须将其锚定到行的开头和结尾。

    另一个技巧是确保第一个 1 之前、1s 之间和最后一个 1 之后的间隙匹配。在您的情况下,所有这些间隙都可以非常简单地表示为除1 之外的零个或多个字符的范围:[^1]*。将这些部分放在一起就可以得到上面的正则表达式。

    【讨论】:

    • “正好五个”只是\{5\},不需要\{5,5\},是吗?
    • @BenjaminW.,两种形式都有效。我使用了\{5,5\},因为它更能说明一般的枚举量词。实际上,我也可以在第二个示例中使用 \{5\},因为该模式没有锚定。
    【解决方案4】:

    sed -nE '/^([^1]*1[^1]*){5}$/p' your_file
    

    恰好匹配 5 次,并且

    sed -nE '/^([^1]*1[^1]*){5,}$/p' your_file
    

    5 场或更多场比赛。


    注意:在 GNU sed 中,您可能在手册页中看不到 -E 选项,但它是受支持的。使用-E 是为了移植到Mac OSX 等。

    【讨论】:

      【解决方案5】:

      perl

      $ perl -ane 'print if (grep {$_==1} @F) == 5' ip.txt 
      1   0   0   1   0   1   1   0   1
      
      $ perl -ane 'print if (grep {$_==1} @F) >= 5' ip.txt 
      1   0   0   1   0   1   1   0   1
      1   1   0   1   0   1   0   1   1
      1   1   1   1   1   1   1   1   1
      
      • -a 自动将输入行拆分为空格并保存到 @F 数组
      • grep {$_==1} @F 返回数组,其中包含来自 @F 数组的元素完全等于 1
      • (grep {$_==1} @F) == 5 在标量上下文中,将根据数组元素的数量进行比较
      • 有关-ane 选项的详细信息,请参阅http://perldoc.perl.org/perlrun.html#Command-Switches

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-04-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-03-06
        相关资源
        最近更新 更多