簡體   English   中英

Perl:正則表達式在重復模式之間獲取所有文本

[英]Perl: Regex to get all text between repeating patterns

我想為以下內容創建一個正則表達式。

我有一些如下文字:

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 =”字符串。 有什么建議么?

我能看到的最簡單方法是將$string split/^\\s*field\\s*=/ expression。 如果我們想要捕獲文本的'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
]

你可以使用哪個。

快速而骯臟的方法是定義一個主要與字段賦值匹配的正則表達式,然后在另一個正則表達式中使用它來匹配它們之間的內容。

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

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

這種方法的缺點是它可能匹配引用的字符串等。


您可以使用正則表達式解析代碼,但正確解析它超出了正常的正則表達式。 這是因為大量的平衡分隔符(即parens和braces)和轉義(即"<foo \\"bar\\"">" )。為了使它正確,你需要寫一個語法。

Perl 5.10增加了遞歸的正確匹配 ,使編寫語法成為可能。 他們還添加了命名捕獲組以跟蹤所有這些規則。 現在,您可以使用Perl 5.10正則表達式編寫遞歸語法。

它仍然有點笨重, Regexp :: Grammar增加了一些增強功能,使編寫正則表達式語法變得更加容易。

寫一個語法是從某個角度開始並填寫規則。 你的程序是一堆Statement 什么是聲明? 分配,或后跟一個FunctionCall ; 什么是作業? 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數組,查找Variable匹配field Assignment s。

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規則會處理大部分問題。

對於關於樣本數據的總體“whipupitude”,我認為將模式傳遞給split將是最簡單的。 但是,正如@Schwern指出的那樣,當使用語法變得更加復雜時會有所幫助。

為了好玩,我創建了一個示例腳本,它使用Pegex構建的解析表達式語法來解析數據。 在快速構建語法時, Regexp::GrammarRegexp::Common都具有廣泛使用和熟悉的優勢。 如果您已經了解perl並且需要為項目提供簡單但超級強大的正則表達式版本,則進入門檻較低。 Pegex的方法是嘗試使用 perl 輕松構造和使用語法 使用Pegex,您可以使用正則表達式構建解析表達式語法:

“Pegex ......通過將解析表達式語法(PEG)與常規表現(正則表達式)相結合而得名。這實際上是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";    

您當然可以以經典的perl方式輕松讀取文件句柄或STDIN中的數據,例如,使用IO::All我們可以做的事情:

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

您可以從CPAN安裝Pegex ,然后下載並使用要點來了解Pegex的工作原理。

使用Perl6,我們將獲得一個功能強大且易於使用的“語法引擎”,它基於Perl在處理正則表達式方面的優勢。 如果語法開始在更廣泛的項目中使用,這些開發必然會反饋到perl5並導致更強大的功能。

Pegex的PEG部分及其跨語言開發允許在不同的編程語言社區(Ruby,Javascript)之間交換語法。 Pegex可以在相當簡單的場景中使用,並且非常適合需要解析功能的更復雜的模塊。 Pegex API允許輕松創建可在“ 接收器類 ”中定義的規則派生函數集。 使用接收器類,您可以構建復雜的方法來處理解析后的數據,這些方法允許您“在解析時進行掃描”,甚至可以動態修改語法(!)更多可以重新使用和改進的工作語法示例而且越來越多的使用Pegex的模塊將有助於它變得更有用和更強大。

嘗試Pegex框架的最簡單方法可能是Pegex::Regex - 它允許您像使用Pegex::Regex一樣方便地使用語法,將解析結果存儲在%/ Pegex作者Pegex::Regex稱為解析表達式語法的“門戶葯物”,並指出它是“Damian Conway的Regexp::Grammars模塊API的克隆”(由@Schwern在回答這個問題時所涵蓋)。

它很容易被吸引住。

這對正則表達式來說很難。 幸運的是,這不是你的盒子里唯一的工具。

看起來每條記錄之間都有一個空行。 如果是這樣,您可以通過將$/設置$/ "\\n\\n"來輕松完成此操作。 然后,您可以使用while循環讀取文件,每次迭代$_將設置為您嘗試處理的塊。

如果失敗了,您可以將其設置為field =或者甚至可以使用split

這對於awk是微不足道的

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

使用段落模式,打印第一條記錄。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM