5

我想为以下内容创建一个正则表达式。

我有一些如下文本:

field = "test string";
type =  INT;
funcCall(.., field, ...);
...
text = "desc";

field = "test string 1";
type = FLOAT;
funcCall(.., field, ...);
...
text = "desc 2";

field = "test string 2";
type = FLOAT;
funcCall(.., field, ...);
...
text = "desc 3";

.... keeps repeating

基本上我正在尝试创建一个正则表达式,它将获取从第一个“field =”开头到第二个“field =”开头的所有文本。它必须跳过函数调用中使用的字段文本。

我目前有以下内容:

my @overall = ($string =~ m/field\s*=.*?/gis);

但是,这只是获得文本“field =”。没有“?” 它从第一个实例一直到最后一个实例获取所有数据。

我也试过:

my @overall = ($string =~ m/field\s*=.*field\s*=/gis);

但是,这会让我每隔一个实例,因为它是第二个“field =”字符串的所有格。有什么建议么?

4

5 回答 5

5

我能看到的最简单的方法是split通过$string表达式/^\s*field\s*=/。如果我们想捕获'field = '文本的一部分,我们可以先看一下

foreach ( split /(?=^\s*field\s*=)/ms, $string ) {
    say "\$_=[\n$_]";
}

'field'因此,它在下一个非空白字符串所在的每一行的开头中断,然后是任意数量的空白,然后是'='.

输出是:

$_=[
field = "test string";
type =  INT;
funcCall(.., field, ...);
...
text = "desc";
]
$_=[

]
$_=[
field = "test string 1";
type = FLOAT;
funcCall(.., field, ...);
...
text = "desc 2";
]
$_=[

]
$_=[
field = "test string 2";
type = FLOAT;
funcCall(.., field, ...);
...
text = "desc 3";

.... keeps repeating
]

几乎是我想要的。但是,它会在我们想要的捕获之间留下一个空白行。我不知道如何摆脱它,所以我们只过滤掉全空白字符串:

foreach ( grep { m/\S/ } split /(?=^\s*field\s*=)/ms, $string ) {
    say "\$_=[\n$_]";
}

然后它产生:

$_=[
field = "test string";
type =  INT;
funcCall(.., field, ...);
...
text = "desc";
]
$_=[
field = "test string 1";
type = FLOAT;
funcCall(.., field, ...);
...
text = "desc 2";
]
$_=[
field = "test string 2";
type = FLOAT;
funcCall(.., field, ...);
...
text = "desc 3";

.... keeps repeating
]

您可以使用它。

于 2015-10-26T23:10:09.090 回答
4

快速而肮脏的方法是定义一个主要匹配字段分配的正则表达式,然后在另一个正则表达式中使用它来匹配它们之间的内容。

my $field_assignment_re = qr{^\s* field \s* = \s* [^;]+ ;}msx;

$code =~ /$field_assignment_re (.*?) $field_assignment_re/msx;
print $1;

这种方法的缺点是它可能匹配带引号的字符串等。


