如何对n位数但不超过n的组进行grep?


33

我正在学习Linux,但我面临的挑战似乎是我自己无法解决。这里是:

grep文件中的一行,该行连续包含4个数字,但不超过4。

我不确定该如何处理。我可以搜索特定数字,但不能搜索字符串中的数字。


2
是否应1234a12345显示类似的行?
伊利亚·卡根

@佛,您需要解释您的问题以及示例。
Avinash Raj 2014年

如果数字前面有空格或行锚的开头,然后是空格或行锚的末尾,那么您可以简单地使用单词边界。\b\d{4}\b
Avinash Raj 2014年

1
这个问题与正则表达式的某些问题有所不同,因为它明确地涉及grep的用法。关于在Ubuntu中使用Unix实用程序(例如grep,sed和awk)的问题在这里始终被认为是很好的。有时人们会问如何使用错误的工具来完成工作。那么缺少上下文是一个大问题,但这不是这里发生的事情。这是很热门的话题,足够清晰,可以被有用地回答,对我们的社区有帮助,并且阻止进一步的回答或将其推向删除或迁移没有任何好处。我正在投票重新打开它。
伊利亚·卡根

1
非常感谢你们,我不知道我会得到这么多反馈。这是我一直在寻找的答案:grep -E'(^ | [^ 0-9])[0-9] {4}($ | [^ 0-9])'文件。该命令必须能够提取这样的字符串(确实如此):abc1234abcd99999
佛陀

Answers:


52

有两种方法可以解释这个问题。我将解决这两种情况。您可能要显示以下行:

  1. 包含一个四位数的序列,而该序列本身不属于任何更长序列的一部分,或者
  2. 包含四位数的序列,但不再包含数字的序列(甚至不单独)。

例如,(1)将显示1234a56789,但(2)将不显示。


如果要显示包含四位数字的序列的所有行,这些行本身不属于任何更长的数字序列,则一种方法是:

grep -P '(?<!\d)\d{4}(?!\d)' file

这使用Perl正则表达式,Ubuntu grepGNU grep)通过Perl支持-P。它不会12345与之类的文本匹配,也不会与12342345其中的一部分匹配。但它将与1234in相匹配1234a56789

在Perl正则表达式中:

  • \d表示任何数字(这是一个简短的说法[0-9][[:digit:]])。
  • x{4}匹配x4次。({ }语法并非特定于Perl正则表达式;它也通过扩展正则表达式存在grep -E。)因此\d{4}与相同\d\d\d\d
  • (?<!\d)是零宽度的负向后看断言。它的意思是“除非前面有” \d
  • (?!\d)是零宽度的否定超前断言。它的意思是“除非紧随其后\d”。

(?<!\d)并且(?!\d)不匹配四位数序列之外的文本;相反,如果它们是较长数字序列的一部分,则它们(一起使用时)将防止四个数字序列本身被匹配。

仅使用先行查找或仅预读是不够的,因为最右边或最左边的四位数子序列仍将匹配。

使用先后断言和先行断言的一个好处是您的模式仅匹配四位数字本身,而不匹配周围的文本。使用颜色突出显示(带有--color选项)时,这很有用。

ek@Io:~$ grep -P '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
12345abc789d0123e4

在Ubuntu中,默认情况下,每个用户都alias grep='grep --color=auto'在其~.bashrc文件中。因此,当您运行一个简单的命令开头时grep(这是扩展别名时),而标准输出终端(这是检查的内容),则您会自动获得颜色突出显示。比赛通常以红色(接近朱红色)突出显示,但我已将其显示为斜体。这是屏幕截图:--color=auto
屏幕快照显示了该grep命令,输出为12345abc789d0123e4,红色突出显示了0123。

您甚至可以使用以下命令grep打印仅匹配的文本,而不是整行-o

ek@Io:~$ grep -oP '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
0123

替代方法,后顾之忧和前瞻性断言

但是,如果您:

  1. 需要一个将在grep不支持-P或不想使用Perl正则表达式的系统上运行的命令,并且
  2. 不需要专门匹配这四个数字-如果您的目标只是显示包含匹配项的行,那么通常会出现这种情况,并且
  3. 可以使用不太优雅的解决方案

...然后您可以使用扩展的正则表达式来实现:

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

这匹配四个数字,非数字字符(或行的开头或结尾)包围它们。特别:

  • [0-9]匹配任何数字(如[[:digit:]]\dPerl正则表达式中的数字),并{4}表示“四次”。因此[0-9]{4}匹配一个四位数的序列。
  • [^0-9]不在的范围相匹配的字符0通过9。它等效于[^[:digit:]](或\D,在Perl正则表达式中)。
  • ^,当它没有出现在[ ]方括号中时,匹配行的开头。同样,$匹配行尾。
  • |装置与括号里的分组(如在代数)。因此,(^|[^0-9])匹配行的开头或非数字字符,而($|[^0-9])匹配行的结尾或非数字字符。

因此,匹配仅发生在同时包含四位数序列([0-9]{4})的行中:

  • 在该行的开头或前面有一个非数字((^|[^0-9])),并且
  • 在行的末尾或后面跟一个非数字(($|[^0-9]))。

