Sed: Find Pattern Over Two Lines, Not Replace After That Pattern

sed: Find pattern over two lines, not replace after that pattern

Try this:

sed "h; :b; \$b ; N; /^${1}\n     n/ {h;x;s//Noun\n/; bb}; \$b ; P; D"

Unfortunately, Paul's answer reads the whole file in which makes any additional processing you might want to do difficult. This version reads the lines in pairs.

By enclosing the sed script in double quotes instead of single quotes, you can include shell variables such as positional parameters. I would recommend surrounding them with curly braces so they are set apart from the adjacent characters. When using double quotes, you'll have to be careful of the shell wanting to do its various expansions. In this example, I've escaped the dollar signs that signify the last line of the input file for the branch commands. Otherwise the shell will try to substitute the value of a variable $b which is likely to be null thus making sed unhappy.

Another technique would be to use single quotes and close and open them each time you have a shell variable:

sed 'h; :b; $b ; N; /^'${1}'\n     n/ {h;x;s//Noun\n/; bb}; $b ; P; D'
# ↑open close↑ ↑open close↑

I'm assuming that the "[/code]" in your expected result is a typo. Let me know if it's not.

How to replace 2 consecutive lines after Match with Sed

gnu and some other sed versions allow you to grab a range using relative number so you can simply use:

sed -E '/^#ABC$/,+2 s/(Size=)bar/\1foo/' file
#ABC
oneSize=foo
twoSize=foo
threeSize=foo

Command details:

  • /^#ABC$/,+2 Match range from pattern #ABC to next 2 lines
  • s/(Size=)bar/\1foo/: Match Size=bar and replace with Size=foo, using a capture group to avoid repeat of same String in search and substitution

You may also consider awk to avoid repeating pattern and replacements N times if you have to replace N lines after matching pattern:

awk 'n-- {sub(/Size=bar/, "Size=foo")} /^#ABC$/ {n=2} 1' file

#ABC
oneSize=foo
twoSize=foo
threeSize=foo

Sed replace on a pattern after another specific pattern match?

I found an answer to my own question using an address range:

sed -i -r '/header3/,/pattern/ s|pattern|replacement|' filename

Use sed to replace patterns that are not at the start of end of lines

You can translate all slashes to + and then replace + (at the beginning or at the end) with a slash:

sed 'y/\//+/;s/^+\|+$/\//g;'

or if the OR operator isn't available:

sed 'y/\//+/;s/^+/\//;s/+$/\//;'

better if you change the delimiter to avoid to escape all literal slashes:

sed 'y~/~+~;s~^+\|+$~/~g;'

or if the OR operator isn't available:

sed 'y~/~+~;s~^+~/~;s~+$~/~;'

(where ^ is an anchor for the start of the line and $ for the end)


Other way: you can protect the slashes you want to preserve using a placeholder:

sed 's~^/~{`%{~;s~/$~{`%{~;y~/~+~;s~{`%{~/~g;'

Search and replace a multi-line pattern with sed

There are a variety of options — the i, c, and a commands can all be used.

Amended answer

This amended answer deals with the modified data file now in the question. Here's a mildly augmented version of the modified data file:

There's material at the start of the file then the key information:
Favourite Animals
Monkey
Penguin
Cat
!
Favourite Things
Shoe
Wheel
Moth
!
and some material at the end of the file too.

All three of these sed scripts produce the same output:

sed '/Favourite Animals/,/!/c\
Favourite Animals\
Sloth\
Platypus\
Badger\
!
' data

sed '/Favourite Animals/i\
Favourite Animals\
Sloth\
Platypus\
Badger\
!
/Favourite Animals/,/!/d' data

sed '/Favourite Animals/a\
Favourite Animals\
Sloth\
Platypus\
Badger\
!
/Favourite Animals/,/!/d' data

Sample output:

There's material at the start of the file then the key information:
Favourite Animals
Sloth
Platypus
Badger
!
Favourite Things
Shoe
Wheel
Moth
!
and some material at the end of the file too.

It is crucial that the scripts all use the unique string, /Favourite Animals/ and do not key off the repeated trailing context /!/. If the i or a use /!/ instead of /Favourite Animals/, the outputs change — and not for the better.

/!/i:

There's material at the start of the file then the key information:
Favourite Animals
Sloth
Platypus
Badger
!
Favorite Things
Shoe
Wheel
Moth
Favourite Animals
Sloth
Platypus
Badger
!
!
and some material at the end of the file too.

/!/a:

There's material at the start of the file then the key information:
Favourite Animals
Sloth
Platypus
Badger
!
Favorite Things
Shoe
Wheel
Moth
!
Favourite Animals
Sloth
Platypus
Badger
!
and some material at the end of the file too.

Extra request

Would it be possible to select a range within a range using sed? Basically, what if I wanted to change or remove one/many of my favorite animals within the previously specified range. That is /Favorite Animals/,/!/... change something within this range.

Yes, of course. For a single mapping:

 sed '/Favourite Animals/,/!/ s/Monkey/Gorilla/'

For multiple mappings:

 sed '/Favourite Animals/,/!/ {
s/Monkey/Gorilla/
s/Penguin/Zebra/
s/Cat/Dog/
}'

You can also combine those onto a single line if you wish — use semicolons to separate them:

 sed '/Favourite Animals/,/!/ { s/Monkey/Gorilla/; s/Penguin/Zebra/; s/Cat/Dog/; }'

Be aware that GNU sed and BSD (Mac OS X) sed have different views on the necessity for the last semicolon — what's shown works with both.


The original answer works with a simpler input file.

Original answer

Consider the file data containing:

There's material
at the start of the file
then the key information:
Favourite Animals
Monkey
Penguin
Cat
!
and material at the end of the file too.

Using c, you might write:

$ sed '/Favourite Animals/,/!/c\
> Replacement material\
> for the favourite animals
> ' data
There's material
at the start of the file
then the key information:
Replacement material
for the favourite animals
and material at the end of the file too.
$

Using i, you would use:

$ sed '/Favourite Animals/i\
> Replacement material\
> for the favourite animals
> /Favourite Animals/,/!/d' data
There's material
at the start of the file
then the key information:
Replacement material
for the favourite animals
and material at the end of the file too.
$

Using a, you might write:

$ sed '/!/a\
> Replacement material\
> for the favourite animals
> /Favourite Animals/,/!/d' data
There's material
at the start of the file
then the key information:
Replacement material
for the favourite animals
and material at the end of the file too.
$

Note that with:

  • c — you change the whole range
  • i — you insert before the first pattern in the range before you delete the entire range
  • a — you append after the last pattern in the range before you delete the entire range

Though, come to think of it, you could insert before the last pattern in the range before deleting the entire range, or append after the first pattern in the range before deleting the entire range. So, the key with i and a is to put the 'replacement text' operation before the range-based delete. But c is most succinct.

sed to match multiline range and replace

If we can assume you have the URL on the second line below second: you may use

sed -i '/second:/{N;N;s#"http://.*"#"next.com"#}' file

See this online sed demo.

N appends the newline and then subsequent line to the pattern space. So, the substitute command is run on the following text:

second:
{
host = "http://nxt-secondepisode.xcfm.crata.dive.com/err1.2.2/table/kenny.xml.gz"

If it is not known which line it is exactly, you may loop before you find the lione that starts with 0+ spaces, then host =, and only then run substitution:

sed -i '/second:/{:a;n;/^ *host *=/!ba;s#"http://.*"#"next.com"#}' file

See this online sed demo.

Here,

  • /second:/ - once a line contains second:
  • :a - set a label named a
  • n - discard the current pattern space and read the next line into it
  • /^ *host *=/!ba - if the line does not (!) start with 0+ spaces, host, 0+ spaces, =, then go back (b) to label a position
  • s#"http://.*"#"next.com"# - run the substitution.

Literal spaces can be replaced with [[:space:]]*, [[:blank:]]* or \s* to match any whitespace depending on what works in your sed.

Multiline pattern after pattern match using sed

sed -i.bak '/^section2 {/,/^}/s/host .*/host = "newvalue"/' file

This will search between section2 { and the next }, changing all host = occurences. GNU sed syntax, you should use eg sed -i '' ... on OSX.

SED script not matching single in Multiline pattern after line breaks

The reason your code never loops again doesn't actually have anything to do with the loop condition; it's that inside the loop, you run

    d

...which aborts the processing of the current input line. That you constructed several lines in the pattern space from the input line is of no consequence; d tells sed to stop what it was doing, read the next line of input (if there is one) and start over with that.

Anyway, your approach seems overly complicated to me. I'd suggest (in GNU parlance, because the mechanism is more obvious in GNU sed code)

#!/bin/sed -rf

s/\s*(^|&&|;)\s*/\n/g # split tokens onto several lines, make sure
# there's a newline in front of each (so the next
# regex matches all)
s/(\n[^\n])_x\*/\1_x\1x/g # Match lines that end with _x*, expand to
# \nfoo_x\nfoox
s/^\n*// # remove leading newlines (we put at least one
# there in the beginning)

You seem to have taken great pains to make the code work with non-GNU sed, so here's a POSIX version that does the same thing:

#!/bin/sed -f

s/[[:space:]]*&&[[:space:]]*/\
/g
s/[[:space:]]*;[[:space:]]*/\
/g
s/^/\
/
s/\(\n[^\n]\)_x\*/\1_x\1x/g
s/^\
*//

This removes whitespaces around the tokens. It seemed like a sensible thing to do. If you don't want that to happen, the space-matching parts will have to be removed from the code, and provisions will have to be made for whitespace at the end of a token line.

#!/bin/sed -rf

s/^|&&|;/\n/g
s/(\n[^\n])_x\*([[:blank:]]*)/\1_x\1x\2/g
s/^\n//

is a possible adaptation of the GNU sed code.



Related Topics



Leave a reply



Submit