【问题标题】:Filtering Using GREP使用 GREP 过滤
【发布时间】:2021-09-14 03:55:12
【问题描述】:

问题就像 找出数字大于等于 m 但小于 n 的名字。 给出了一个“.csv”文件。最好使用 grep (regex) 来解决这个问题。


我会这样:

cat abc.csv|cut -f 3,7 -d ","|grep "4[4-9][0-9]*"|head

但它给了我不想要的东西

注意第 3 列是人名,第 7 列是这些人的相应人数。

任何解决此问题的建议都会非常有帮助。


【问题讨论】:

  • 1) 你需要4[0-9][0-9] 来匹配400499 2) 使用,4[0-9][0-9](注意逗号),这样匹配就不会在列中的任何地方进行
  • 感谢 Sundeep。有用。你能说一下那个逗号的作用吗?如果我只想显示第 3 列的唯一值,我该怎么办?
  • 欢迎来到 SO,感谢您分享您的努力。请在您的问题中添加您的输入和预期输出示例,以使其更清晰。
  • @NobitaNobi,您能否在问题中以文本形式更新输入和预期输出示例,不鼓励使用图像/链接。谢谢。
  • 如果您 edit 您的问题包括 minimal reproducible example 简洁、可测试的样本输入和预期输出,那么我们可以为您提供帮助。

标签: regex awk grep cut


【解决方案1】:
Some people, when confronted with a problem, think "I know,
I'll use regular expressions." Now they have two problems.

(参考https://blog.codinghorror.com/regular-expressions-now-you-have-two-problems/)。

这不是如何使用 grep 的一个很好的例子,因为有据可查的是,使用正则表达式进行数字比较是一种比仅比较数字更加困难和脆弱的方法,例如使用awk,并且当您的数据位于特定字段中时在一行上使用grep也比使用理解字段的工具更困难和脆弱,例如再次awk

测试字段内容是否在数字范围内的正确方法是仅对该字段进行数字比较:

awk -F, '(440<=$7) && ($7<500){print $3}' abc.csv

我根据您在问题中尝试的正则表达式猜测您希望范围具有的值,如果我猜错了,只需更改它们。

我从其他一些答案中看到,您不想打印 $7 包含 . 的行,或者您可能只想要 $7 是整数的行。如果是这样,那么使用正则表达式进行测试是一件微不足道且合适的事情:

awk -F, '($7 !~ /\./) && (440<=$7) && ($7<500){print $3}' abc.csv

或:

awk -F, '($7 ~ /^[0-9]+$/) && (440<=$7) && ($7<500){print $3}' abc.csv

希望您将来能看到与尝试使用 grep 跨行使用正则表达式做同样的事情相比,它是多么清晰、简单、健壮和易于修改。

【讨论】:

    【解决方案2】:

    试试:

    cut -d, -f 3,7 Bulk.csv | grep ',4[0-9][0-9][^0-9]' | cut -d, -f 1
    

    说明:cat 不是必需的。表达式[^0-9] 表示除数字之外的所有内容;仅使用,4[0-9][0-9] 作为正则表达式还会选择包含小数点前位数较多的数字的行,例如4247.14,这不是您想要的。

    我们错过了您的输入文件Bulk.csv 的样本以重现您的问题。

    【讨论】:

      【解决方案3】:

      如果您只需要名称,则必须添加:

      cut -f 1 -d ","

      如果您只需要 400.00 和 499.99 之间的实数(我从您的结果中看到),那么 grep 应该是:

      grep "4[0-9][0-9]\.[0-9][0-9]"

      如果您需要接受任意数量的小数和整数,并注意可选的尾随空格和行尾 ($),您可以使用:

      grep -E "4[0-9][0-9](\.[0-9][0-9]*)* *$"

      如果您需要确保它不匹配 1400 或包含 400 的名称,那么您应该使用:

      grep -E " *, *4[0-9][0-9](\.[0-9][0-9]*)* *$"

      我们可以继续,但我会在这里停下来。 我的建议是使用这个:

      cat Bulk.csv | cut -f 3,7 -d "," | grep -E " *, *4[0-9][0-9](\.[0-9][0-9]*)* *$" | cut -f 1 -d ","

      【讨论】:

      • 谢谢,但类似 ​​4214.00 的数字也会通过这个正则表达式。我不想要的
      • 你说得对,我们必须添加行尾。
      • 如果您想避免使用 awk 并使用整数而不是实数,那么您可以将名称和数字作为参数传递给 shell 脚本并使用 shell 测试函数来获得结果。测试将类似于: [ $7 -gt 400 ] && [ $7 -lt 500 ] && echo $3
      【解决方案4】:

      使用模式打印第 3 列中的值,其中第 7 列在 400-499 范围内,只有 awk,而不是通过多个程序进行管道传输。

      ^4[0-9][0-9]$ 模式使用锚点 ^$ 来防止部分匹配,并使用 2 个范围 0-9 来匹配 400 到 499。

      awk -F, '
      $7 ~ /^4[0-9][0-9]$/ {
        print $3
      }
      ' abc.csv
      

      如果你可以使用gnu grep,你可以匹配第3个字段的值,如果第7个字段在400-499范围内,但它是一个长模式,我建议使用awk。

      ^(?:[^,]*,){2}\K[^,\n]+(?=(?:,[^,\n]*){3},\s*4[0-9][0-9](?=\s*,|$))
      
      • ^ 字符串开始
      • (?:[^,]*,){2} 匹配前 2 个逗号分隔的字段
      • \K忘记到目前为止匹配的内容
      • [^,]+匹配第三个字段
      • (?= 积极的前瞻断言
        • (?:,[^,\n]*){3},\s*4[0-9][0-9](?=\s*,|$) 将第 7 个字段匹配为介于 400-499 之间的值,后跟逗号或字符串结尾以防止部分匹配
      • ) 关闭前瞻

      查看regex demo

      例如

      grep -oP "^(?:[^,]*,){2}\K[^,]+(?=(?:,[^,]*){3},\s*4[0-9][0-9](?=\s*,|$))" abc.csv
      

      【讨论】:

        猜你喜欢
        • 2013-04-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-02-05
        • 2014-04-23
        • 1970-01-01
        • 2013-12-11
        • 1970-01-01
        相关资源
        最近更新 更多