Как получить все строки между первым и последним вхождениями шаблонов?

Как я могу обрезать файл (поток ввода скважины), так что я получаю только строки от первого появления шаблона foo до последнего появления шаблона?

Например, рассмотрим следующий ввод:

 A line like foo this foo bar something something else foo bar and the rest 

Я ожидаю, что этот результат:

 foo this foo bar something something else foo bar 

5 Solutions collect form web for “Как получить все строки между первым и последним вхождениями шаблонов?”

 sed -n '/foo/{:a;N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba};' 

Соответствие шаблону sed /first/,/second/ читает строки один за другим. Когда какая-либо строка соответствует /first/ она запоминает ее и ожидает первого совпадения для /second/ pattern. В то же время он применяет все действия, указанные для этого шаблона. После этого процесс начинается снова и снова до конца файла.

Это не то, что нам нужно. Нам нужно найти последнее соответствие /second/ pattern. Поэтому мы строим конструкцию, которая выглядит только для первой записи /foo/ . При обнаружении начинается цикл a . Мы добавляем новую строку в буфер соответствия с N и проверяем, соответствует ли она шаблону /bar/ . Если это так, мы просто печатаем его и очищаем буфер совпадения и janyway прыгаем до начала цикла с ba .

Также нам нужно удалить символ новой строки после очистки буфера с помощью /^\n/s/^\n// . Я уверен, что есть намного лучшее решение, к сожалению, это мне не пришло в голову.

Надеюсь, все ясно.

Я бы сделал это с помощью небольшого Perl-вкладыша.

 cat <<EOF | perl -ne 'BEGIN { $/ = undef; } print $1 if(/(foo.*bar)/s)' A line like foo this foo bar something something else foo bar and the rest EOF 

доходность

 foo this foo bar something something else foo bar 

Вот двухпроходное решение GNU sed, которое не требует большой памяти:

 < infile \ | sed -n '/foo/ { =; :a; z; N; /bar/=; ba }' \ | sed -n '1p; $p' \ | tr '\n' ' ' \ | sed 's/ /,/; s/ /p/' \ | sed -n -f - infile 

объяснение

  • Первый вызов sed пропускает infile и находит первое появление foo и все последующие появления bar .
  • Эти адреса затем формируются в новый сценарий sed с двумя вызовами sed и одного tr . Выход третьего sed[start_address],[end_address]p без скобок.
  • Заключительный вызов sed снова передает infile , распечатывает найденные адреса и все между ними.

Если входной файл удобно помещается в память, сохраните его просто .

Если входной файл огромен, вы можете использовать csplit чтобы разбить его на куски при первом foo и на каждом последующем bar собрать сборки. Куски называются piece-000000000 , piece-000000001 и т. Д. Выберите префикс (здесь, piece- ), piece- не будет конфликтовать с другими существующими файлами.

 csplit -f piece- -n 9 - '%foo%' '/bar/' '{*}' <input-file 

(В системах, отличных от Linux, вам нужно будет использовать большое количество внутри фигурных скобок, например {999999999} , и передать параметр -k . Это число – количество элементов bar ).

Вы можете собрать все куски с cat piece-* , но это даст вам все после первого foo . Поэтому сначала удалите этот последний кусок. Поскольку имена файлов, созданных csplit , не содержат каких-либо специальных символов, вы можете их обработать без каких-либо специальных предостережений, например, с помощью

 rm $(echo piece-* | sed 's/.* //') 

или эквивалентно

 rm $(ls piece-* | tail -n 1) 

Теперь вы можете присоединиться ко всем частям и удалить временные файлы:

 cat piece-* >output rm piece-* 

Если вы хотите удалить фрагменты, поскольку они объединены для сохранения дискового пространства, сделайте это в цикле:

 mv piece-000000000 output for x in piece-?????????; do cat "$x" >>output; rm "$x" done 

Вот еще один способ с sed :

 sed '/foo/,$!d;H;/bar/!d;s/.*//;x;s/\n//' infile 

Он добавляет каждую строку в /foo/,$ range (строки ! Не в этом диапазоне, d eleted) в H старое пространство. Строки, не соответствующие bar , затем удаляются. В строках, которые соответствуют, пространство шаблонов опустошено, e x изменено с пространством удержания, а ведущая пустая строка в пространстве шаблонов удаляется.

С огромным входным и несколькими вхождениями в bar это должно быть (намного) быстрее, чем вытягивание каждой строки в пространство шаблонов, а затем, каждый раз, проверку пространства шаблонов для bar .
Разъяснение:

 sed '/foo/,$!d # delete line if not in this range H # append to hold space /bar/!d # if it doesn't match bar, delete s/.*// # otherwise empty pattern space and x # exchange hold buffer w. pattern space then s/\n// # remove the leading newline ' infile 

Конечно, если это файл (и подходит в памяти), вы можете просто запустить:

  ed -s infile<<'IN' .t. /foo/,?bar?p q IN 

потому что ed может искать вперед и назад.
Вы даже можете прочитать вывод команды в текстовый буфер, если ваша оболочка поддерживает замещение процесса:

 printf '%s\n' .t. /foo/,?bar?pq | ed -s <(your command) 

или если это не так, с gnu ed :

 printf '%s\n' .t. /foo/,?bar?pq | ed -s '!your command' 
  • Найти и заменить с помощью командной строки
  • Найти текст между вкладкой (\ t) в качестве разделителя
  • менее в сочетании с последовательными цветовыми последовательностями предотвращает разрывы страниц
  • избегать разрыва строки
  • Как добиться переносимости с помощью sed -i (редактирование на месте)?
  • Как найти значение из выражения
  • База данных библиотек
  • sed или tr однострочный, чтобы удалить все числовые цифры
  • Добавить строку в файл конфигурации из сценария bash?
  • vi - заменить символы из части строки
  • Sed: Заменить N первых вхождений персонажа
  • Как я могу отредактировать редактируемый результат?
  • Linux и Unix - лучшая ОС в мире.