Perl正则表达式——从邮件日志中提取IPv4

3
我正在使用perl/mysql/iptables开发一个类似于分布式fail2ban的系统。
从/var/log/messages中提取ipv4地址已经可以实现,但是现在我想将/var/log/maillog加入到这个系统中。
我有一个perl正则表达式:[1]
/ (?:25[012345]|2[0-4]\d|1?\d\d?)\.
  (?:25[012345]|2[0-4]\d|1?\d\d?)\.
  (?:25[012345]|2[0-4]\d|1?\d\d?)\.
  (?:25[012345]|2[0-4]\d|1?\d\d?) /x

以下是maillog中的一行记录:

v817YjcU016645: 194.102.60.190.host.ifxnetworks.com [190.60.102.194] did not issue MAIL/EXPN/VRFY/ETRN during connection to MTA

这里的正则表达式匹配了 194.102.60.190.host.ifxnetworks.com 和 [190.60.102.194]。

我的代码中有以下内容($IP是上述正则表达式):

if ($line =~ m/($IP)/)
{
    my ($ip) = $1;

在这里找到了第一个匹配的类似IP的字符串194.102.60.190。host.ifxnetworks.com

那么,我该如何让正则表达式忽略以.结尾的IPv4地址?


[1]为了易读性,Perl支持/x选项


1
你尝试过负向先行断言吗?例如/...(?!\.)/ - zdim
我认为IP地址后面的点只是问题的起点。 - Wolf
我将您的正则表达式分成多行以提高可读性。请检查一下。如果没问题...这四个确实是相同的吗? - zdim
@zdim 是的。它们是相同的 my ($IP) = $OCT . '\.' . $OCT . '\.' . $OCT . '\.' . $OCT :) - Mogens TrasherDK
谢谢。问题在于最后一个数字是可选的。因此,无论您跟随它与什么,它都可以自由地匹配少一个数字,然后该后续非“.”字符可以是最后一个数字本身。 - zdim
显示剩余2条评论
2个回答

4
如果这是唯一的问题,尝试使用 "negative lookahead"。具体解释可以参考这个链接
my ($ip) = $line =~ /($IP)(?![.\d])/;

这适用于所示的数据。

在先行断言中,字符类[.\d]是必需的,因为$IP正则表达式中的最后一个术语允许通过\d?使用可变数量的数字。 因此,仅使用(?!\.)引擎可以匹配比实际少一个数字,然后剩余的数字满足非.限制。

因此,我们需要禁止.和遵循模式的数字。


一个完整的程序

use warnings;
use strict;

my $t = 'a 194.102.60.190.host.ifxnetworks.com [190.60.102.194] b';

my $n = qr/(?:25[012345]|2[0-4]\d|1?\d\d?)/;

my $IP = qr/$n\.$n\.$n\.$n/;

my @m = $t =~ /($IP)(?![.\d])/g;

print "@m\n";

打印 190.60.102.194


考虑子字符串90.host。其对应的模式/\d\d?(?!\.)/的工作原理如下。

第一个\d匹配到9。但是接下来的\d?是可选的(非贪婪),如果剩余的模式可以匹配,则不匹配。实际上,(?!\.)看到后面的0不是.,所以我们匹配了9,并且0满足(?!\.)。整个模式(错误地)匹配了。

perl -wE'$_ = q(90.host); @m = /(\d)(\d?)(?!\.)(.)/; say for @m'

打印

9
0

中间的捕获组未捕获任何内容,下一个字符(.)0

现在考虑针对相同的子字符串的模式/\d\d?(?![.\d])/ (?![.\d])要求其后面既不是.也不是数字。因此,可选的\d?强制匹配下一个数字,即0。由于接下来的字符是.,因此模式失败。

在上述单行测试中,将(?!\.)替换为(?![.\d]),则根本没有打印任何内容,因为该模式根本没有匹配。(在某些shell中,您可能需要转义!,例如(?\![.\d]),或使用脚本。)

引擎可能不会完全按照描述进行操作,这更像是对其操作的宽松描述。


在这里使用__DATA__是否更好,以更好地分离输入和程序? - Wolf
@Wolf 嗯,当有一些数据并且我们想要一个完全自包含的程序,用于简单演示(没有需要讨论的输入文件等)。在这种情况下,只需给出一行代码即可,这样更清晰明了(而不是读取数据等)。 - zdim
到目前为止,我避免使用负向先行断言(没有特殊原因)。在使用它时,有哪些性能方面的问题我应该注意? - Wolf
1
@Wolf 当然没有什么极端的情况,我认为也没有其他需要注意的地方。正则表达式会影响性能,应该注意这一点。所以,如果前瞻进入回溯游戏(例如),那不是它本身的问题,而是正则表达式的结构允许它。这只是一种锚点(非消耗性断言)的类型。 - zdim
@MogensTrasherDK 我已经添加了一个解释,希望能有所帮助。这有点棘手但很有趣,是正则表达式操作的一个例子。 - zdim
显示剩余3条评论

0
通常情况下,正则表达式匹配现有字符序列中的所需模式,如果存在不需要的内容,则匹配非常困难。
您可以匹配后面没有点([^.])的 IP 地址[1]
(?:\d{1,3}\.){3}\d{1,3}[^.]

并在行末添加IP地址($):

(?:\d{1,3}\.){3}\d{1,3}$

您可以通过非捕获组((?:...))中的选择操作符(|)结合两个模式:

(?:\d{1,3}\.){3}\d{1,3}(?:[^.]|$)

一个类似的问题可能是你的下一个任务是排除那些在它们前面有一个点的IP地址,另一个问题是它也会匹配1.2.3.4.5中的2.3.4.5,这就回到了我的开场白...

我认为最好找到检查周围字符的东西来匹配你尝试匹配的IP地址。要具体说明这一点。在开发阶段,尝试通过将它们与“垃圾模式”匹配来检查不匹配的行。在所提出的问题中(其中空格和括号是可接受的环境),我建议使用

(?:[ \[]|^)((?:\d{1,3}\.){3}\d{1,3})(?:[ \]]|$)

[1]在这里我使用的是一个简化的正则表达式,可以匹配333.333.333.333或者000.000.000.000,当然它可以被改进以限制匹配有效的IP地址,但是关于此问题的解决方案是丰富的。


我认为解决方案是分两步进行提取。第一步:my ($line1) = $line =~ /(\[)($IP)(\])/; 匹配 [190.60.102.194] 。第二步:my ($ip) = $line =~ /($IP)/;匹配 190.60.102.194 - Mogens TrasherDK
@MogensTrasherDK 我认为两个阶段是不必要的,可以看看我最后添加的内容。实际匹配 (((?:\d{1,3}\.){3}\d{1,3})) 已经包含在最后一个(“建议的”)正则表达式中了。 - Wolf
@MogensTrasherDK 提醒一下:如果您(可能很快)在某个日志文件中遇到其他令人不安的IP地址和它们的文本上下文之间的交互,请回来看看是否能够积极指定这些模式。 - Wolf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接