在Perl中,如何从正则表达式中获取匹配的子字符串?

In Perl, how can I get the matched substring from a regex?

提问人:kato sheen 提问时间:7/15/2009 最后编辑:Donatkato sheen 更新时间:8/31/2023 访问量:67626

问:

我的程序读取其他程序的源代码并收集有关使用的 SQL 查询的信息。我在获取子字符串时遇到问题。

...
$line = <FILE_IN>;
until( ($line =~m/$values_string/i && $line !~m/$rem_string/i) || eof )
{
   if($line =~m/ \S{2}DT\S{3}/i)
   {

   # here I wish to get (only) substring that match to pattern \S{2}DT\S{3} 
   # (7 letter table name) and display it.
      $line =~/\S{2}DT\S{3}/i;
      print $line."\n";
...

结果,print 打印整行,而不是我期望的子字符串。我尝试了不同的方法,但我很少使用Perl,并且可能会犯基本概念错误。( tablename 在行中的位置不固定。另一个问题是多次发生,即[...选择 * 从 AADTTAB、BBDTTAB 等]).如何获取该子字符串?

正则表达式 Perl

评论

0赞 kato sheen 7/16/2009
感谢大家的快速和各种方法。我昨天和今天早上都尝试使用它们,但只有 $& 对我有用。也感谢(使用严格;使用警告;)线索向我展示了我的即兴创作风格。今天我也意识到我没有通知我在 Windows 下工作(我的珍珠是:这是 perl,为 MSWin32-x86-multi-thread 构建的 v5.8.7 版权所有 1987-2005,Larry Wall Binary build 813 [148120] 由 ActiveState 提供 www.ActiveState.com Built Jun 6 2005 13:36:37)。再次感谢你。
3赞 kato sheen 7/17/2009
在我脸上“无知是一种幸福”之后,我有点恼火,但它促使我......井。。。现在说我知道“捕获组”“括号/括号”是什么意思,它确实有效。请不要评论我已经觉得自己很傻了。顺便说一句,有没有人支持全球投票将 perl 重命名为 - 我不知道 - 珍珠?;)
0赞 Brad Gilbert 7/17/2009
当拉里·沃尔(Larry Wall)去寻找名字时,已经有一种名为Pearl的语言了。

答:

8赞 Sinan Ünür 7/15/2009 #1

如果遵循,最好匹配该模式。我假设表名仅由 ASCII 字母组成。在这种情况下,最好说出你想要的。撇开这两个注释,请注意,在列表上下文中成功捕获正则表达式匹配将返回匹配的子字符串。FROM

#!/usr/bin/perl

use strict;
use warnings;

my $s = 'select * from aadttab, bbdttab';
if ( my ($table) = $s =~ /FROM ([A-Z]{2}DT[A-Z]{3})/i ) {
    print $table, "\n";
}
__END__

输出:

C:\Temp> s
aadttab

根据系统上的版本,您可以使用命名的捕获组,这可能会使整个内容更易于阅读:perl

if ( $s =~ /FROM (?<table>[A-Z]{2}DT[A-Z]{3})/i ) {
    print $+{table}, "\n";
}

请参见 perldoc perlre

22赞 Jesse Vogt 7/15/2009 #2

使用带括号的分组并存储第一个组。

if( $line =~ /(\S{2}DT\S{3})/i )
{
  my $substring = $1;
}

上面的代码修复了拉出第一个表名的直接问题。但是,该问题还询问了如何提取所有表名。所以:

# FROM\s+     match FROM followed by one or more spaces
# (.+?)       match (non-greedy) and capture any character until...
# (?:x|y)     match x OR y - next 2 matches
# [^,]\s+[^,] match non-comma, 1 or more spaces, and non-comma
# \s*;        match 0 or more spaces followed by a semi colon
if( $line =~ /FROM\s+(.+?)(?:[^,]\s+[^,]|\s*;)/i )
{
  # $1 will be table1, table2, table3
  my @tables = split(/\s*,\s*/, $1);
  # delim is a space/comma
  foreach(@tables)
  {
     # $_ = table name
     print $_ . "\n";
  }
}

结果:

如果 $line = “SELECT * FROM AADTTAB, BBDTTAB;”

输出:

AADTTAB
BBDTTAB

如果 $line = “SELECT * FROM AADTTAB;”

输出:

AADTTAB

Perl 版本:为 MSWin32-x86-multi-thread 构建的 v5.10.0

3赞 friedo 7/15/2009 #3

使用捕获组:

my $substr;
if( $line =~ /(\S{2}DT\S{3})/i ) {
    $substr = $1;
}

评论

3赞 Sinan Ünür 7/15/2009
在使用匹配变量之前,请务必检查匹配是否成功。
7赞 mleykamp 7/15/2009 #4

Parens 将允许您将正则表达式的一部分抓取到特殊变量中:$1、$2、$3...... 所以:

$line = ' abc andtabl 1234';
if($line =~m/ (\S{2}DT\S{3})/i)   {   
    # here I wish to get (only) substring that match to pattern \S{2}DT\S{3}    
    # (7 letter table name) and display it.      
    print $1."\n";
}
2赞 Abhinav Gupta 7/16/2009 #5

$&包含与最后一个模式匹配匹配的字符串。

例:

$str = "abcdefghijkl";
$str =~ m/cdefg/;
print $&;
# Output: "cdefg"

所以你可以做类似的事情

if($line =~m/ \S{2}DT\S{3}/i) {
    print $&."\n";
}

警告:

如果在代码中使用,它将减慢所有模式匹配的速度。$&

评论

1赞 daotoad 7/16/2009
避免使用 $& 以及相关的 $' 和 $',它们会导致代码中所有正则表达式的性能下降。有关更多信息,请参见 perlre (perldoc.perl.org/perlre.html)。
1赞 Brad Gilbert 7/17/2009
只要在代码中的任何位置提到 ,都会减慢所有正则表达式的速度。即使你真的使用这个值也没关系。$&
0赞 kato sheen 7/17/2009
在学习期间,我曾经习惯于评估这种说法。有人检查过这个($&)糟糕的做法有多糟糕吗?高达 10%/30%,可以分享结果吗?
0赞 Brad Gilbert 7/17/2009
我想我记得我读过那些计划在将来的某个时候被弃用的书。$&
0赞 Brad Gilbert 7/18/2009
我认为可能有一些更改降低了 perl 5.10 中的效果
19赞 Axeman 7/16/2009 #6

我更喜欢这个:

my ( $table_name ) = $line =~ m/(\S{2}DT\S{3})/i;

  1. 扫描并捕获与图案对应的文本$line
  2. 将捕获的“所有”(1) 返回到另一端的“列表”。

这个伪列表上下文是我们捕获列表中第一项的方式。它的完成方式与传递给子例程的参数相同。

my ( $first, $second, @rest ) = @_;


my ( $first_capture, $second_capture, @others ) = $feldman =~ /$some_pattern/;

注意::也就是说,您的正则表达式对文本的假设太多,在少数情况下没有用。没有捕获任何没有 dt 的表名,如位置 3 和 4 中的 7?它足以满足 1) 快速和肮脏,2) 如果你对有限的适用性感到满意。

