для положительных целых чисел:
a=305
b=15
echo $((a%b?a/b+1:a/b))
21
Вот решение, пытающееся обработать все случаи, включая отсутствие печати для не найденного блока, и быть эффективным в отношении памяти и времени выполнения. В этом решении нет построчной записи, обработки каждой строки и буферизации строк.
#!/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
Одним из способов было бы просто сохранить каждый набор, переопределить его следующим и распечатать тот набор, который вы сохранили, как только вы дойдете до конца:
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
Это может сработать, если вам нужна полная проверка регулярного выражения:
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
.
Кажется, я могу просто использоватьtac
:
tac | sed -n '/endpattern_here/,/startpattern_here/ {p; /startpattern_here/q;}' | tac
Спасибо @glenn jackman и @Quasimodo за помощь в правильном вызове sed.
Комбинация tac и awk
tac file \
| awk '
!p && /endpattern_here/ {p = 1}
p {print}
p && /startpattern_here/ {exit}
' \
| tac
С 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
Вы можете 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
$ 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
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
Некоторые возможные решения:
сед: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
Вы можете сопоставить последнее вхождение шаблона между 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
Быстрое и простое 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