Perl正则表达式负向后查找

3

我试图理解正则表达式处理中的 前瞻后顾

假设我有一个文件列出了 PIDs 和其他内容,我想构建一个正则表达式来匹配 PID 格式 \d{1,5} ,但也需要排除某个特定的 PID。

$myself = $$;
@file = `cat $FILE`;
@pids = grep /\d{1,5}(?<!$myself)/, @file;

在这个正则表达式中,我尝试使用否定回顾 (?<!TO_EXCLUDE) 结构将数字匹配和排除组合在一起。但是这样做不起作用。
示例文件:
456
789
4567
345
22743
root
bin
sys

希望有人能指导我正确的方向。

同时,我想知道在这种情况下,这种负向回顾是否最有效。


1
你能发布一些样本数据吗?请注意,回顾必须具有固定宽度,否则它将无法工作。 - nhahtdh
了解有关文件内容的更多信息比“PID和其他事情”更有帮助。 - TLP
实际上,“file”来自/proc的目录列表(其想法是获取运行中的PID列表而不包括我的)。 - emx
5个回答

6
“Look behind”确实是在后面查找。因此,您可以检查一个PID是否有前导内容,而不是匹配某些内容。如果您只想排除$$,那么可以更加直接:
@file = `cat $FILE`;
@pids = grep /(\d{1,5})/ && $1 ne $$, @file;

有趣的语法,正是我想做的,但我不知道可以这样做。 - emx
重新阅读您的答案,我意识到我完全误解了正则表达式中的向后查找概念。 - emx

5
我已经点赞了choroba的解决方案,只是想解释一下您最初的方法为什么不起作用。
你看,正则表达式解析器是一个复杂的东西:它内部努力匹配尽可能多的符号,而且总是试图以任何代价匹配。而后者通常会获胜。
例如,我们来分析以下内容:
my $test_line = '22743';
my $pid = '22743';
print 'Matched?', "\n" if $test_line =~ /\d{1,5}(?<!$pid)/;
print $&, "\n";

你可能会问,为什么它打印了“匹配”?因为事实就是这样的:首先引擎试图消耗所有五个数字,然后匹配下一个子表达式 - 但失败了(这不是负回顾后发现的要点吗?)
如果是你,你已经停止了 - 但引擎没有!它仍然感觉到想要无论如何都能匹配的黑暗欲望!因此,它采取了下一个可能的量化器 - 四个而不是五个 - 现在,当然,回顾后发现子表达式注定会成功。通过检查由print $&打印的内容,很容易验证这一点。
可以在正则表达式的领域内使用所谓的原子来解决这个问题。
print 'No match for ya!', "\n" unless $test_line =~ /(?>\d{1,5})(?<!$pid)/;

但我想这通常被视为黑魔法。

这就是 Stack Overflow 的魔力,你不仅可以得到答案,还能遇到热心的人们,他们会花时间详细地解释你没有理解透彻的问题。非常感谢您这个开阔思路的解释。 - emx
谢谢您的赞美之词。 - raina77ow

4

如果你好奇如何使用正则表达式来实现,这里有一些例子:

/\b\d{1,5}+(?<!\b$pid)/

/\b\d{1,5}\b(?<!\b$pid)/

/\b(?!$pid\b)\d+/

/^(?!$pid$)\d+$/

2

如何呢:

chomp(@file);      # remove newlines that will otherwise mess things up
my @pids = grep /\d{1,5}/, @file;
my %pids = map { $_ => 1 }, @pids;

delete $pids{$$};  # delete one specific pid

@pids = keys %pids;

即通过哈希传递PID列表并删除自己的PID。需要对从文件中读取的行进行chomp以匹配PID。
我相信CPAN上有一个处理进程的模块。
如果您正在从readdir中读取值,如您在评论中提到的,那么像这样的东西可能是您最好的选择(未经测试):
opendir my $dh, "/proc" or die $!;
my @pids;
while ( my $line = readdir $dh ) {     # iterate through directory content
    next unless $line =~ /^\d{1,5}$/;  # skip non-numbers
    next if $line == $$;               # skip own PID
    push @pids, $line;
}

谢谢。优雅但可能不是最CPU高效的。 在CPAN上确实有一个Proc::ProcessTable,它可以满足我的需求。 - emx
如果有一个CPAN模块可用,我会使用它,而不是试图拼凑一些东西。 - TLP
在这种情况下,我正在寻找最便携的解决方案,因为它将在多台具有不同操作系统的机器上运行,在那里我没有能力预先安装模块。我甚至已经考虑将模块与我的代码嵌入在一起。 - emx

0

一种稍微不同的方式(我尝试避免使用 @file = cat text.txt

my @pids;
open my $fi, "<", "pids.txt";
while (<$fi>) {
   if (/(\d{1,5})/) {
      push @pids, $1 if $1 ne $$;
   }
}
close $fi;

print join(", ", @pids), "\n";

这是我在 Stack Overflow 上的第二篇帖子,我希望提供一种替代方法没问题。


感谢您的贡献。在这种情况下,我正在尽可能地提高效率,因此通过 while 循环可能不如使用 grep 正则表达式优化。 - emx
@emx 逐行读取文件实际上比将文件读入数组(就像您所做的那样)更有效率。 - TLP
完全正确。$file 这只猫只是为了简单起见,我实际上是在 /proc 上使用 readdir 获取数据的。 - emx
反引号运行cat调用了一个外部进程,在这种情况下是没有意义的。甚至有一个奖项颁发给无用的cat。正如TLP所指出的那样,在这种情况下,让Perl逐行读取文件即可。 - JRFerguson
好的,这是我最后一次简化我的代码,以使我的帖子更易于阅读。我的实际代码中没有 cat。我实际上是通过执行 open $dh, "/proc"readdir $dh 来获取数据的。 - emx
如果有人不理解opendirreaddir,我怀疑他们对你的问题的回答不会有太大帮助。当展示你的sscce时,你不应该改变其基本过程。 - TLP

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