【问题标题】:Perl regex negative look-behindPerl 正则表达式负回顾
【发布时间】:2012-06-18 12:50:45
【问题描述】:

试图在正则表达式处理中围绕 look-aheadlook-behind

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

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

在这个正则表达式中,我尝试使用 (? 构造将数字匹配与排除相结合。这不起作用。

示例文件:

456
789
4567
345
22743
root
bin
sys

如果有人能指出我正确的方向,我将不胜感激。

还想知道在这种情况下,这种消极的后视是否是最有效的。

【问题讨论】:

  • 你能发布一些示例数据吗?请注意,look-behind 必须具有固定宽度,否则将不起作用。
  • 比“PID 和其他东西”了解更多关于文件内容会很有帮助。
  • 实际上“文件”来自 /proc 的目录列表(想法是在没有我自己的情况下获取正在运行的 PID 列表)

标签: regex perl negative-lookbehind


【解决方案1】:

“往后看”真的是往后看。因此,您可以检查 PID 前面是否有某些东西,而不是它是否匹配某些东西。如果只想排除$$,可以更直接:

@file = `cat $FILE`;
@pids = grep /(\d{1,5})/ && $1 ne $$, @file;

【讨论】:

  • 有趣的语法,正是我想做的,但不知道可以这样。
  • 再次阅读您的答案,我意识到我完全误解了正则表达式中的外观概念。
【解决方案2】:

我赞成 choroba 解决方案,只是想解释一下为什么您的原始方法不起作用。

你看,正则表达式解析器是一个复杂的野兽:它在试图匹配尽可能多的符号的内部斗争中受到影响 - 并且不惜一切代价尝试匹配。后者,嗯,通常会赢。 )

比如,我们来分析一下:

my $test_line = '22743';
my $pid = '22743';
print 'Matched?', "\n" if $test_line =~ /\d{1,5}(?<!$pid)/;
print $&, "\n";

您可能会问,为什么它会打印“匹配”?因为这就是发生的事情:首先引擎尝试消耗所有五个数字,然后匹配下一个子表达式 - 并且失败了(那是消极的后视点,不是吗?)

如果是你,你已经停止了 - 但不是引擎!还是觉得那阴暗的欲望,无论如何都要匹配!所以它需要下一个可能的量词 - 四个而不是五个 - 现在,当然,lookbehind 子表达式注定要成功。 ) 这很容易通过检查print $&amp; 打印的内容来检查;

能否在正则表达式的范围内解决?是的,所谓的atomics

print 'No match for ya!', "\n" unless $test_line =~ /(?>\d{1,5})(?<!$pid)/;

但我猜这通常被认为是一种黑暗魔法。 )

【讨论】:

  • 这就是 SO 的魔力,您不仅可以得到答案,还可以得到敬业的好人,他们会花时间详细解释您没有正确理解的内容。非常非常感谢您提供的令人大开眼界的解释。
  • 感谢您的客气话。 )
【解决方案3】:

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

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

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

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

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

【讨论】:

    【解决方案4】:

    怎么样:

    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 上有一个模块可以处理进程。

    预计到达时间:

    如果您从 cmets 中提到的 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 可以满足我的需要。
    • 如果有你可以使用的 cpan 模块,我会使用它而不是试图一起破解一些东西。
    • 在这种特殊情况下,我正在寻找最便携的解决方案来解决我的问题,因为这将在具有各种操作系统的多台机器上运行,而我无法预先安装模块。我什至 considered 用我的代码内联了模块。
    【解决方案5】:

    稍微不同的方式(我尽量避免@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";
    

    这是我对 SO 的第二个帖子,我希望可以提供另一种方法。

    【讨论】:

    • 感谢您的贡献。在这种情况下,我试图尽可能地提高效率,因此通过 while 循环可能不如使用 grep 正则表达式那样最佳。
    • @emx 逐行读取文件实际上比将文件插入数组(就像您所做的那样)更有效。
    • 完全正确。 cat $file 只是为了简单起见,我实际上使用 /proc 上的 readdir 获取数据
    • 运行cat 的反引号调用了一个在这种情况下毫无意义的外部进程。甚至还有一个award 用于无用的cats。正如 TLP 所指出的,在这种情况下,让 Perl 逐行读取文件。
    • 对,这是我最后一次简化代码以使我的帖子更易于阅读。我的实际代码中没有cat。我实际上是通过open $dh, "/proc"readdir $dh 得到数据
    猜你喜欢
    • 2018-01-02
    • 1970-01-01
    • 1970-01-01
    • 2023-02-22
    • 2012-02-27
    • 2021-03-21
    • 2015-11-21
    • 1970-01-01
    • 2015-12-10
    相关资源
    最近更新 更多