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

для положительных целых чисел:

a=305
b=15
echo $((a%b?a/b+1:a/b))
21
9
10.09.2020, 19:27
11 ответов

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

#!/bin/bash
    
sp="startpattern_here"
ep="endpattern_here"
f="file"
    
range=$(tac "$f" | grep -n "$sp\|$ep" | awk -F: -v sp="$sp" -v ep="$ep"\
        '$2 ~ sp && prev ~ ep {s=$1; print s,e; exit} {prev=$2; e=$1}')
    
if [[ "$range" ]]; then
    # echo "Counting from the end => start: ${range% *} end: ${range#* }"
    tail -n "${range% *}" "$f" | head -n "${range#* }"
else
    echo "No blocks found" 1>&2
fi

Объяснение и пример:

> cat file
startpattern_here
text
endpattern_here
startpattern_here
text
startpattern_here
42
endpattern_here
text
endpattern_here

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

1:endpattern_here
3:endpattern_here
5:startpattern_here
7:startpattern_here
8:endpattern_here
10:startpattern_here

, который передается в awk, чтобы решить, существует ли допустимый последний блок или нет. Обратите внимание, что здесь awkиспользуется для простого программирования, а не для фактической обработки текста. Для больших входных данных grepбыстрее, чем поиск в файле с помощью awkили даже больше, запись построчно с помощью awkили sed.

Кроме того, в случае обнаружения блока между шаблонами, быстро закрывающегося в конце, awkвыходит и закрывает свой конвейер, поэтому предыдущая последовательность также завершается без поиска по всему файлу.

Таким образом, мы получаем диапазон, считая с конца, и, наконец, tailи headищут ()эти номера строк и "котируют" содержимое. В случае пустого диапазона стандартного вывода нет.

startpattern_here
42
endpattern_here
1
18.03.2021, 23:05

Одним из способов было бы просто сохранить каждый набор, переопределить его следующим и распечатать тот набор, который вы сохранили, как только вы дойдете до конца:

awk '{ 
        if(/startpattern_here/){
            a=1; 
            lines=$0; 
            next
        } 
        if(a){
            lines=lines"\n"$0
        } 
        if(/end_pattern/){
            a=0
        }
    } 
    END{
        print lines
    }' file

Например, используя этот тестовый файл:

startpattern_here
line 1
line 2
line 3
end_pattern
startpattern_here
line 1b
line 2b
line 3b
end_pattern
startpattern_here
line 1c
line 2c
line 3c
end_pattern

Я понял:

$ awk '{ if(/startpattern_here/){a=1; lines=$0; next} if(a){lines=lines"\n"$0} if(/end_pattern/){a=0}} END{print lines}' file
startpattern_here
line 1c
line 2c
line 3c
end_pattern
1
18.03.2021, 23:05

Это может сработать, если вам нужна полная проверка регулярного выражения:

awk '/startpattern_here/ {buf="";f=1}
     f{buf=buf $0 "\n"}
     /endpattern_here/ {f=0; lastcomplete=buf}
     END{printf("%s",lastcomplete)}' file.txt

Это гарантирует, что будут распечатаны только полные шаблоны начала -остановки -.

Тестовый пример:

irrelevant
irrelevant
irrelevant
startpattern_here
relevant_but_dont_show_1
relevant_but_dont_show_1
relevant_but_dont_show_1
endpattern_here

irrelevant
irrelevant
 
startpattern_here
relevant_but_dont_show_2
relevant_but_dont_show_2
relevant_but_dont_show_2
endpattern_here

irrelevant
irrelevant

startpattern_here
relevant_and_show
relevant_and_show
relevant_and_show
endpattern_here

irrelevant
startpattern_here
incomplete_dont_show

Результат:

startpattern_here
relevant_and_show
relevant_and_show
relevant_and_show
endpattern_here

Примечание Если вы хотите подавить вывод начального и конечного шаблонов, просто поменяйте местами правила /startpattern_here/ {... }и /endpattern_here/ {... }, т.е. сначала поместите правило «конечный шаблон», а правило «начальный шаблон». непосредственно перед правилом END.

6
18.03.2021, 23:05

Кажется, я могу просто использоватьtac:

tac | sed -n '/endpattern_here/,/startpattern_here/ {p; /startpattern_here/q;}' | tac

Спасибо @glenn jackman и @Quasimodo за помощь в правильном вызове sed.

5
18.03.2021, 23:05

Комбинация tac и awk

tac file \
| awk '
   !p && /endpattern_here/   {p = 1}
    p                        {print}
    p && /startpattern_here/ {exit}
' \
| tac
7
18.03.2021, 23:05

С Ex(редактором POSIX ), который довольно прост:

