[英]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
。 Variable
和Expression
什么? 等等...
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::Grammar
和Regexp::Common
都具有廣泛使用和熟悉的優勢。 如果您已經了解perl並且需要為項目提供簡單但超級強大的正則表達式版本,則進入門檻較低。 Pegex的方法是嘗試使用 perl 輕松構造和使用語法 。 使用Pegex,您可以使用正則表達式構建解析表達式語法:
“Pegex ......通過將解析表達式語法(PEG)與常規表現(正則表達式)相結合而得名。這實際上是Pegex所做的。” ( 來自POD )。
下面是一個獨立的腳本,它使用Pegex語法解析數據的簡化版本。
首先,腳本將$grammar
“inline”作為多行字符串讀出,並使用它來->parse()
從<DATA>
句柄讀取的樣本數據。 通常,解析語法和數據將駐留在單獨的文件中。 語法的“ 原子 ”和正則表達式使用pegex
函數編譯成“樹”或用於解析數據的正則表達式的哈希。 parse()
方法返回perl可以使用的數據結構。 在腳本中添加use DDP
和p $ast
可以幫助您查看語法返回的結構( AoH , HoH等)。
#!/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.