Извлечение определенного шаблона из строк с помощью sed, awk или perl

Могу ли я использовать sed, если мне нужно извлечь шаблон, заключенный в определенный шаблон, если он существует в строке?

Предположим, у меня есть файл со следующими строками:

Многие не осмеливаются убить себя из-за [/страха/] того, что скажут соседи.

Совет — это то, о чем мы просим, ​​когда уже знаем /* ответ */, но хотели бы этого не знать.

В обоих случаях я должен сканировать строку для первого встречающегося шаблона, т.е. «[/» или «/*» в соответствующих случаях, и сохранять следующий шаблон до тех пор, пока не выйдет шаблон, т.е. «/]» или «*/» соответственно.

Короче говоря, мне нужны fear и answer. Если возможно, можно ли его расширить для нескольких строк; в том смысле, если шаблон выхода происходит в строке, отличной от той же.

Любая помощь в виде предложений или алгоритмов приветствуется. Заранее спасибо за ответы


person Gil    schedule 19.06.2012    source источник
comment
Я не совсем уверен, можно ли это сделать с помощью SED, и, кстати, я бы не возражал против Perl-скрипта.   -  person Gil    schedule 19.06.2012
comment
Что касается sed, см. мои однострочный">вопрос: простых путей пока не предложено, но кое-что можно сделать.   -  person Lev Levitsky    schedule 19.06.2012
comment
@LevLevitsky Довольно интересно! Обязательно пересмотрю еще раз, одного раза мало. Спасибо за добавление ссылки :)   -  person Gil    schedule 20.06.2012


Ответы (3)


use strict;
use warnings;

while (<DATA>) {
    while (m#/(\*?)(.*?)\1/#g) {
        print "$2\n";
    }
}


__DATA__
There are many who dare not kill themselves for [/fear/] of what the neighbors will say.
Advice is what we ask for when we already know the /* answer */ but wish we didn’t.

Как однострочный:

perl -nlwe 'while (m#/(\*?)(.*?)\1/#g) { print $2 }' input.txt

Внутренний цикл while будет перебирать все совпадения с модификатором /g. Обратная ссылка \1 гарантирует, что мы сопоставляем только идентичные открытые/закрытые теги.

Если вам нужно сопоставить блоки, которые занимают несколько строк, вам нужно проглотить ввод:

use strict;
use warnings;

$/ = undef;
while (<DATA>) {
    while (m#/(\*?)(.*?)\1/#sg) {
        print "$2\n";
    }
}

__DATA__
    There are many who dare not kill themselves for [/fear/] of what the neighbors will say. /* foofer */ 
    Advice is what we ask for when we already know the /* answer */ but wish we didn’t.
foo bar /
baz 
baaz / fooz

Один лайнер:

perl -0777 -nlwe 'while (m#/(\*?)(.*?)\1/#sg) { print $2 }' input.txt

Переключатель -0777 и $/ = undef вызовут глотание файла, что означает, что весь файл читается в скаляр. Я также добавил модификатор /s, чтобы подстановочный знак . соответствовал новой строке.

Объяснение регулярного выражения: m#/(\*?)(.*?)\1/#sg

m#              # a simple m//, but with # as delimiter instead of slash
    /(\*?)      # slash followed by optional *
        (.*?)   # shortest possible string of wildcard characters
    \1/         # backref to optional *, followed by slash
#sg             # s modifier to make . match \n, and g modifier 

«Магия» здесь в том, что обратная ссылка требует звездочки * только тогда, когда она находится перед ней.

