2015-10-26 27 views
3

我想爲以下創建一個正則表達式。Perl:正則表達式來獲取重複模式之間的所有文本

我有一個像下面的一些文字:

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 =「。它必須跳過函數調用中使用的字段文本。

目前,我有以下幾點:

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

然而,這只是獲得文本 「域=」。沒有「?」它從首先到最後一次獲取所有數據。

我也試過:

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

然而,那會讓我所有其他情況下,因爲它是佔有慾第二個「域=」字符串。有什麼建議麼?

+0

這類問題很難用正則表達式。我會建議使用[Parse :: RecDescent](https://metacpan.org/pod/Parse::RecDescent)或[Regexp :: Grammars](https://metacpan.org/pod/Regexp: :文法)。 – Schwern

+0

你只能用[脾氣暴躁的貪婪牌]做這種事情(https://regex101.com/r/fC8bS2/1)。不允許跳過,除非您想使用兩個預讀並捕獲一些不連續的文本塊。 –

+2

這很容易用正則表達式來完成。問題是你的例子展示了一種複雜的語言。沒有細節,對於那種特定語言的解析器來說似乎更合適。 – sln

回答

0

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

看起來你在每條記錄之間有一條空行。如果是這樣,您可以通過將$/設置爲"\n\n"來輕鬆完成此操作。然後你可以用while循環讀你的文件,並且每個迭代$_將被設置爲你正在處理的塊。

如果做不到這一點,你可以將其設置爲field =或者甚至只是用split

+1

'$ /'很聰明,但它僅限於字符串。文本看起來像某種編程語言,所以'field'和'='之間的空格可能會改變。 – Schwern

+1

我正在處理空白行分隔的樣本數據。你可能是對的,但鑑於我們必須合作,這可能也是矯枉過正。 – Sobrique

+0

當然!畢竟perl開始對'awk'進行改進:-)嗯,但是'perl -MData :: Dumper -ne'$/=「\ n \ n」; push @arr,[$ _];} {print Dumper @arr'data.txt'雖然給我一個額外的分割(第一個'field'行自行結束)。 –

0

這是瑣碎awk

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

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

+0

這依賴於每個'field = ...'恰好在它前面有一個換行符。文本是某種編程語言,這個假設不會成立。 – Schwern

+0

'perl -00 -nE'chomp; push @_,$ _;} {說$ _ [0]'文件'爲考古比較的目的:-) –

+0

'perl6 -e'說slurp.split(「\ n \ n」)[0]'文件'...考古學和進化適應。 –

2

快速和骯髒的方法是定義一個大多匹配字段分配的正則表達式,然後在另一個正則表達式中使用它來匹配它們之間的內容。

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

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

這種方法的不足之處是它可能會匹配引用的字符串等。


您可以排序的解析與正則表達式的代碼,但它的解析正確超出正常的正則表達式。這是因爲大量的平衡分隔符(即,parens和大括號)和逃逸(即"<foo \"bar\"">")。要做到這一點,你需要編寫一個語法。

Perl 5.10增加了recursive decent matching使寫文法成爲可能。他們還添加了named capture groups以跟蹤所有這些規則。現在,您可以使用Perl 5.10正則表達式編寫遞歸語法。

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

寫一個語法是關於從某個點開始並填入規則。你的程序是一堆Statement s。什麼是聲明?一個Assignment或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尋找Assignment爲什麼會產生Variable比賽field

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

+1

++優秀的文章解決某些編程任務是微不足道的。謝謝!您認爲PEG方法和['Pegex'](https://metacpan.org/pod/distribution/Pegex/lib/Pegex.pod)在CPAN上的實現與'Regexp :: Grammar'相比如何?我的文章僅僅是一個簡短的介紹,但我想知道Perl6的語法使用是否會影響perl5在使用語法來提高正則表達式的能力方面的發展。 –

+0

@ G.Cito我對使用哪種語法庫沒有意見。我沒有太多的經驗,這是我第一次使用Regexp :: Grammar,首先我看過Pegex。我認爲Perl 6意味着我們將在Perl 5中看到更多語法的使用,它已經發生,並且還有更多的人意識到Perl 5命名和遞歸模式。但我一直沒有跟上時事。 – Schwern

4

我可以看到由/^\s*field\s*=/表達要做到這一點是split$string最簡單的方法。如果我們想捕捉文本的'field = '部分,我們可以在look-ahead突破:

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 
] 

,你可以工作。

+1

這是基於正則表達式的最佳答案。 – Schwern

+0

++當在函數中構建古老而謙虛(比如[split()'](http://perldoc.perl.org/functions/split.html)等等)以正則表達式作爲參數時, ** **一線**獲得巨大的權力。你*可以把它全部放在一行......對嗎? :-) –

+0

@ G.Cito,好吧,我認爲'grep {m/\ S /} split /(?=^\ s * field \ s * =)/ ms,$ string'就是它的一部分。這一切都在一條線上。 for循環只顯示它。我不得不說,我不喜歡結構化循環的後綴,我很少在無效的情況下使用'map'。但是,對於grep {m/\ S /} split /(?=^\ s * field \ s * =)/ ms,'$ say'\ $ _ = [\ n $ _]「,$ string'是有效的有效的Perl單行程。 – Axeman

1

對於您的樣本數據的總體「whipupitude」,我認爲傳遞一個模式到split將是最容易的。但是,正如@Schwern指出的那樣,當使用語法變得更復雜時,情況會有所幫助。

爲了好玩,我創建了一個示例腳本,它使用解析表達式語法解析數據,該解析表達語法使用Pegex構建。當涉及到快速構建語法時,Regexp::GrammarRegexp::Common都具有廣泛使用和熟悉的優點。如果你已經知道perl,並且需要一個簡單但超級強大的正則表達式版本,那麼你的入門門檻很低。 Pegex方法是嘗試使其易於構建和使用與perl grammars。隨着Pegex你建立一個解析表達式語法出來的正則表達式:

「Pegex ...得到它通過組合解析表達式語法(PEG), 與普通Expessions(正則表達式)名稱是Pegex並不實際的東西。 「 (from the POD)。

下面是一個獨立的腳本,它使用Pegex語法分析數據的簡化版本。


首先腳本讀出$grammar「內聯」作爲多行字符串和它用來->parse()它從<DATA>手柄讀取樣本數據。通常解析語法和數據將駐留在單獨的文件中。語法的「atoms」和正則表達式使用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然後download and play with the gist瞭解Pegex如何工作。

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

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

也許最簡單的嘗試Pegex框架的方法是Pegex::Regex - 它允許您像使用正則表達式一樣方便地使用語法,將解析結果存儲在%/中。 Pegexauthor調用Pegex::Regex解析表達式語法的「網關藥物」,並指出它是「Damian Conway的Regexp::Grammars模塊API的克隆」(在他對這個問題的回答中覆蓋了@Schwern)。

很容易被鉤住。

相關問題