regex - Change text block with nearest search pattern - Stack Overflow

时间: 2025-01-06 admin 业界

I want to search and replace a textblock:

  • Find by start-pattern and end-pattern
  • copy&paste the found block with changing the end-pattern.

I tried with perl -0777 -pe 's/(section:[\s\S]*?"MARKER")/\$1 =~ s/"MARKER"/"NEW MARKER"/gr/e' input.txt (I know, it doesn't work as expected)

Curious about answers :-)

My textfile is like this:

active: true
sections:
- others: "a"
  othersa: "aa"
- section:
  - addPermissions: []
    attribute: "attrA"
    permission: "permA"
    a: "this is some text"
    value: "MARKER"
- section:
  - addPermissions: []
    a: "this is some text"
    value: "M7"
- section:
  - addPermissions: []
    attribute: "attrB"
    value: "MARKER"
- section:
  - addPermissions: []
    a: "this is some text"
    value: "M8"
- section:
  - addPermissions: []
    a: "this is some text"
    value: "M9"
- section:
  - addPermissions: []
    permission: "permC"
    d: "this is some text"
    value: "MARKER"

I want to search a block, where

  • starts with "- section:"
  • "- section:" is followed by "value: "MARKER""

The found block shall be copie&pasted behind itself having "MARKER" replaced by "NEW MARKER".

The result shall be like:

active: true
sections:
- others: "a"
  othersa: "aa"
- section:
  - addPermissions: []
    attribute: "attrA"
    permission: "permA"
    a: "this is some text"
    value: "MARKER"
- section:
  - addPermissions: []
    attribute: "attrA"
    permission: "permA"
    a: "this is some text"
    value: "NEW MARKER"
- section:
  - addPermissions: []
    a: "this is some text"
    value: "M7"
- section:
  - addPermissions: []
    attribute: "attrB"
    value: "MARKER"
- section:
  - addPermissions: []
    attribute: "attrB"
    value: "NEW MARKER"
- section:
  - addPermissions: []
    a: "this is some text"
    value: "M8"
- section:
  - addPermissions: []
    a: "this is some text"
    value: "M9"
- section:
  - addPermissions: []
    permission: "permC"
    d: "this is some text"
    value: "MARKER"
- section:
  - addPermissions: []
    permission: "permC"
    d: "this is some text"
    value: "NEW MARKER"

I want to search and replace a textblock:

  • Find by start-pattern and end-pattern
  • copy&paste the found block with changing the end-pattern.

I tried with perl -0777 -pe 's/(section:[\s\S]*?"MARKER")/\$1 =~ s/"MARKER"/"NEW MARKER"/gr/e' input.txt (I know, it doesn't work as expected)

Curious about answers :-)

My textfile is like this:

active: true
sections:
- others: "a"
  othersa: "aa"
- section:
  - addPermissions: []
    attribute: "attrA"
    permission: "permA"
    a: "this is some text"
    value: "MARKER"
- section:
  - addPermissions: []
    a: "this is some text"
    value: "M7"
- section:
  - addPermissions: []
    attribute: "attrB"
    value: "MARKER"
- section:
  - addPermissions: []
    a: "this is some text"
    value: "M8"
- section:
  - addPermissions: []
    a: "this is some text"
    value: "M9"
- section:
  - addPermissions: []
    permission: "permC"
    d: "this is some text"
    value: "MARKER"

I want to search a block, where

  • starts with "- section:"
  • "- section:" is followed by "value: "MARKER""

The found block shall be copie&pasted behind itself having "MARKER" replaced by "NEW MARKER".

The result shall be like:

active: true
sections:
- others: "a"
  othersa: "aa"
- section:
  - addPermissions: []
    attribute: "attrA"
    permission: "permA"
    a: "this is some text"
    value: "MARKER"
- section:
  - addPermissions: []
    attribute: "attrA"
    permission: "permA"
    a: "this is some text"
    value: "NEW MARKER"
- section:
  - addPermissions: []
    a: "this is some text"
    value: "M7"
- section:
  - addPermissions: []
    attribute: "attrB"
    value: "MARKER"
- section:
  - addPermissions: []
    attribute: "attrB"
    value: "NEW MARKER"
- section:
  - addPermissions: []
    a: "this is some text"
    value: "M8"
- section:
  - addPermissions: []
    a: "this is some text"
    value: "M9"