另一方面,如果您要显示所有包含四位数序列但不包含任何四位数以上序列的行(即使是与另一只四位数序列分开的序列),则从概念上讲,目标是找到匹配一种模式但不匹配另一种模式的线。

因此,即使您知道如何使用单个模式,我也建议您使用matt的第二个建议,分别grep为两种模式使用。

在执行此操作时,您不会从Perl正则表达式的任何高级功能中强烈受益,因此您可能不希望使用它们。但是与上述样式保持一致,这是使用(和花括号)代替以下方法简化了Matt的解决方案\d[0-9]

grep -P '\d{4}' file | grep -Pv '\d{5}'

由于使用[0-9],因此matt的方式更具可移植性-它将在grep不支持Perl正则表达式的系统上运行。如果使用[0-9](或[[:digit:]])代替\d,但继续使用{ },则可以更简洁地获得matt方法的可移植性:

grep -E '[0-9]{4}' file | grep -Ev '[0-9]{5}'

替代方式,具有单个模式

如果您确实喜欢使用以下grep命令

  1. 使用单个正则表达式(如上所述,不是两个grep管道分隔的)
  2. 显示至少包含一个四位数序列的行,
  3. 但不包含五个(或更多)数字的序列,
  4. 而且您不介意与整行匹配,而不仅仅是数字(您可能不介意)

...然后您可以使用:

grep -Px '(\d{0,4}\D)*\d{4}(\D\d{0,4})*' file

-x标志grep仅显示整行匹配的行(而不是包含匹配项的任何行)。

我使用了Perl正则表达式,因为在这种情况下,我认为简洁明了\d\D大大提高了清晰度。但是,如果您需要一些可移植到grep不支持系统的系统,则-P可以用[0-9][^0-9](或用[[:digit:]][^[:digit]])替换它们:

grep -Ex '([0-9]{0,4}[^0-9])*[0-9]{4}([^0-9][0-9]{0,4})*' file

这些正则表达式的工作方式是:

  • 在中间,\d{4}[0-9]{4}匹配一个四位数的序列。我们可能有不止一种,但我们至少需要有一种。

  • 在左侧,(\d{0,4}\D)*([0-9]{0,4}[^0-9])*与零个或多个(*)实例(不超过四位数字,后跟一个非数字)匹配。零位数(即为零)是“不超过四位数”的一种可能性。这匹配(a)空字符串或(b)以非数字结尾且不包含任何四位数以上的序列的任何字符串。

    由于紧靠中心\d{4}(或[0-9]{4})左边的文本必须为空或以非数字结尾,因此可以防止中心\d{4}匹配四位数字,而四位数字恰好在其左边有另一个(第五位)数字。

  • 在右侧,(\D\d{0,4})*([^0-9][0-9]{0,4})*匹配零个或多个(*)非数字实例,后跟不超过四个数字(与以前一样,可以是四个,三个,两个,一个,甚至根本没有)。此匹配的(a)的空字符串或(b)中的任何字符串开始在一个非数字的和不含有多于四个数字的任何序列。

    由于紧靠中心\d{4}(或[0-9]{4})右边的文本必须为空或以非数字开头,因此可以防止中心\d{4}匹配四位数字,而四位数字恰好在其右边有另一个(第五位)数字。

这样可确保在某处出现四位数的序列,并且在任何地方都不会出现五位或更多位数的序列。

这样做是不错的,也没有错。但是,考虑使用此替代方法的最重要原因可能是,它阐明了使用替代方法(或类似方法)的好处,如上面和马特答案中所建议。grep -P '\d{4}' file | grep -Pv '\d{5}'

通过这种方式,很明显,您的目标是选择包含一件事而不包含另一件事的行。另外,语法更简单(因此许多读者/维护人员可能会更快地理解它)。


9

这将连续显示4个数字,但不会更多

grep '[0-9][0-9][0-9][0-9][^0-9]' file

注意^表示不是

尽管我不确定如何解决,但是有一个问题……如果数字是该行的末尾,那么它将不会显示。

但是,此较丑的版本适用于这种情况

grep '[0-9][0-9][0-9][0-9]' file | grep -v [0-9][0-9][0-9][0-9][0-9]

哎呀,didnt需要被egrep的-我已经编辑它
马特·

2
第一个是错误的-它找到了a12345b,因为它匹配2345b
Volker Siegel 2014年

0

如果grep不支持perl正则表达式(-P),请使用以下shell命令:

grep -w "$(printf '[0-9]%.0s' {1..4})" file

哪里printf '[0-9]%.0s' {1..4}会产生4倍[0-9]。当您有长数字并且不想重复模式时(只用4要查找的数字替换),此方法很有用。

使用-w将查找整个单词。但是,如果您对字母数字字符串(例如)感兴趣,请在模式末尾1234a添加[^0-9],例如

grep "$(printf '[0-9]%.0s' {1..4})[^0-9]" file

使用$()基本上是命令替换。检查这篇文章,看看如何printf重复模式。


0

您可以通过file在系统中替换为实际的文件名来尝试以下命令:

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

您还可以查看本教程,了解grep命令的更多用法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.