Использование GNU awk или mawk:
$ awk '$1~"^"word{printf("--\n%s",$0)}' word='are' RS='--\n' infile
--
are you happy
--
are(you hungry
too
Это устанавливает слово переменной равным слову, которое должно соответствовать в начале записи, а разделитель записей RS ()равен ' --', за которым следует новая строка \n
. Потом,для любой записи, начинающейся со слова, совпадающего с ($1~"^"word
), выведите отформатированную запись. Формат начинается с '--' с новой строкой с точной найденной записью.
Использование (GNU для опции -z
)grep:
grep -Pz -- '--\nare(?:[^\n]*\n)+?(?=--|\Z)' infile
grep -Pz -- '(?s)--\nare.*?(?=\n--|\Z)\n' infile
grep -Pz -- '(?s)--\nare(?:(?!\n--).)*\n' infile
Описание (с )Для следующих описаний опция PCRE (?x)
используется для добавления (большого количества )поясняющих комментариев (и пробелов ), встроенных в реальное (рабочее )регулярное выражение. Если удалить комментарии (и большинство пробелов)(до следующей новой строки ), результирующая строка останется тем же регулярным выражением. Это позволяет подробно описать регулярное выражение в рабочем коде. Это значительно упрощает обслуживание кода.
(?x)--\nare(?:[^\n]*\n)+?(?=--|\Z)
(?x) # match the remainder of the pattern with the following
# effective flags: x
# x modifier: extended. Spaces and text after a #
# in the pattern are ignored
-- # matches the characters -- literally (case sensitive)
\n # matches a line-feed (newline) character (ASCII 10)
are # matches the characters are literally (case sensitive)
(?: # Non-Capturing Group (?:[^\n]*\n)+?
[^\n] # matches non-newline characters
* # Quantifier — Matches between zero and unlimited times, as
# many times as possible, giving back as needed (greedy)
\n # matches a line-feed (newline) character (ASCII 10)
) # Close the Non-Capturing Group
+? # Quantifier — Matches between one and unlimited times, as
# few times as possible, expanding as needed (lazy)
# A repeated capturing group will only capture the last iteration.
# Put a capturing group around the repeated group to capture all
# iterations or use a non-capturing group instead if you're not
# interested in the data
(?= # Positive Lookahead (?=--|\Z)
# Assert that the Regex below matches
# 1st Alternative --
-- # matches the characters -- literally (case sensitive)
| # 2nd Alternative \Z
\Z # \Z asserts position at the end of the string, or before
# the line terminator right at the end of the
# string (if any)
) # Closing the lookahead.
(?sx)--\nare.*?(?=\n--|\Z)\n
(?sx) # match the remainder of the pattern with the following eff. flags: sx
# s modifier: single line. Dot matches newline characters
# x modifier: extended. Spaces and text after a # in
# the pattern are ignored
-- # matches the characters -- literally (case sensitive)
\n # matches a line-feed (newline) character (ASCII 10)
are # matches the characters are literally (case sensitive)
.*? # matches any character
# Quantifier — Matches between zero and unlimited times,
# as few times as possible, expanding as needed (lazy).
(?= # Positive Lookahead (?=\n--|\Z)
# Assert that the Regex below matches
# 1st Alternative \n--
\n # matches a line-feed (newline) character (ASCII 10)
-- # matches the characters -- literally.
| # 2nd Alternative \Z
\Z # \Z asserts position at the end of the string, or
# before the line terminator right at
# the end of the string (if any)
) # Close the lookahead parenthesis.
\n # matches a line-feed (newline) character (ASCII 10)
(?xs)--\nare(?:(?!\n--).)*\n
(?xs) # match the remainder of the pattern with the following eff. flags: xs
# modifier x : extended. Spaces and text after a # in are ignored
# modifier s : single line. Dot matches newline characters
-- # matches the characters -- literally (case sensitive)
\n # matches a line-feed (newline) character (ASCII 10)
are # matches the characters are literally (case sensitive)
(?: # Non-capturing group (?:(?!\n--).)
(?! # Negative Lookahead (?!\n--)
# Assert that the Regex below does not match
\n # matches a line-feed (newline) character (ASCII 10)
-- # matches the characters -- literally
) # Close Negative lookahead
. # matches any character
) # Close the Non-Capturing group.
* # Quantifier — Matches between zero and unlimited times, as many
# times as possible, giving back as needed (greedy)
\n # matches a line-feed (newline) character (ASCII 10)
$ sed -nEe 'bend
:start ;N;/^--\nare/!b
:loop ;/^--$/!{p;n;bloop}
:end ;/^--$/bstart' infile
Следуя твоему подходу,
tac file|sed '/juice/{n;n;s/.*/coconut/}'|tac
/juice/
соответствует строке с juice
. n;n;
печатает текущую и следующую строку. s/.*/coconut/
производит замену. По-видимому, у вас есть GNU sed, поэтому вы также можете использовать -z
, чтобы загрузить весь файл в память и напрямую отредактировать вторую строку выше,
sed -rz 's/[^\n]*(\n[^\n]*\n[^\n]*juice)/coconut\1/' file
[^\n]
означает «не новая строка», а круглая скобка ()
захватывает группу, воспроизводимую ссылкой \1
назад -.
Если ed
верно& #xA0;нужно& #xA0;редактировать& #xA0;файл, не& #xA0;a& #xA0;потока, а& #xA0;есть только один& #xA0;juice
:
$ more <<-EOF | ed -s./tmp.txt
/juice/
-2
c
coconut
.
w
q
EOF
$
Найти& #xA0;линию, перейти на две строки& #xA0;вверх, c
повесить, w
обряд, и& #xA0; q
уит.
Предложен& #xA0;еще более& #xA0;компактный вариант by& #xA0;@d -ben -ручка в& #xA0;the& #xA0;комментарии:
$ printf '%s\n' '/^juice$/-2s/.*/coconut/' w q | ed -s./tmp.txt
$ tac file | awk 'c&&!(--c){$0="coconut"} /juice/{c=2} 1' | tac
coconut
apple
juice
mango
something
С bash, если ваш файл не большой:
mapfile -t lines < file
for (( i = 0; i < ${#lines[@]}; i++ )); do
if [[ ${lines[i]} == *"juice"* ]]; then
lines[i-2]="coconut"
break
fi
done
printf '%s\n' "${lines[@]}"
Если вы хотите совпадение без учета регистра , добавьте это перед циклом:
shopt -s nocasematch
С жемчугом:
perl -0777 -pe 's/^.*(?=\n.*\n.*juice)/coconut/m' file
Вот еще один awk
подход, на этот раз метод двойного -прохода:
awk 'NR==FNR&&/juice/{m=FNR} NR>FNR&&FNR==m-2{$0="coconut"} (NR>FNR)' file file
FNR
, счетчик на -строку файла -, равен NR
, общему счетчику строк ), мы просто определяем, в какой строке выполняется поиск. шаблон juice
и сохраните его в переменной m
. m-2
в текст замены coconut
. NR>FNR
оценивается как "истина" ). Если у вас есть GNUawk
(некоторые другие awk
реализации тоже поддерживают это )вы можете немного ускорить процесс, прервав первый проход, как только совпадение будет найдено с помощью команды nextfile
:
awk 'NR==FNR&&/juice/{m=FNR;nextfile} NR>FNR&&FNR==m-2{$0="coconut"} (NR>FNR)' file file
Для этой задачи можно использовать ex линейный редактор:
$ echo '/juice/-2s/.*/coconut/|x' | ex -s file
$ sed -Ee '
1N;$!N
s/.*(\n.*\n.*juice)/coconut\1/;t
P;D
' file
PS :не перед моим терминалом, поэтому они не проверены на синтаксис и/или некоторые проблемы.