您可以使用正则表达式对代码进行排序,但正确解析它超出了正常的正则表达式。这是因为大量的平衡分隔符(即括号和大括号)和转义符(即。"<foo \"bar\"">")。为了让它正确,你需要写一个语法。

Perl 5.10 添加了递归体面匹配,使编写语法成为可能。他们还添加了命名捕获组来跟踪所有这些规则。现在您可以使用 Perl 5.10 正则表达式编写递归语法。

它仍然有点笨拙,Regexp::Grammar添加了一些增强功能,使编写正则表达式语法更加容易。

编写语法是关于从某个点开始并填写规则。你的程序是一堆Statements。什么是声明?一个分配,或一个函数调用,后跟一个;. 什么是作业? Variable = Expression. 什么是VariableExpression?等等...

use strict;
use warnings;
use v5.10;

use Regexp::Grammars;

my $parser = qr{
  <[Statement]>*

  <rule: Variable>      \w+
  <rule: FunctionName>  \w+
  <rule: Escape>        \\ .
  <rule: Unknown>       .+?
  <rule: String>        \" (?: <Escape> | [^\"] )* \"
  <rule: Ignore>        \.\.\.?
  <rule: Expression>    <Variable> | <String> | <Ignore>
  <rule: Assignment>    <Variable> = <Expression>
  <rule: Statement>     (?: <Assignment> | <FunctionCall> | <Unknown> ); | <Ignore>
  <rule: FunctionArguments>     <[Expression]> (?: , <[Expression]> )*
  <rule: FunctionCall>  <FunctionName> \( <FunctionArguments>? \)
}x;

my $code = <<'END';
field = "test \" string";
alkjflkj;
type =  INT;
funcCall(.., field, "escaped paren \)", ...);
...
text = "desc";

field = "test string 1";
type = FLOAT;
funcCall(.., field, ...);
...
text = "desc 2";

field = "test string 2";
type = FLOAT;
funcCall(.., field, ...);
...
text = "desc 3";
END

$code =~ $parser;

这比正则表达式要健壮得多。包括:

<rule: Escape>        \\ .
<rule: String>        \" (?: <Escape> | [^\"] )* \"

处理其他棘手的边缘情况,例如:

funcCall( "\"escaped paren \)\"" );

这一切都结束了%/。这是第一部分。

$VAR1 = {
          'Statement' => [
                           {
                             'Assignment' => {
                                               'Variable' => 'field',
                                               'Expression' => {
                                                                 'String' => '"test string"',
                                                                 '' => '"test string"'
                                                               },
                                               '' => 'field = "test string"'
                                             },
                             '' => 'field = "test string";'
                           },
          ...

然后你可以遍历Statement数组寻找匹配的Assignments 。Variablefield

my $seen_field_assignment = 0;
for my $statement (@{$/{Statement}}) {
    # Check if we saw 'field = ...'
    my $variable = ($statement->{Assignment}{Variable} || '');
    $seen_field_assignment++ if $variable eq 'field';

    # Bail out if we saw the second field assignment
    last if $seen_field_assignment > 1;

    # Print if we saw a field assignment
    print $statement->{''} if $seen_field_assignment;
}

这似乎需要做很多工作,但值得学习如何编写语法。有很多问题可以用正则表达式解决一半,但用简单的语法就可以完全解决。从长远来看,正则表达式会变得越来越复杂,并且永远不会完全覆盖所有边缘情况,而语法则更容易理解并且可以变得完美。

这种方法的缺点是您的语法可能不完整并且可能会出错,尽管Unknown规则会处理大部分内容。

于 2015-10-26T22:09:41.810 回答
1

对于关于您的样本数据的整体“鞭笞”,我认为将模式传递给split将是最简单的。但是,正如@Schwern指出的那样,当事情变得更复杂时,使用语法会有所帮助。

为了好玩,我创建了一个示例脚本,该脚本使用使用Pegex. 在快速构建语法时,两者Regexp::Grammar都具有广泛使用和熟悉的优势。Regexp::Common如果您已经了解 perl 并且需要为您的项目提供一个简单但功能强大的正则表达式版本,那么进入门槛很低。Pegex方法是尝试使使用perl构建和使用语法变得容易。使用 Pegex,您可以从正则表达式构建解析表达式语法:

“Pegex ......通过将解析表达式语法 (PEG) 与正则表达式 (Regex) 相结合而得名。这实际上就是 Pegex 所做的。” (来自 POD)。

下面是一个独立的脚本,它使用 Pegex 语法解析您的数据的简化版本。


首先,脚本将$grammar“inline”作为多行字符串读出,并将其用于从句柄中->parse()读取的样本数据。<DATA>通常,解析语法和数据将驻留在单独的文件中。使用该函数将语法的“原子”和正则表达式编译pegex成用于解析数据的正则表达式的“树”或散列。该parse()方法返回一个可供 perl 使用的数据结构。在脚本中添加use DDPp $ast可以帮助您查看语法返回的结构(AoHHoH等)。

#!/usr/bin/env perl
use v5.22;
use experimental qw/ refaliasing postderef / ;
use Pegex;

my $data = do { local $/; <DATA> } ;

my $grammar = q[
%grammar thing
%version 0.0.1

things: +thing*
thing: (+field +type +text)+ % end 

value: / <DOUBLE> (<ANY>*) <DOUBLE> /
equals: / <SPACE> <EQUAL>  <SPACE> /
end: / BLANK* EOL / 

field: 'field' <equals> <value> <SEMI> <EOL>
type:  'type' <equals> /\b(INT|FLOAT)\b/ <SEMI> <EOL>
func:  / ('funcCall' LPAREN <ANY>* RPAREN ) / <SEMI> <EOL> .( <DOT>3 <EOL>)*
text:  'text' <equals> <value> <SEMI> <EOL>    
];

my $ast = pegex($grammar, 'Pegex::Tree')->parse($data);

for \my @things ( $ast->[0]->{thing}->@* ) {
  for \my %thing ( @things ) { 
    say $thing{"text"}[0] if $thing{"text"}[0] ; 
    say $thing{"func"}[0] if $thing{"func"}[0] ; 
  }
}

在脚本的最后一个__DATA__部分保存要解析的文件内容:

__DATA__
field = "test string 0";
type = INT;
funcCall(.., field, ...);
...
text = "desc 1";

field = "test string 1";
type = FLOAT;
funcCall(.., field, ...);
...
text = "desc 2";

field = "test string 2";
type = FLOAT;
funcCall(.., field, ...);
...
text = "desc 3";    

您当然可以轻松地从文件句柄或STDIN以经典的 perl 方式读取数据,或者,例如,使用IO::All我们可以做的地方:

use IO::All; 
my $infile < io shift ; # read from STDIN

您可以PegexCPAN安装,然后下载并使用 gist来了解 Pegex 的工作原理。

通过 Perl6,我们得到了一个强大且易于使用的“语法引擎”,它建立在 Perl 在处理正则表达式方面的优势之上。如果语法开始在更广泛的项目中使用,这些发展势必会反馈到 perl5 并导致更强大的功能。

PEG 部分Pegex及其跨语言开发允许在不同编程语言社区(Ruby、Javascript)之间交换语法。Pegex 可以在相当简单的场景中使用,并且非常适合需要解析功能的更复杂的模块。Pegex API 允许轻松创建可以在“接收器类”中定义的规则派生函数集。使用接收器类,您可以构建复杂的方法来处理已解析的数据,允许您“在解析时进行调整”,甚至可以即时修改语法(!)更多可以重新使用和改进的工作语法示例,并且越来越多的使用模块Pegex将帮助它变得更加有用和强大。

尝试 Pegex 框架的最简单方法可能是Pegex::Regex - 它允许您像使用正则表达式一样方便地使用语法,将解析结果存储在%/. Pegex作者称其为解析表达式语法的“网关药物”,并指出它是“Damian Conway模块 API 的克隆”(@Schwern在他对这个问题的回答中对此进行了介绍)。Pegex::RegexRegexp::Grammars

很容易上瘾。

于 2015-10-30T05:31:28.647 回答
0

这对于正则表达式来说很难。幸运的是,这不是你盒子里唯一的工具。

看起来每条记录之间都有一个空行。如果是这样,您可以通过设置$/为轻松做到这一点"\n\n"。然后您可以使用 while 循环读取文件,并且每次迭代$_都将设置为您尝试处理的块。

如果做不到这一点,您可以将其设置为,field =或者甚至只是使用split

于 2015-10-26T21:44:49.180 回答
0

这是微不足道的awk

$ awk -v RS= 'NR==1' file
field = "test string";
type =  INT;
funcCall(.., field, ...);
...
text = "desc";

使用段落模式,打印第一条记录。

于 2015-10-26T21:46:29.810 回答