Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Search and replace a multi-line pattern with sed

Tags:

bash

sed

I've seen a lot of examples regarding this problem, but have yet to see the one that is tailored for my use. Although I'm very familiar with sed I'm ashamed to say that I'm a noob when it comes to more advanced features. Here's the problem at hand.

I have a multi-line pattern that I can successfully match with sed like so,

sed -e '/Favorite Animals/, /!/p

Favorite Animals
Monkey
Penguin
Cat
!
Favorite Things
Shoe
Dog
Wheel
Moth
!

and what I personally like about this expression is that I can match a variable number of lines up to the exclamation character. Now let's say I wanted to do a search and replace on that same pattern. Basically I would like to replace that multi-line pattern that was previously demonstrated with any string of my choosing. Any Ideas? I'm hoping for similar syntax to my demonstrated sed command, but beggars can't be choosers.

The idea is so that I can replace one of the groups delimited by the exclamation with a string. I'll call them "entries". I want to be able to update or overwrite these entries. If I had a new updated version of favorite animals I would like to be able to replace the old entry with a new one like this.

Favorite Animals
Sloth
Platypus  
Badger
Dog
!
Favorite Things
Shoe
Dog
Wheel
Moth
!

As you can see I'm no longer a fan of monkeys now.

like image 770
Mozzy Avatar asked Jun 19 '16 00:06

Mozzy


People also ask

How do you replace some text pattern with another text pattern in a file?

`sed` command is one of the ways to do replacement task. This command can be used to replace text in a string or a file by using a different pattern.

How can sed be used to identify a pattern?

Basic text substitution using 'sed'Any particular part of a text can be searched and replaced by using searching and replacing pattern by using `sed` command. In the following example, 's' indicates the search and replace task.


2 Answers

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.

like image 109
Jonathan Leffler Avatar answered Nov 14 '22 23:11

Jonathan Leffler


The i (insert) command has ugly syntax, but it works:

sed '/Favorite Animals/i\
some new text\
some more new text\
and a little more
/Favorite Animals/,/!/d'
like image 32
Beta Avatar answered Nov 14 '22 23:11

Beta