如何grep n个数字组,但不超过n?

Bud*_*dha 42 command-line grep text-processing

我正在学习 Linux,我遇到了一个我自己似乎无法解决的挑战。这里是:

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

我不知道如何解决这个问题。我可以在字符串中搜索特定数字,但不能搜索它们的数量。

Eli*_*gan 72

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

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

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


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

grep -P '(?<!\d)\d{4}(?!\d)' file
Run Code Online (Sandbox Code Playgroud)

这使用Perl 正则表达式,Ubuntu grepGNU grep)通过-P. 它不会像匹配的文本12345,也不会匹配1234或者2345是它的一部分。但它会匹配1234in 1234a56789

在 Perl 正则表达式中:

  • \d表示任何数字(简单的说[0-9]or [[:digit:]])。
  • x{4}匹配x4 次。({ }语法并非特定于 Perl 正则表达式;它也包含在扩展的正则表达式 viagrep -E中。)所以\d{4}\d\d\d\d.
  • (?<!\d)是一个零宽度负后视断言。它的意思是“除非前面有 ” \d
  • (?!\d)是零宽度负前瞻断言。它的意思是“除非后面跟着\d。”

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

仅使用后视或前视是不够的,因为最右边或最左边的四位数子序列仍将匹配。

使用后视和前瞻断言的一个好处是您的模式只匹配四位数序列本身,而不匹配周围的文本。这在使用颜色突出显示(带有--color选项)时很有帮助。

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

默认情况下,在 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
Run Code Online (Sandbox Code Playgroud)

替代方法,没有后视和前瞻断言

但是,如果您:

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

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

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file
Run Code Online (Sandbox Code Playgroud)

这匹配四位数字和非数字字符 - 或行的开头或结尾 - 围绕它们。具体来说:

  • [0-9]匹配任何数字(如[[:digit:]], 或\d在 Perl 正则表达式中)并{4}表示“四次”。所以[0-9]{4}匹配一个四位数的序列。
  • [^0-9]匹配不在0through范围内的字符9。它等效于[^[:digit:]](或\D, 在 Perl 正则表达式中)。
  • ^, 当它没有出现在[ ]括号中时,匹配一行的开头。同样,$匹配行尾。
  • |表示or和括号用于分组(如在代数中)。所以(^|[^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}'
Run Code Online (Sandbox Code Playgroud)

由于它使用[0-9]matt 的方式更加便携——它可以在grep不支持 Perl 正则表达式的系统上工作。如果您使用[0-9](或[[:digit:]]) 代替\d,但继续使用{ },您会更简洁地获得 matt 的可移植性:

grep -E '[0-9]{4}' file | grep -Ev '[0-9]{5}'
Run Code Online (Sandbox Code Playgroud)

替代方式,使用单一模式

如果你真的更喜欢一个grep命令

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

...然后你可以使用:

grep -Px '(\d{0,4}\D)*\d{4}(\D\d{0,4})*' file
Run Code Online (Sandbox Code Playgroud)

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

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

grep -Ex '([0-9]{0,4}[^0-9])*[0-9]{4}([^0-9][0-9]{0,4})*' file
Run Code Online (Sandbox Code Playgroud)

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

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

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

    由于紧靠中央\d{4}(或[0-9]{4})左侧的文本必须为空或以非数字结尾,这会阻止中央\d{4}匹配在其左侧有另一个(第五个)数字的四位数字。

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

    由于紧靠中央\d{4}(或[0-9]{4})右侧的文本必须为空或以非数字开头,这会阻止中央\d{4}匹配四个数字,而这些数字的右侧还有另一个(第五个)数字。

这可确保某处存在四位数序列,并且任何地方均不存在五位数或更多位数的序列。

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

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


mat*_*att 10

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

grep '[0-9][0-9][0-9][0-9][^0-9]' file
Run Code Online (Sandbox Code Playgroud)

注意 ^ 表示不是

虽然我不知道如何解决这个问题,但有一个问题......如果数字是行尾,那么它就不会出现。

然而,这个丑陋的版本适用于这种情况

grep '[0-9][0-9][0-9][0-9]' file | grep -v [0-9][0-9][0-9][0-9][0-9]
Run Code Online (Sandbox Code Playgroud)

  • 第一个是错误的 - 它找到了 `a12345b`,因为它匹配 `2345b`。 (3认同)