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

6qftjkof  于 12个月前  发布在  Perl
关注(0)|答案(7)|浏览(122)

我的程序读取其他程序的源代码,并收集有关使用的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,可能会犯基本的概念错误。(表名在行中的位置不固定。另一个问题是多次发生,即[…] SELECT * FROM AADTTAB,BBDTTAB,...])。我怎么才能得到这个子串呢?

8tntrjer

8tntrjer1#

使用捕获组:

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

camsedfj2#

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

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

所以你可以做一些

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

警告:

如果你在代码中使用$&,它会降低所有模式匹配的速度。

wa7juj8i

wa7juj8i3#

当人们最初回答这个问题时,关于使用捕获的建议可能是一种方法。Perl从那时起就开始前进了,现在使用$&可能是最好的答案。
不使用捕获有一个重要原因:它抛出了模式内所有其他捕获的编号。在这种情况下,您可以使用带标签的捕获,例如(?<name>\w+),并在%-%+中查找它们,这样就不会有数字。
另一个答案提到了$&,这是字符串中与模式匹配的部分。这个回答还指出,它会减慢整个程序的速度,因为perl现在需要为每个正则表达式跟踪此信息,以防您将其用于该模式。
然而,Perl v5.20开始在许多地方使用写时复制,$&的问题变得毫无意义。Perl v5.18也做了一些更改,因此它只跟踪您实际使用的特殊per-match变量,而不是所有三个变量($``,$&$')。 在此之前,Perl v5.10已经添加了/p`开关,以启用一组并行的每个匹配变量,这些变量没有这种性能损失。这些变量只有长名称:

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

而且,v5.26增加了@{^CAPTURE},这样你就可以得到所有捕获的列表,而不知道有多少捕获。然而,第一项(索引0)不是$&,而是$1,因此所有内容都是一次性的。:/

p4tfgftt

p4tfgftt4#

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

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版本:v5.10.0为MSWin 32构建-x86-多线程

tzxcd3kk

tzxcd3kk5#

我更喜欢这样:

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


1.扫描$line并捕获与该模式对应的文本
1.返回“所有”捕获(1)到另一边的“列表”。
这个伪列表上下文就是我们如何捕获列表中的第一个元素。它的实现方式与将参数传递给子例程的方式相同。

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

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

注意::也就是说,你的正则表达式对文本的假设太多了,在很多情况下都是有用的。* 不捕获任何没有dt的表名,如7个位置中的3和4?* 这是足够好的1)快速和肮脏,2)如果你是有限的适用性好。

hxzsmxv2

hxzsmxv26#

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

#!/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

vfh0ocws

vfh0ocws7#

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";
}

相关问题