简体   繁体   中英

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, eg "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).

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 .

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/'

In plain bash:

#!/bin/bash

keypattern='^[[:blank:]]*keyB$'
valpattern='(.*):'
replacement=888

while read -r; do
    printf '%s\n' "$REPLY"
    if [[ $REPLY =~ $keypattern ]]; then
        read -r
        if [[ $REPLY =~ $valpattern ]]; then
            printf '%s%s\n' "${BASH_REMATCH[0]}" "$replacement"
        else
            printf '%s\n' "$REPLY"
        fi
    fi
done < file

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM