Как получить несколько строк из файла с помощью регулярных выражений?
Я часто хотел бы получить несколько строк / изменить несколько строк с помощью регулярных выражений. Пример дела:
Я пытаюсь прочитать часть файла XML / SGML (они не обязательно хорошо сформированы или имеют предсказуемый синтаксис, поэтому регулярное выражение будет более безопасным, чем правильный синтаксический анализатор. Кроме того, я хотел бы иметь возможность сделать это также полностью неструктурированные файлы, в которых известны только некоторые ключевые слова.) в сценарии оболочки (работающем в Solaris и Linux).
Пример XML:
<tag1>
<tag2>bar</tag2>
</tag1>
<tag1>
<tag2>foo</tag2>
</tag1>
Из этого я хотел бы прочитать, <tag1>
если он содержит foo
где-то в нем.
Подобное регулярному выражению (<tag1>.*?foo.*?</tag1>)
должно дать правильную часть, но инструменты, подобные grep
и sed
только для меня, работают с единственными строками. Как я могу получить
<tag1>
<tag2>foo</tag2>
</tag1>
в этом примере?
Ответы:
Если у вас есть GNU Grep установлена , вы можете сделать многострочный поиск пропускания в
-P
(Perl-регулярные выражения) и флаге активацииPCRE_DOTALL
с(?s)
Если вышеописанное не работает на вашей платформе, попробуйте передать
-z
флаг дополнительно, это заставит grep рассматривать NUL как разделитель строк, в результате чего весь файл будет выглядеть как одна строка.источник
(?s)
совет(GNU grep) 2.14
в Debian. Я скопировал пример OP как есть (добавив только последний перевод строки) и запустил ваш,grep
но не получил результатов.grep -ozP
вместоgrep -oP
ваших платформ?Если вы сделаете выше, учитывая данные, которые вы показываете, перед последней строкой очистки, вы должны работать с
sed
пространством шаблона, которое выглядит следующим образом:Вы можете распечатать свое пространство шаблона в любое время с помощью
l
Ook. Затем вы можете обратиться по\n
символам.Покажет вам каждая строка
sed
обрабатывает ее на этапе, на которомl
вызывается.Итак, я только что проверил его, и он нуждался в еще одном
\backslash
после,comma
первой строки, но в остальном работает как есть. Здесь я поместил это в_sed_function
так, чтобы я мог легко вызвать это для демонстрационных целей в течение этого ответа: (работает с включенными комментариями, но здесь удален ради краткости)Теперь мы переключим параметр
p
на,l
чтобы мы могли видеть, с чем мы работаем, когда мы разрабатываем наш сценарий, и удаляем неоперационную демонстрацию,s?
чтобы последняя строка нашегоsed 3<<\SCRIPT
кода выглядела так:Тогда я запустлю это снова:
ОК! Так что я был прав - это хорошее чувство. Теперь давайте перетасуем наш
l
ook, чтобы увидеть строки, которые он вытягивает, но удаляет. Мы удалим наш текущийl
и добавим один к!{block}
так, чтобы это было похоже на:Вот как это выглядит перед тем, как мы уничтожим это.
И последнее, что я хочу показать вам, это
H
старое пространство, в котором мы его строим. Есть пара ключевых концепций, которые я надеюсь продемонстрировать. Поэтому яl
снова удаляю последний ook и изменяю первую строку, чтобы добавить заглядывание вH
старое пространство в конце:H
старое пространство переживает линейные циклы - отсюда и название. Так что люди часто сбиваются с толку - хорошо, то, что я часто сбиваю с толку - это то, что его нужно удалить после того, как вы его используете. В этом случае я могуx
изменить только один раз, поэтому пространство удержания становится пространством шаблона и наоборот, и это изменение также сохраняется в циклах строк.Эффект заключается в том, что мне нужно удалить мое пространство удержания, которое раньше было моим пространством образца. Я делаю это, сначала очистив пространство текущего шаблона с помощью:
Который просто выбирает каждого персонажа и удаляет его. Я не могу использовать,
d
потому что это завершит мой текущий цикл строки, а следующая команда не будет выполнена, что в значительной степени испортит мой сценарий.Это работает аналогично,
H
но перезаписывает пространство удержания, поэтому я просто скопировал пустое пространство шаблона поверх моего пространства удержания, фактически удалив его. Теперь я могу просто:вне.
И вот как я пишу
sed
сценарии.источник
Ответ @ jamespfinn будет отлично работать, если ваш файл такой же простой, как ваш пример. Если у вас более сложная ситуация, в которой
<tag1>
может быть больше двух строк, вам понадобится немного более сложный прием. Например:Сценарий Perl обработает каждую строку вашего входного файла и
if(/<tag1>/){$a=1;}
: переменная$a
устанавливается на,1
если<tag1>
найден открывающий тег ( ).if($a==1){push @l,$_}
: для каждой строки, если$a
есть1
, добавить эту строку в массив@l
.if(/<\/tag1>/)
: если текущая строка соответствует закрывающему тегу:if(grep {/foo/} @l){print "@l"}
: если какая-либо из строк, сохраненных в массиве@l
(это строки между<tag1>
и</tag1>
), совпадает со строкойfoo
, выведите содержимое@l
.$a=0; @l=()
: очистить список (@l=()
) и установить$a
значение 0.источник
<tag1>
с,foo
и он отлично работает. Когда это терпит неудачу для вас?Вот
sed
альтернатива:объяснение
-n
означает не печатать строки, если не указано/<tag1/
сначала соответствует открывающему тегу:x
это метка, позволяющая перейти к этой точке позжеN
добавляет следующую строку в пространство шаблона (активный буфер)./<\/tag1/!b x
означает, что если текущее пространство шаблона не содержит закрывающего тега, переход кx
метке, созданной ранее. Таким образом, мы продолжаем добавлять строки в пространство шаблона, пока не найдем наш закрывающий тег./foo/p
означает, что если текущее пространство образца совпадаетfoo
, оно должно быть напечатано.источник
Я думаю, вы могли бы сделать это с помощью GNU awk, рассматривая конечный тег как разделитель записей, например, для известного конечного тега
</tag1>
:или в более общем случае (с регулярным выражением для конечного тега)
Тестирование на @ terdon's
foo.xml
:источник
Если ваш файл структурирован точно так, как вы показали выше, вы можете использовать флаги -A (строки после) и -B (строки до) для grep ... например:
Если ваша версия
grep
поддерживает это, вы также можете использовать более простой-C
(для контекста) параметр, который печатает окружающие N строк:источник
tail -3 input_file.xml
. Да, это работает для этого конкретного примера, но это не полезный ответ на вопрос.