2015-10-26 3 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 

В принципе я пытаюсь создать регулярное выражение, которое будет получать весь текст от начала первых «полей =» на старт второго " поле = ". Он должен пропустить текст поля, используемый в вызове функции.

я в настоящее время следующие:

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

Это зависит от того, что каждое 'поле = ...' имеет перед ним новую строку. Текст - это своего рода язык программирования, это предположение не будет выполнено. – Schwern

+0

'perl -00 -nE 'chomp; push @_, $ _;} {скажем $ _ [0] 'файл' с целью археологического сравнения :-) –

+0

'perl6 -e' say 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; 

Недостатком этого подхода является то, что он может совпадать с цитируемыми строками и тому подобным.


Вы можете сортировать разбор кода с регулярными выражениями, но разбор его правильно находится за пределами нормальных регулярных выражений. Это связано с большим количеством сбалансированных разделителей (т. Е. С парсером и скобками) и ускользает (то есть "<foo \"bar\"">"). Чтобы понять это правильно, вам нужно написать грамматику.

Perl 5.10 добавлено recursive decent matching, чтобы написать грамматику. Они также добавили named capture groups, чтобы отслеживать все эти правила. Теперь вы можете написать рекурсивную грамматику с регулярными выражениями Perl 5.10.

Это до сих пор неловкое, Regexp::Grammar добавляет некоторые улучшения, чтобы значительно упростить грамматику регулярных выражений.

Написание грамматики начинается с некоторого момента и заполнения правил. Ваша программа - куча Statement s. Что такое заявление?Назначение или 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 массив ищет 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

Самый простой способ, я могу видеть, чтобы сделать это split$string выражением /^\s*field\s*=/. Если мы хотим, чтобы захватить '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' в контексте void. Но да: 'say '\ $ _ = [\ n $ _]" для grep {m/\ S /} split/(? =^\ S * field \ s * =)/ms, строка $ корректна a действительный Perl однострочный. – Axeman

1

Для общей «безысходности» относительно ваших данных образца, я думаю, что прохождение шаблона до split будет самым простым. Но, как указывает @Schwern, когда все становится сложнее с помощью грамматики.

Для удовольствия я создал пример скрипта, который анализирует ваши данные с помощью текстовой выражения разбора , построенной с помощью PegexRegexp::Grammar и Regexp::Common есть преимущество широкого использования и знакомства, когда дело доходит до быстрого построения грамматики. Есть низкий барьер для входа, если вы уже знаете perl и нуждаетесь в простой, но сверхмощной версии регулярных выражений для вашего проекта. Подход Pegex состоит в том, чтобы попытаться установить . Легко построить и использовать grammars с perl. С Pegex вы строите разбор выражений грамматику из регулярных выражений:.

«Pegex ... получает это имя путем объединения PARSING Expression грамматик (PEG), с помощью регулярных Expessions (Regex) Это на самом деле то, что делает Pegex. " (from the POD).

Ниже приведен отдельный сценарий, который анализирует упрощенную версию ваших данных с использованием грамматики Pegex.


Первого сценарий считывает $grammar «встроенный» в качестве строки многострочных и использует его для ->parse() выборочных данных, которые он читает из <DATA> ручки. Обычно грамматика синтаксического анализа и данные будут находиться в отдельных файлах. Грамматика «atoms» и регулярные выражения скомпилированы с использованием функции 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";  

Конечно, вы можете так же легко считывать данные из файла ручки или STDIN в классический Perl моды или, например, с помощью IO::All, где мы могли бы сделать:

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

Вы можете установить Pegex от CPAN, а затем download and play with the gist, чтобы понять, как работает Pegex.

С помощью Perl6 мы получаем мощный и простой в использовании «грамматический движок», который основывается на сильных сторонах Perl при работе с регулярными выражениями. Если грамматики начнут использоваться в более широком спектре проектов, эти разработки обязательно вернутся к perl5 и приведут к еще более мощным функциям.

PEG часть Pegex и ее развитие перекрестного языка позволяет обменяться грамматиками между различными сообществами языков программирования (Ruby, Javascript). Pegex можно использовать в довольно простых сценариях и хорошо вписывается в более сложные модули, требующие возможности синтаксического анализа. API Pegex API позволяет легко создавать набор функций, основанный на правилах, который может быть определен в «receiver class». С классом приемника вы можете создавать сложные методы для работы с вашими анализируемыми данными, которые позволяют вам «махать во время разбора» и даже модифицировать грамматику «на лету» (!). Дополнительные примеры рабочих грамматик, которые могут быть повторно заданы и улучшены , а растущий выбор модулей, которые используют Pegex, поможет ему стать более полезным и мощным.

Возможно, самый простой подход к проверке структуры Pegex - Pegex::Regex - что позволяет использовать грамматики так же удобно, как регулярные выражения, сохраняя результаты вашего анализа в %/. author из Pegex вызывает Pegex::Regex «шлюзовое лекарственное средство» для анализа грамматических выражений выражения и отмечает, что это «клон модуля« Regexp::Grammars модуля »Дамиана Конвей» (в его ответе на этот вопрос содержится @Schwern).

Легко подключиться.

Смежные вопросы