评论

0赞 daotoad 7/16/2009
这真的是列表上下文,没有什么伪的!棘手的事情是使用一个项目的列表。当您希望强制执行正在调用的运算符或子例程的列表上下文行为时,在单个项目列表中捕获操作结果可能非常方便。 与 有很大不同,这种区别可以派上用场。my $foo = @bar;my ($foo) = @bar;
0赞 Axeman 7/16/2009
哦,它确实派上用场了。我一直在使用它。我想“伪”的说法很糟糕。我知道一个列表仍然是一个列表,它看起来很像一个标量——无论如何,这就是我想要得到的。
1赞 brian d foy 8/31/2023 #7

当人们最初回答这个问题时,关于使用捕获的建议可能是一种方法。从那时起,Perl 就一直在前进,使用可能是现在最好的答案。$&

不使用捕获的一个重要原因:它会取消模式中所有其他捕获的编号。在这种情况下,您可以使用标记的捕获,例如 ,并查看它们或它们,这样您就没有数字了。(?<name>\w+)%-%+

另一个答案提到了 ,这是字符串中与模式匹配的部分。该答案还指出,它减慢了整个程序的速度,因为perl现在需要跟踪每个正则表达式的此信息,以防万一你将其用于该模式。$&

然而,Perl v5.20 开始在许多地方使用写时复制,这个问题变得毫无意义。Perl v5.18 也做了一些更改,因此它只跟踪您实际使用的特殊每场比赛变量,而不是所有三个变量 (, , )。$&$`$&$'

以前,Perl v5.10 已经添加了开关,以启用一组并行的每场比赛变量,这些变量没有这种性能损失。这些变量只有长名称:/p

use v5.10;
if( $string =~ m/.../p ) {
    say <<"HERE";
Before match: ${^PREMATCH}    
Matched: ${^MATCH}
After match: ${^POSTMATCH}    
HERE
    }

并且,添加了 v5.26,因此您可以在不知道有多少捕获的情况下获得所有捕获的列表。但是,与其让第一项(索引 0)等价于 ,不如让一切都是一次性的。@{^CAPTURE}$&$1