2

I am very new to sed, and everything I find is a little bit here, a little bit there.

I have a text file which contains a block like the following:

#start
a
b
c


#whatever
…

Obviously, that’s a simplified version. I would like to append a line to the end of the #start block to give me:

#start
a
b
c
d

#whatever
…

I can sort of locate the block with the following:

sed -n '/^#\s*start/,/^$/ p' data.txt

so I think that’s in the right direction. However:

  • the selection includes the empty line, which I don’t want
  • I can’t work out how to add another line after the match
Manngo
  • 14,066
  • 10
  • 88
  • 110
  • There won’t be any gaps in the block itself. There may be multiple newlines after the block. – Manngo Jan 27 '19 at 05:19
  • @Tiw GNU sed version 4.2.1 – Manngo Jan 27 '19 at 05:21
  • 1
    Maybe you need `i` command `sed -e '/#start/,/^$/{/^$/i\d' -e '}' data.txt` – revo Jan 27 '19 at 06:37
  • Maybe I thought too much, but you don't have blocks that are consecutive, no empty lines between? – Til Jan 27 '19 at 08:11
  • 1
    That's not the right direction. sed is the right tool for doing s/old/new on individual lines but that is all - for anything else there are other tools you should be using for clearer, simpler, more robust, more portable, and more efficient results. – Ed Morton Jan 27 '19 at 15:25

5 Answers5

3

With sed:

sed '/#start/,/^$/ s/^$/d/;' file
  • /#start/,/^$/: search for blocks starting with #start and ending with a blank line
  • s/^$/d/: replace matching blank line with a d

If you want to add the string before the blank line:

sed '/#start/,/^$/{/^$/{s//d/;G;};}' file
SLePort
  • 15,211
  • 3
  • 34
  • 44
  • Thanks, this does the job nicely, and is simple enough for me to understand. As an additional question, is it possible to insert the new line _before_ the blank line? Otherwise it ends up consuming the blank line, and I will soon run out of them … – Manngo Jan 27 '19 at 08:05
  • I edited my answer. You can do it with a `G` command that will insert a blank line after the one that has been replaced with `d`. – SLePort Jan 27 '19 at 08:12
  • 1
    @SLePort Brilliant. Thanks again. – Manngo Jan 27 '19 at 08:13
0

For GNU sed, try this please (update again, had a little bug):

sed '/^#\s*start/{x;s/.*/d/;x;be};/^\s*#[a-zA-Z]*/{x;G;x;s/.*//;x;};/^\s*$/{x;};:e'

Eg:

$ cat file                                                               
#start                                                                   
a                                                                        
b                                                                        
c                                                                        


#whatever                                                                
...                                                                      

$ cat file2                                                              
#start                                                                   
a                                                                        
b                                                                        
c                                                                        
#whatever                                                                
...                                                                                                       
$ sed '/^#\s*start/{x;s/.*/d/;x;be};/^\s*#[a-zA-Z]*/{x;G;x;s/.*//;x;};/^\s*$/{x;};:e' file  
#start                                                                   
a                                                                        
b                                                                        
c                                                                        
d                                                                        


#whatever                                                                
...                                                                      

$ sed '/^#\s*start/{x;s/.*/d/;x;be};/^\s*#[a-zA-Z]*/{x;G;x;s/.*//;x;};/^\s*$/{x;};:e' file2 
#start                                                                   
a                                                                        
b                                                                        
c                                                                        
d                                                                        
#whatever                                                                
...      

This sed command will shuffle those not-so-empty empty lines (only spaces lines). But I guess it's ok for you, right?
The idea is to use hold space to keep the thing needs append (d here), and append it when time comes.
x is to exchange hold space with pattern space(current read-in line).

Btw, if there're multiple #start blocks, it will append to all of them, if you don't want this behavior please comment.

Til
  • 5,150
  • 13
  • 26
  • 34
0

You could use awk quite simply like so:

awk -v RS= '/#start/{$0 = $0 ORS "d\n"} 1' file

If #start is not at the top of the file the you'll want to do:

awk -v RS= '/#start/{$0 = $0 ORS "d"} {$0 = $0 ORS} 1' file

*This removes all but one linefeed between each block.

Result:

#start
a
b
c
d

#whatever
…
l'L'l
  • 44,951
  • 10
  • 95
  • 146
0

This might work for you (GNU sed):

sed '/^#start/,/^\s*$/!b;/^\s*$/c\d' file

Focus on the range of lines between a line starting #start and an empty line. If the line is empty, change it to a d.

N.B. The ! negates the match and the b with out a place holder, bails out of any further processing by sed.

For comparison, note the behaviour of these solutions which in the first case inserts d before the empty line and in the second, appends d following the empty line.

sed '/^#start/,/^\s*$/!b;/^\s*$/i\d' file

sed '/^#start/,/^\s*$/!b;/^\s*$/a\d' file
potong
  • 55,640
  • 6
  • 51
  • 83
-1

With any awk in any shell on any UNIX box:

$ awk 'BEGIN{RS=""; ORS="\n\n"; FS=OFS="\n"} $1=="#start"{$(NF+1)="d"} 1' file
#start
a
b
c
d

#whatever
…

Unlike the currently accepted sed solution the above will work even if the target string contains regexp metacharacters and even if the string to be added contains backreferences and even if either of them contains delimiters (/) and it can be trivially modified to make the change based on the value of the 2nd, 3rd, or any other line of the input block rather than or in addition to just the first line and it can be trivially modified to add or modify any line in the middle of the block. In short it's a vastly superior approach.

Ed Morton
  • 188,023
  • 17
  • 78
  • 185