printf '%s\n' 1 '?END?' '?START?,.p' | ex -s file
  • 1переходит к первой строке файла. Это необходимо в случае, если ENDявляется последней строкой файла.

  • ?END?ищет (в обратном направлении вокруг конца -файла -)для END, таким образом находя его последнее вхождение в файле.

  • ?START?,.pпечатает все от предыдущего STARTдо текущего адреса.

Ниже приведен пример с -документами вместо printfпросто для разнообразия.

$ cat file
zdk
START
b12
END
kdn
START
000
111
END
START
ddd
$ ex -s file <<EOF
> 1
> ?END?
> ?START?,.p
> EOF
START
000
111
END
6
18.03.2021, 23:05
  • Вы можете grepудалить последний диапазон, используя разновидность grep PCRE в режиме slurp.

    grep -zoP '(?ms).*\K^start.*?\nend[^\n]*' file | tr '\0' '\n'
    
  • Мы используем оператор диапазона в awkдля сохранения и восстановления после того, как мы начнем новый диапазон. Предполагая, что рядом с eof нет оборванной линии стартового шаблона.

    awk '
      /^start/,/^end/ {
        t = (/^start/ ? "" : t ORS) $0
      }
      END { print t }
    ' file
    
  • Здесь мы используем файл tacдля реверсирования, а затем оператор m??в Perl, который соответствует только один раз.

    < file tac \
    | perl -lne 'print if m?end?.. m?start?' \
    | tac;
    
  • Другие варианты

    < file sed -ne '/start/=;/end/='  \
    | sed -ne 'N;s/\n/,/;$s/$/p/p' \
    | sed -nf - file
    
    < file \
    tac | sed -e '/start/q' |
    tac | sed -e '/end/q'
    
    sed -e '
      /start/,/end/H
      /start/h;g;$q;d
    ' file
    
2
18.03.2021, 23:05
$ seq 20 > file
$ awk '/5/{rec=""; f=1} f{rec=rec $0 ORS; if (/8/) f=0} END{if (!f) printf "%s", rec}' file
15
16
17
18
0
18.03.2021, 23:05
 perl -ne '$x = (/startpattern/../endpattern/ ? $x. $_ : ""); $y=$x if $x and /endpattern/; END { print $y }'

Или, более читабельно (т.е. не на одной строке):

#!/usr/bin/perl -n

# save a set; could be incomplete
$x = /startpattern/../endpattern/
        ?   $x. $_
        :   ""
    ;

# save last complete set seen
if ($x and /endpattern/) {
    $y = $x;
}

# print last complete set seen, ignoring any incomplete sets that may have come after
END {
    print $y;
}

Который вы запускаете какperl./script < inputfile

1
18.03.2021, 23:05

Некоторые возможные решения:

:sed -z 's/.*\(StartPattern.*EndPattern[^\n]*\n\).*/\1\n/' file
:printf '%s\n' '1;kx' '?^End?;kx' "?^Start?;'xp" | ed -s file
:printf '%s\n' '1' '?^End?' "?^Start?,.p" | ex file
:awk '/^Start/{s=1;section=""}
s{section=section $0 ORS}
/^End/{complete=section;s=0}
END{printf ("%s",complete)}' file
:tac file | sed -n '/^End/,/^Start/{p;/^Start/q}' | tac


регулярное выражение sed

Вы можете сопоставить последнее вхождение шаблона между startи endс помощью регулярного выражения, например:

.*START.*END.*

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

.*\(START.*END\).*

