Bubnoff
Bubnoff

Reputation: 4097

sed: delete previous line

I need to remove a blank line prior to a match.

So given the file:

random text
more random text






#matchee

I need to match the pattern /#matchee/, then delete the blank line before it.

Here's what I've tried --- no success:

sed '/^[ \t]*$/{N;/#matchee.*$/{D;}}' file.txt

My logic:

Basically /matchee/ is a constant that must be maintained and I need to remove an extra blank line from each record in a file of records delimited by /#matchee/.

This produces no effect whatever. I am RTFM-ing and D is supposed to delete pattern space up to the newline. Since N appends a newline plus next line --- this should produce the desired results ....alas ....no and no again. Is this because the match contains essentially nothing ( blankline )?

Upvotes: 6

Views: 5783

Answers (2)

potong
potong

Reputation: 58420

This might work for you:

sed '$!N;s/^\s*\n\(#matchee.*\)/\1/;P;D' file.txt

to follow how it works run this:

sed '$!N;l;s/^\s*\n\(#matchee.*\)/\1/;P;D' file.txt

N.B. P prints upto the first newline, D deletes upto the first newline and starts a new cycle without reading in another record unless there is no newline, in which case it behaves like d and reads in a line and begins the next cycle.

Upvotes: 4

ruakh
ruakh

Reputation: 183321

Your approach will work if there are an odd number of blank lines, but it will fail if there are an even number. Your sed script is a loop of the following.

  • grab a line L1, and add it to the pattern space
  • if L1 is non-blank, print it; but if it is blank, then:
    • grab another line, L2, and add it to the pattern space
    • if L2 contains #matchee, discard L1 from the pattern space
    • print the pattern space, which is consists either of L1 and L2, or just of L2

You'll notice that L2 is always printed, even if it's blank and followed by a line that contains #matchee. It's protected by the fact that it immediately follows an odd number of blank lines.

Edited to add: To fix the above-described problem, you can add an inner loop by using the : command to create a label and the b command to "branch" to it (goto). This:

sed '/^[ \t]*$/{: a;N;/#matchee/!P;D;/^[ \t]*$/b a}' file.txt

is a loop of the following:

  • grab a line L1, and add it to the pattern space
  • if L1 is non-blank, print it; but if it is blank, then:
    • create label a ← this is a no-op, just a place to goto
    • grab another line, L2, and add it to the pattern space
    • if L2 does not contain #matchee, print L1
    • discard L1 from the pattern space (whether or not we printed it)
    • we can now think of L2 as L1; it's the only thing in the pattern space
    • if the rechristened L1 is blank, goto a

Upvotes: 6

Related Questions