Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find multi-line text & replace it, using regex, in shell script

I am trying to find a pattern of two consecutive lines, where the first line is a fixed string and the second has a part substring I like to replace.

This is to be done in sh or bash on macOS.

If I had a regex tool at hand that would operate on the entire text, this would be easy for me. However, all I find is bash's simple text replacement - which doesn't work with regex, and sed, which is line oriented.

I suspect that I can use sed in a way where it first finds a matching first line, and only then looks to replace the following line if its pattern also matches, but I cannot figure this out.

Or are there other tools present on macOS that would let me do a regex-based search-and-replace over an entire file or a string? Maybe with Python (v2.7 and v3 is installed)?

Here's a sample text and how I like it modified:

  keyA
  value:474
  keyB
  value:474    <-- only this shall be replaced (follows "keyB")
  keyC
  value:474
  keyB
  value:474

Now, I want to find all occurances where the first line is "keyB" and the following one is "value:474", and then replace that second line with another value, e.g. "value:888".

As a regex that ignores line separators, I'd write this:

  • Search: (\bkeyB\n\s*value):474
  • Replace: $1:888

So, basically, I find the pattern before the 474, and then replace it with the same pattern plus the new number 888, thereby preserving the original indentation (which is variable).

like image 479
Thomas Tempelmann Avatar asked Sep 02 '25 17:09

Thomas Tempelmann


2 Answers

You can use

sed -e '/keyB$/{n' -e 's/\(.*\):[0-9]*/\1:888/' -e '}' file
# Or, to replace the contents of the file inline in FreeBSD sed:
sed -i '' -e '/keyB$/{n' -e 's/\(.*\):[0-9]*/\1:888/' -e '}' file

Details:

  • /keyB$/ - finds all lines that end with keyB
  • n - empties the current pattern space and reads the next line into it
  • s/\(.*\):[0-9]*/\1:888/ - find any text up to the last : + zero or more digits capturing that text into Group 1, and replaces with the contents of the group and :888.

The {...} create a block that is executed only once the /keyB$/ condition is met.

See an online sed demo.

like image 151
Wiktor Stribiżew Avatar answered Sep 05 '25 09:09

Wiktor Stribiżew


Use a perl one-liner with -0777 to scan over multiple lines:

$ # inline edit:
$ perl -0777 -i -pe 's/\bkeyB\s*value):\d*/$1:888/' file.txt
$ # to stdout:
$ cat file.txt | perl -0777 -pe 's/\bkeyB\s*value):\d*/$1:888/'
like image 31
Peter Thoeny Avatar answered Sep 05 '25 08:09

Peter Thoeny