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 liness/(Size=)bar/\1foo/
: MatchSize=bar
and replace withSize=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 rangei
— you insert before the first pattern in the range before you delete the entire rangea
— 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 s
ubstitute 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 containssecond:
:a
- set a label nameda
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 labela
positions#"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
When Installing Rust Toolchain in Docker, Bash 'Source' Command Doesn't Work
Resolve Relative Relocations in Partial Link
Jupyter Lab - Suppress Console Output
How to Get the CPU Time for a Perl System Call
Will Process Lost Wake-Up Chance in a Preemptive Kernel
How to Update a Varible from One Shell to Another Shell
Linux Zip and Exclude Dir via Bash/Shell Script
How to Redirect Cout to Console in Linux
Bash Syntax Error: Unaccepted Token Elif
How to Escape the Bang (!) Character in Linux Bash Shell
Compile Git for 32-Bit Linux on Shared Hosting
Which Capabilities Are Needed for Statx to Stop Giving Eperm
Print the Directory Where the 'Find' Linux Command Finds a Match
Linking to Modules Folder Gives Undefined Reference
Shell Script Error: "Head: Invalid Trailing Option -- 1"
Sort Entries of Lines Using Shell
Can Someone Explain How This "Shellshock" Code Works in Shell