5

I try to use sed to insert a line after [Block B] in the following file:

[Block A]  
line 1  
line 2  

[Block B]  
line 1  
line 2  

[Block C]  
line 1  
line 2  

The command I used:

sed '/\[Block B\]/,/^$/a\inserted line' file

The correct/desired result should be:

[Block B]  
line 1  
line 2  
inserted line  

However, I got this instead:

[Block B]  
inserted line  
line 1  
inserted line  
line 2  
inserted line  

Please tell me how I can get the desired result using sed. Thanks!

Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
tonytz
  • 135
  • 1
  • 2
  • 10

3 Answers3

12
sed -e '/\[Block B\]/{:a;n;/^$/!ba;i\inserted line' -e '}'
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • thanks a lot! this is really helpful. However, I really don't understand why this worked lol! I guess it's time to read up on sed – tonytz Jun 25 '12 at 04:35
  • Can you please explain why you need the extra -e to make the command work. The following command for some odd reason doesn't work: sed -e '/\[Block B\]/{:a;n;/^$/!ba;i\inserted line}' file – tonytz Jun 25 '12 at 04:52
  • 2
    @tonytz You might like these [sed one-liners](http://sed.sourceforge.net/sed1line.txt) – Levon Jun 25 '12 at 04:58
  • @Levon Thanks for the link. I am reading it now. There is a lot to learn about this "simple" command :) – tonytz Jun 25 '12 at 04:59
  • 3
    @tonytz: The extra `-e` is so commands like `i` and `a` know where the inserted or appended string ends. – Dennis Williamson Jun 25 '12 at 05:02
  • @tonytz agreed (`awk` is worth knowing well too). If you google for `sed one-liners` (or `awk one-liners`) you'll find several links similar to the one I sent. I've learned a fair bit from those. – Levon Jun 25 '12 at 05:04
  • 1
    for anyone who's interested, here is another command that also works: sed -e '/\[Block B\]/,/^$/{/^$/!b;i\inserted line' -e '}' file – tonytz Jun 25 '12 at 07:07
  • Watch out - if you were using the above example in a shell_exec command for instance in PHP - you would need to double \\ before inserted line - example: shell_exec ("sudo sed -i -e '/\[git\]/{:a;n;/^$/!ba;i\\repositories[] = $gitlistDirectory' -e '}' /var/www/gitlist/config.ini"); – Jeremy Hajek Jun 04 '13 at 01:58
6

I found this question while looking for a solution to my own problem, which was similar but a little different. I adapted the answers here to solve my problem.

I needed to insert some text at the end of a block inside a configuration file like this:

name1 {
    ...
}

name2 {
    ...
    inserted text line 1
    inserted text line 2
}

name3 {
    ....
}

To achieve this I took @toyntz comment from above and adapted it thus:

/^name2 {/,/^}/{
    /^}/i\    inserted text line 1
    /^}/i\    inserted text line 2
}

That is just the sed expression; it can be put in a file and executed with sed -f like this:

$ sed -f sed_expression data_file

This first expression searches for a range of lines starting with name2 { occurring at the beginning of a line and ending with } also occurring at the beginning of a line. That selects the block to work on. The remaining expression is enclosed in {curly braces} and operates on the selected range. It contains one command per line we wish to insert, each with an expression /^}/ that matches the line with the closing curly brace followed by an insert i operation to insert a line of text. The i is followed with a \ so that leading whitespace is also inserted.

I then took the expression a bit further, replacing the two insert commands with one:

/^name2 {/,/^}/{
    /^}/i\
    inserted text line 1\
    inserted text line 2
}

Here the text to be inserted by one command is spread across the following two lines. Note the additional trailing \ on the first line to continue the single command.

Next, I reduced it to one line. It makes it messy and harder to read but it still works:

/^name2 {/,/^}/{/^}/i\    inserted text line 1\n    inserted text line 2
}

The two lines to be inserted are separated by a newline \n. Astute readers will note that there are actually two lines there - you can't put the closing brace on the end of the first line; this is why the other answers above have a second -e expression. So, the above was the best I could do. To represent that on a bash command line:

sed -e '/^name2 {/,/^}/{/^}/i\    inserted text line 1\n    inserted text line 2' -e '}' data_file

I've written out this longhand in the hope that it explains to anyone looking to insert at the end of a block of text how a sed expression can be written to achieve that. Sed expressions can be quite cryptic and difficult to figure out - hopefully my explanations help in that regard.

starfry
  • 9,273
  • 7
  • 66
  • 96
0

This might work for you (GNU sed):

sed '/^\[Block B\]/,/^$/!b;/^$/i\inserted line' file
potong
  • 55,640
  • 6
  • 51
  • 83