- section:
  - addPermissions: []
    permission: "permC"
    d: "this is some text"
    value: "MARKER"
- section:
  - addPermissions: []
    permission: "permC"
    d: "this is some text"
    value: "NEW MARKER"
Share Improve this question edited 8 hours ago Carsten asked yesterday CarstenCarsten 394 bronze badges 6
  • Is this YAML? If it is, you should use a parser. – TLP Commented yesterday
  • it is yaml, but I don't have enough skills to solve it this way :-( – Carsten Commented yesterday
  • 1 If you post the proper YAML input, someone might be able to fix it for you. But the YAML module isn't exactly complicated, just use and Load. I tried it with your current input, but it did not work properly. – TLP Commented yesterday
  • I had to edit the test data: all blocks start with "- section:". Due to this change the result has changed. – Carsten Commented yesterday
  • Well, the YAML parser dies on line 3, so... that is not valid yaml. – TLP Commented yesterday
 |  Show 1 more comment

3 Answers 3

Reset to default 2

Your code is:

perl -0777 -pe 's/(section:[\s\S]*?"MARKER")/\$1 =~ s/"MARKER"/"NEW MARKER"/gr/e' input.txt

This appears to be attempting a nested substitution.

With /e flag, RHS is actual code, so $ should not be escaped:

perl -0777 -pe 's/(section:[\s\S]*?"MARKER")/$1 =~ s/"MARKER"/"NEW MARKER"/gr/e' input.txt

Conversely, delimiters for nested s/// must be escaped (or different):

perl -0777 -pe 's/(section:[\s\S]*?"MARKER")/$1 =~ s\/"MARKER"\/"NEW MARKER"\/gr/e' input.txt

However, this replaces "section...MARKER" by just "NEW MARKER", which is not what seems to be wanted.


Instead, it is simpler to make use of the capture group directly:

perl -0777 -pe 's/(section:[\s\S]*?)"MARKER"/$1"NEW MARKER"/g' input.txt

or use a lookaround:

perl -0777 -pe 's/section:[\s\S]*?\K"MARKER"/"NEW MARKER"/g' input.txt

Thank you very much for providing examples and explanations! Finally I will use it like this (wrong search result):

perl -0777 -pe 's/(- section:[\s\S]*?)"MARKER"/$1"MARKER"\n$1"NEW MARKER 2"\n$1"NEW MARKER 3"/g' input.txt

My goal was to find the first block and duplicate it with different value: "MARKER".

Unfortunately, I had to adjust test data. Failure: block used as "$1" is much larger then expected. Block with value: "m7" has been duplicated, what was not intended.

Final result:

Some content ...
- section:
this is some text
this is some more text
value: "MARKER"
- section:
this is some text
this is some more text
value: "NEW MARKER 2"
- section:
this is some text
this is some more text
value: "NEW MARKER 3"
- section:
this is some text
this is some more text
value: "M7"
- section:
this is some text
this is some more text
value: "MARKER"
- section:
this is some text
this is some more text
value: "M7"
- section:
this is some text
this is some more text
value: "NEW MARKER 2"
- section:
this is some text
this is some more text
value: "M7"
- section:
this is some text
this is some more text
value: "NEW MARKER 3"
... content goes on

This matches the whole section by grabbing all the consecutive subsequent lines that are further indented than the first:

^ (\h+) - \h+ section: .* \n (?: \1 \h .* \n )*

Since we're getting the whole section, the marker doesn't need to be the last line of the section.


Putting that to use, we could do a search and replace in /e as you did.

s{
   ^ (\h+) - \h+ section: .* \n (?: \1 \h .* \n )*
}{
   my $section = $&;
   if ( $section =~ /\bMARKER\b/ ) {
      $section . ( $section =~ s/\bMARKER\b/NEWMARKER/gr )
   } else {
      $section
   }
}xmeg;

But that's a lot of extra matches which can be avoided as follows:

s{
   ( ^ (\h+) - \h+ section: .* \n
   (?: \2 \h .* \n )*
   \2 \h .* \b ) MARKER ( \b .* \n
   (?: \2 \h .* \n )* )
}{
   $& . $1 . "NEW MARKER" . $3
}xmeg;

Note that you can use perl -gpe'...' instead of perl -0777pe'...' since 5.36.