person TLP    schedule 19.06.2012
comment
Будет ли он соответствовать нескольким строкам? - person Zaid; 19.06.2012
comment
Хорошая работа, хотя ваше регулярное выражение немного болит в моих глазах :) - person Zaid; 19.06.2012
comment
@Zaid Это так кисло, как должно быть: P - person TLP; 19.06.2012
comment
@TLP Хотя мне это немного сложно переварить, в моем случае он работает без сбоев :) и объяснение просто отличное! Большое спасибо программисту высшего уровня ;) - person Gil; 20.06.2012
comment
@Geekasaur Это не то, что означает мой ник. :) Если это отвечает на ваш вопрос, вы должны нажать на галочку, чтобы отметить его как принятый. - person TLP; 20.06.2012
comment
@TLP Готово! Просто небольшой вопрос. Можно ли инвертировать результат, как при отображении несоответствующей части? - person Gil; 20.06.2012
comment
@Гиказавр Да. Измените регулярное выражение на замену, которая удаляет совпадения, и напечатайте строку вместо $2. Например. s#/(\*?)(.*?)\1/##sg; print; для последнего однострочника. - person TLP; 20.06.2012
comment
@TLP Ну, вот и все! Спасибо еще раз :) - person Gil; 20.06.2012
comment
давайте продолжим это обсуждение в чате - person Gil; 20.06.2012
comment
@TLP Извините, что снова беспокою вас. TLP. Можно ли удалить точное место, занимаемое совпадающим шаблоном, на дисплее несоответствующей части? - person Gil; 21.06.2012
comment
@Geekasaur Звучит именно так, как вы меня только что спросили. Как инвертировать матч. - person TLP; 21.06.2012
comment
@ Ага ! Согласен, хочу убрать "пустоту" или "пробел", занимаемый спичкой в ​​перевернутом случае. В примере при выполнении обратного совпадения получаем Многие не посмеют убить себя из-за |пустого места| того, что скажут соседи. Я хочу удалить место, оставленное матчем. - person Gil; 21.06.2012
comment
@Geekasaur Это потому, что у вас есть одно дополнительное место. Вы всегда можете решить эту проблему, вставив ` * ` (это пробел, за которым следует звездочка) до и после совпадения, а также вставив один пробел в замену. s#/ *(\*?)(.*?)\1 */# #sg; - person TLP; 21.06.2012

Быстрый и грязный путь в awk

awk 'NF{ for (i=1;i<=NF;i++) if($i ~ /^\[\//) { print gensub (/^..(.*)..$/,"\\1","g",$i); } else if ($i ~ /^\/\*/) print $(i+1);next}1' input_file

Тестовое задание:

$ cat file
There are many who dare not kill themselves for [/fear/] of what the neighbors will say.

Advice is what we ask for when we already know the /* answer */ but wish we didn't.
$ awk 'NF{ for (i=1;i<=NF;i++) if($i ~ /^\[\//) { print gensub (/^..(.*)..$/,"\\1","g",$i); } else if ($i ~ /^\/\*/) print $(i+1);next}1' file
fear

answer
person jaypal singh    schedule 19.06.2012

Однострочные совпадения

Если вы действительно хотите сделать это в sed, вы можете относительно легко извлечь шаблоны с разделителями, если они находятся в одной строке.

# Using GNU sed. Escape a whole lot more if your sed doesn't handle
# the -r flag.
sed -rn 's![^*/]*(/\*?.*/).*!\1!p' /tmp/foo

Многострочные совпадения

Если вы хотите выполнять многострочные совпадения с помощью sed, все становится немного уродливее. Однако это, безусловно, можно сделать.

# Multi-line matching of delimiters with GNU sed.
sed -rn ':loop
         /\/[^\/]/ { 
             N
             s![^*/]+(/\*?.*\*?/).*!\1!p
             T loop
         }' /tmp/foo

Хитрость заключается в том, чтобы искать начальный разделитель, а затем продолжать добавлять строки в цикле, пока не найдете конечный разделитель.

Это работает очень хорошо, если у вас действительно есть конечный разделитель. В противном случае содержимое файла будет добавляться к пространству шаблонов до тех пор, пока sed не найдет его или пока не достигнет конца файла. Это может вызвать проблемы с некоторыми версиями sed или с очень большими файлами, где размер пространства шаблонов выходит из-под контроля.

См. Ограничения и неограничения GNU sed для получения дополнительной информации.

person Todd A. Jacobs    schedule 20.06.2012