我正在学习 Linux,但我遇到了一个似乎无法独自解决的难题。它如下:
从文件中 grep 一行,该行包含 4 个数字,但不超过 4 个。
我不知道该如何解决这个问题。我可以搜索特定数字,但不能搜索字符串中它们的数量。
答案1
有两种方式可以解释这个问题;我将分别讨论这两种情况。您可能希望显示以下行:
- 包含一个四位数字的序列,该序列本身不属于任何更长的数字序列,或者
- 包含四位数字序列但不再包含数字序列(甚至不是单独的)。
例如,(1)将显示1234a56789
,但(2)则不会。
如果要显示包含四位数字序列的所有行,且该序列本身不属于任何更长的数字序列,则一种方法是:
grep -P '(?<!\d)\d{4}(?!\d)' file
这使用Perl 正则表达式,Ubuntu 的grep
(GNU grep) 支持通过-P
。它不会匹配像 这样的文本12345
,也不会匹配1234
或2345
属于它的部分。但它会1234
匹配1234a56789
。
在 Perl 正则表达式中:
\d
[0-9]
表示任意数字(这是或 的简写[[:digit:]]
)。x{4}
火柴x
4 次。 ({
}
语法并不特定于 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
只打印匹配的文本,而不是整行,如下所示-o
:
ek@Io:~$ grep -oP '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
0123
替代方法,没有后视断言和前视断言
但是,如果您:
grep
需要一个可以在不支持-P
或不想使用 Perl 正则表达式的系统上运行的命令,和- 不需要特别匹配四位数字——如果你的目标只是显示包含匹配的行,通常就是这种情况,和
- 可以接受一个不那么优雅的解决方案
...那么你可以用扩展正则表达式反而:
grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file
匹配四位数字和它们周围的非数字字符(或行首或行尾)。具体来说:
[0-9]
匹配任意数字(如Perl 正则表达式中的[[:digit:]]
、 或),表示“四次”。因此匹配四位数字序列。\d
{4}
[0-9]{4}
[^0-9]
0
匹配不在 到范围内的字符9
。它相当于Perl 正则表达式中的[^[:digit:]]
(或\D
,)。^
,当它不出现在[
]
括号中时,匹配行首。同样,$
匹配行尾。|
方法或者和括号用于分组(如代数中)。因此(^|[^0-9])
匹配行首或非数字字符,而($|[^0-9])
匹配行尾或非数字字符。
[0-9]{4}
因此,匹配仅发生在同时包含四位数字序列()的行中:
- 在行首或以非数字 (
(^|[^0-9])
) 开头,和 - 位于行尾或后跟非数字 (
($|[^0-9])
)。
另一方面,如果你想显示所有包含四位数字序列但不包含任何超过四位数字的序列(即使该序列与另一个只有四位数字的序列分开),那么从概念上讲,您的目标是找到与一个模式匹配但不与另一个模式匹配的行。
因此,即使你知道如何使用单一模式来实现这一点,我还是建议使用类似马特的第二个建议,grep
分别针对这两种模式进行分析。
这样做不会给你带来 Perl 正则表达式的任何高级功能带来很大好处,所以你可能不想使用它们。但为了保持上述风格,下面是马特的解决方案使用\d
(和括号)代替[0-9]
:
grep -P '\d{4}' file | grep -Pv '\d{5}'
由于它使用[0-9]
,马特的方式可移植性更强——它可以在grep
不支持 Perl 正则表达式的系统上运行。如果您使用[0-9]
(或[[:digit:]]
) 代替\d
,但继续使用{
}
,则可以更简洁地获得 matt 方式的可移植性:
grep -E '[0-9]{4}' file | grep -Ev '[0-9]{5}'
另一种方式,使用单一模式
如果你确实更喜欢一个grep
命令
- 使用单个正则表达式(不是两个
grep
被 a 分隔的管道, 如上) - 显示至少包含一个四位数字序列的行,
- 但没有五位(或更多)数字的序列,
- 并且你不介意匹配整行,而不仅仅是数字(你可能不介意这一点)
...然后您可以使用:
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)空字符串或(二)任意字符串结尾非数字且不包含任何超过四位数字的序列。\d{4}
由于中央(或)左侧的文本[0-9]{4}
必须为空或以非数字结尾,这会阻止中央\d{4}
匹配左侧有另一位(第五位)数字的四位数字。在右侧,
(\D\d{0,4})*
或([^0-9][0-9]{0,4})*
匹配零个或多个 (*
) 个非数字实例,后跟不超过四个数字(与前面一样,可以是四个、三个、两个、一个,甚至没有)。这匹配(A)空字符串或(二)任意字符串开始非数字且不包含任何超过四位数字的序列。\d{4}
由于中央(或)右侧的文本[0-9]{4}
必须为空或以非数字开头,这会阻止中央\d{4}
匹配右侧有另一位(第五位)数字的四位数字。
这确保了某处存在四位数字的序列,并且任何地方都不存在五位或更多位数字的序列。
这样做并没有什么不好或错误。但也许考虑这种替代方案的最重要原因是,它阐明了使用(或类似)的好处,正如上文和grep -P '\d{4}' file | grep -Pv '\d{5}'
马特的回答。
这样,您的目标就很明显了,就是选择包含一个内容但不包含其他内容的行。此外,语法也更简单(因此许多读者/维护者可能会更快地理解它)。
答案2
这将显示连续 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]
答案3
答案4
如果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