Это будет работать в sed (, так как он может использовать замену s/// ), но требует, чтобы GNU sed сделал весь файл одной строкой (с помощью -опции z):

sed -z 's/.*\(StartPattern.*EndPattern[^\n]*\n\).*/\1\n/' file    

изд

Поиск назад в edвозможен с помощью ?regex?. Таким образом, мы можем выполнить поискEndPattern(в обратном направлении, чтобы убедиться, что шаблон завершен и мы находимся на последнем ), а затем выполнить поиск в обратном направлении к предыдущему StartPattern.

printf '%s\n' '?^End?;kx' '?^Start?;kx' '.;/End/p' | ed -s file

;kxиспользуется, чтобы избежать печати выбранной строки.

Это не удастся, если последняя строка будет End. Чтобы избежать этого, начните с первой строки и выполните поиск назад для End.

И, так как пределы отмечены, мы можем использовать более простой диапазон:

printf '%s\n' '1;ky' '?^End?;ky' '?^Start?;kx' "'x;'yp" | ed -s file

Или,

printf '%s\n' '1;kx' '?^End?;kx' "?^Start?;'xp" | ed -s file

Это предполагает, что по крайней мере один полный разделStart--Endсуществует. Если его нет, сценарий не работает.

Я видел несколько применений ?Start?,?End?. Это может потерпеть неудачу по нескольким причинам, потому что это не означает «найти следующееEndпосле того, что было найдено Start. Сравните:

$ printf '%s\n' 1 '?START?,?END?p' | ex -s <(printf '%s\n' 111 START 222 END 333 END 444)

START
222
END
333
END


$ printf '%s\n' 1 '?START?,/END/p' | ex -s <(printf '%s\n' 111 START 222 END 333 END 444)

START
222
END


# ex
The command from `ed` could be simplified to work in `ex`:

```printf '%s\n' '1' '?^End?' '?^Start?,.p' | ex file```


# awk
We can store each complete section `Start` to `End` in one variable and print it at the end.

авк ' /^Начало/{s=1;section=""} #Если есть начало, отметить раздел. s{section=section $0 ORS} #если внутри раздела, захватить все строки. /^End/{complete=section;s=0} #Если раздел заканчивается, снимите с него отметку, но сохраните. END{printf ("%s",complete )}' file #Напечатать полный раздел (, если он существует ).


# tac
We can reverse the whole file (line by line) and then print only the **first** section that starts at `End` and ends at `Start`. Then reverse again:

```tac file | sed -n '/^End/,/^Start/{p;/^Start/q}' | tac```

The `/^Start/q` exists sed to ensure that only the first section is printed.

Note that this will print everything from the last `End` to the start of the file if there is no `Start` to be found (instead of just not printing).


### test file
Tested with (at least) this file (and others):

$ cat файл3 Не печатать 1 Не печатать 2 Не печатать 3 Начальный образец _здесь -1 Внутренний узор, но не печатать 1 -1 Внутренний узор, но не печатать 1 -2 Внутренний узор, но не печатать 1 -3 EndPattern _здесь -1

Строки между 1 и 2 -1 Строки между 1 и 2 -2 Строки между 1 и 2 -3

StartPattern _здесь -2 Внутренний узор, но не печатать 2 -1 Внутренний узор, но не печатать 2 -2 Внутренний узор, но не печатать 2 -3 EndPattern _здесь -2

Строки между 2 и 3 -1 Строки между 2 и 3 -2 Строки между 2 и 3 -3

StartPattern _здесь -3 Выкройка внутри, пожалуйста, распечатайте 3 -1 Выкройка внутри, пожалуйста, распечатайте 3 -2 Выкройка внутри, пожалуйста, распечатайте 3 -3 EndPattern _здесь -3

Строки между 3 и 4 -1 Строки между 3 и 4 -2 Строки между 3 и 4 -3

StartPattern _здесь -4 Этот раздел имеет начало но не конец, таким образом, неполный. Строки между 4 и $ -1 Строки между 4 и $ -2 Строки между 4 и $ -3

1
18.03.2021, 23:05

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

Это обрабатывает текстовую строку -за -строкой, поэтому нам требуется память только для одной копии совпадающего блока, и мы не разветвляем и не выполняем другие вещи, которые потребовали бы еще большей дополнительной обработки. В качестве бонуса он вполне читабелен и понятен (ну насколько может быть любой sed скрипт ).

Вместо вашего:sed -n '/startpattern_here/,/endpattern_here/p'вы делаете это:

sed -n '/startpattern_here/,/endpattern_here/H; /startpattern_here/h; ${g;p}'

Пояснение (примечание :все после ;не зависит от предыдущих команд, если они не сгруппированы с {и}):

  • первая часть /startpattern_here/,/endpattern_here/Hв основном похожа на ту, что указана в вашем вопросе, но вместо того, чтобы напрямую печатать на стандартный вывод все, что находится между начальным и конечным шаблонами, вместо этого он добавляет этот текст в «удерживаемое пространство»(H).

  • /startpattern_here/hзамечает, когда начинается НОВОЕ совпадение, и стирает предыдущее пространство удержания, перезаписывая его(h)текущим пространством шаблона. Обратите внимание, что следующая строка в файле, конечно же, начнет выполнять все наши команды с нуля, что будет продолжать добавлять место для хранения (, см. пункт)-выше, в результате чего мы всегда будем хранить в пространстве для хранения только последний совпадающий блок.

  • Адрес
  • ${g;p}-$совпадает только с последней строкой в ​​файле, поэтому все, что находится между {и }, выполняется только тогда, когда мы закончим обработку файла. Здесь мы просто печатаем содержимое области хранения (путемg-копирования области хранения в пространство шаблонов,иp-печать пространства шаблона)

например, чтобы получить основную информацию о последнем пакете Debian:

% sed -n '/^Package/,/^Section/H; /^Package/h; ${g;p}' /var/lib/dpkg/status

Package: zsh-common
Status: install ok installed
Priority: optional
Section: shells

0
18.03.2021, 23:05

Теги

Похожие вопросы