Заменить n-ю строку из совпадающего шаблона

АВК

Использование 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)используется для добавления (большого количества )поясняющих комментариев (и пробелов ), встроенных в реальное (рабочее )регулярное выражение. Если удалить комментарии (и большинство пробелов)(до следующей новой строки ), результирующая строка останется тем же регулярным выражением. Это позволяет подробно описать регулярное выражение в рабочем коде. Это значительно упрощает обслуживание кода.

Вариант 1 регулярного выражения(?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.

Вариант 2 регулярного выражения(?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)
 

Вариант 3 регулярного выражения(?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
4
11.08.2020, 22:47
6 ответов

Следуя твоему подходу,

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назад -.

3
18.03.2021, 23:13

Если 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
6
18.03.2021, 23:13
$ tac file | awk 'c&&!(--c){$0="coconut"} /juice/{c=2} 1' | tac
coconut
apple
juice
mango
something
3
18.03.2021, 23:13

С 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
2
18.03.2021, 23:13

Вот еще один 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
2
18.03.2021, 23:13

Для этой задачи можно использовать ex линейный редактор:

$ echo '/juice/-2s/.*/coconut/|x' | ex -s file

$ sed -Ee '
    1N;$!N
    s/.*(\n.*\n.*juice)/coconut\1/;t
    P;D
' file 

PS :не перед моим терминалом, поэтому они не проверены на синтаксис и/или некоторые проблемы.

0
18.03.2021, 23:13

Теги

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