4

How do I replace the first blank line with two lines of content? I did see a question on replacing multiple blank lines with a single blank line in vim sed but don't quite see how to adapt that. So, for example, if my input file is:

% abd
% def

% jkl

% mno

I would like to have a sed command that replaces just the first blank line with these two lines (one containing ghi and the other containing %):

% abd
% def
% ghi
%
% jkl

% mno
Peter Grill
  • 1,593
  • Is the last line supposed to stay jkl? – Michael Mrozek May 24 '11 at 05:41
  • I don't understand the requirements either. What is jkl supposed to become? Could you express the requirements in English as well, for example “replace all blank lines by a fixed multi-line text” or “replace all blank lines by a blank line preceded by a copy of the next line”? – Gilles 'SO- stop being evil' May 24 '11 at 19:30
  • Oppss. Sorry, I have corrected the example. ALL lines (except the first blank line), stay as they are.. The first blank line gets replaced with %ghi\n% where the \n represents a linefeed to get me to the next line so that there is a line with just a % on it. Hope that removes the confusion. – Peter Grill May 24 '11 at 22:29

4 Answers4

3

Sed matches entire lines but doesn't include the newline, so a blank line will just be an empty string. You can use ^ to match the beginning of a line and $ to match the end, so ^$ matches a blank line. Just replace that with % ghi\n%:

sed 's/^$/% ghi\n%/'

The newline that already existed will remain, so you'll end up with % ghi on one line and % on the next


Edit: If it needs to only match once then the expression is a bit more complicated. The easiest way I know to do it in sed is:

sed '0,/^$/ s/^$/% ghi\n%/'

The replacement is wrapped in an address range 0,/^$/, which means "only apply the following to the lines between 0 and the first line that matches ^$". Since the replacement expression checks for ^$ as well, the only line that's actually going to change is the first that matches ^$ -- the first blank line

Michael Mrozek
  • 93,103
  • 40
  • 240
  • 233
3

Here's another way with sed that replaces only the first empty line without using \n in the RHS or the gnu sed 0 address extension:

sed '/^$/{      # if line is empty
x               # exchange pattern space w. hold space
//{             # if pattern space is empty (means the hold buffer was empty)
s//%/           # replace it with a % character
h               # overwrite hold space (so now hold space looks like this: ^%$)
s/$/ ghi/       # add a space and the string ghi after the %, then
G               # append content of hold buffer to pattern space so now the
}               # pattern space looks like this: ^% ghi\n%$
//!x            # if pattern space is not empty it means a change was
}               # already made so exchange back (do nothing)
' infile

one liner:

sed -e'/^$/{x;//{s//%/;h;s/$/ ghi/;G' -e'}' -e'//!x' -e'}' infile

Though really, this is piece of cake for ed:

ed -s infile <<< $'/^$/s//% ghi\\\n%/\n,p\nq'

replace ,p with w to edit the file in-place.

don_crissti
  • 82,805
  • i wish you'd do more of these with ed. a lot of us can sed - though very few of us can do it at your level so don't stop doing the sed stuff either - but there's a whole lot less talk about ed around here than i think is due. some of this stuff which can be done with sed just doesn't make a whole lot of sense without ed-style multiple alternate buffers or j and similar. – mikeserv Oct 30 '15 at 23:00
  • 1
    @mikeserv - thanks ! re: ed, you're right... in my quest to improve my sed-fu I've left ed aside for a while. This is all sed's fault :) as it can easily get addictive if you know what I mean... Anyway, next month I'll review all my posts here and I'll probably add some ed solutions to my existing sed/awk posts. And yes, I'll do more ed in the future, I promise. – don_crissti Oct 30 '15 at 23:14
2

NB: This answers the original question, which asked for:

How do I replace a single blank line with two lines of content?

This does not answer the 'chameleon' question.


sed '/^$/{i\
% ghi\
%
d
}'

When sed finds a blank line, this inserts the two lines '% ghi' and '%', then deletes the blank line.


From the comments

This produces a syntax error.

Use a real shell instead of a sea-shell; it will save everyone grief in the long run. Place the commands on 5 lines of a simple file and use sed -f sed.script. There are probably other ways to achieve this in C shell - this works, though:

echo '/^$/{\'  > sed.script
echo '% ghi\' >> sed.script
echo '%'      >> sed.script
echo 'd'      >> sed.script
echo '}'      >> sed.script
sed -f sed.script data.file
rm -f sed.script
  • This also produces a syntax errror, even if I attempt to put it on one line. – Peter Grill May 24 '11 at 06:45
  • I have seen quite a few posts about giving up on csh and after your comment I will switch to sh for my script writing needs. Thanks for the frank feedback. – Peter Grill May 24 '11 at 18:21
  • 1
    @Peter: the definitive demolition of C shell for programming is Csh Programming Considered Harmful. I think you just made a correct decision. – Jonathan Leffler May 24 '11 at 18:24
  • this inserts the string for every blank line in the file. and if you're using insert anyway, why not just change? – mikeserv Oct 30 '15 at 20:03
  • 2
    @mikeserv: the question was changed dramatically a long time (multiple hours) after I last edited this answer. Chameleon questions leave valid answers for the original question looking invalid for the revised question. – Jonathan Leffler Oct 30 '15 at 20:09
  • @JonathanLeffler - i believe you. i had a similar experience here, i think. so no downvote, but you should still have used c anyway, which is why i dont upvote either. – mikeserv Oct 30 '15 at 20:16
  • @mikeserv: I would probably use c too now I think about it; I don't now (4+ years later) remember why I didn't use it at the time. This is like Perl: there's more than one way to do it. – Jonathan Leffler Oct 30 '15 at 20:34
1
sed -e1\!b -e:n -e"/^$/c$(printf '\\\n%%%s' \ ghi '')" -en\;bn

sed understands three primitives for scripted, fixed-string writes to stdout. All three begin after the immediately following, backslash-escaped newline in the script and end either at the first occurring unescaped newline in the script or the first occurring end-of-file in the script:

  • i

    • inserts the fixed-string to standard out right now
  • a

    • schedules the fixed-string for an append to stdout in the order of scripted occurrence before the next line-cycle, or, for the last line, at the end of this one
  • c

    • deletes the pattern space for all of address[es] (1(,2)?)?, ends the current line-cycle, and changes output to the fixed-string for the last of any address[es]

printf '\n\n\n\n\n' |
sed -e1\!b -e:n \
    -e"/^$/c$(printf '\\\n%%%s' \ ghi '')" \
    -en\;bn

% ghi
%




So this script branches out of the script for every line which is ! not the 1st, but from the first line until it can change a blank line to the fixed-string, it auto-prints every not-blank line before overwriting it with the next and looping back to the :next label to go again.

mikeserv
  • 58,310
  • This does the correct thing for the first blank line, but also inserts two lines at the end of the file where I had a blank line as well: % ghi and %. I ONLY want the first occurrence to be replaced. – Peter Grill Oct 30 '15 at 19:18
  • @PeterGrill - I don't think it should ever do that. It should only ever change the first occurrence of a blank line, I think... – mikeserv Oct 30 '15 at 19:36
  • @PeterGrill - was the last line the first occurring blank line in the file? if so, then yes, it should do that, because it is supposed only to replace the first occurring blank line. – mikeserv Oct 30 '15 at 19:38