3

I have the following file:

------

Introduction
----------
Optio eum enim ut. Et quia molestias eos. Doloribus laborum quia quae. Magnam cupiditate quis consectetur.

-----
Chapter1: Foo
-----

Odit beatae eius voluptas temporibus sint quia. Eos et tempora similique laboriosam optio consequatur quibusdam. Fugit suscipit cupiditate ea perspiciatis rem labore cum eos.

-----
Chapter bar


-----
Et consequatur quia quia et architecto et sunt. Perferendis qui deserunt qui est illo est sapiente ipsam. Fugiat vel amet magni in quam. Eligendi totam cum sapiente harum blanditiis minima

With the following constaints:

  • The header symbol - appears at least 5 characters or more.
  • There could be an arbitrary (but finite) number of blank lines between - and the header.

The expected output is:

Introduction
Chapter1: Foo
Chapter bar

I know this could be accomplished using awk but please don't suggest that. I would like to see a pure GNU sed solution.

This is what I have tried so far:

sed -n ':a; /-\+/{n; /^$/!{p; b a}}' input.txt

But that command doesn't seem to work.

3
  • Does this answer your question? Multiline grep or sed
    – Kusalananda
    Dec 21, 2021 at 15:32
  • @they: No that doesn't answer my question because it's doesn't account for blank lines between "=" and the header.
    – Amazigh_05
    Dec 21, 2021 at 15:36
  • Just pipe through sed '/^$/d' or awk 'length != 0'
    – Kusalananda
    Dec 21, 2021 at 15:38

4 Answers 4

6

This prints the lines that contain at least one alphabetical or numerical character, as long as they are inside a header.

sed -n '/^-----/,/^-----/{/[[:alnum:]]/p;}' file
0
4

I propose this solution:

$ sed -n '/\-\{5,\}/,/\-\{5,\}/p' file | sed '/\-\+\|^$/d'
Introduction
Chapter1: Foo
Chapter bar

  • '/\-\{5,\}/,/\-\{5,\}/p' select the sections between - (at least 5).
  • '/\-\+\|^$/d' delete either blank lines or those starting with -.
2
  • That's too expensive. one sed process should be enough.
    – Amazigh_05
    Dec 20, 2021 at 17:00
  • @OK-Validation Indeed, Quasímodos' answer, using ranges too, does that. The caveat, being too picky, is that if there are lines between the range that don't have alnum characters, it won't print them. But it seems highly unlikely given your sample. Dec 20, 2021 at 19:27
4

While you can indeed do this in sed, other tools would make it easier (IMO)1 can also work. For example, in awk:

$ awk '/-----/ && !a{a=1;next} /-----/ && a{a=0}a' file 

Introduction
Chapter1: Foo
Chapter bar


And to get rid of the empty lines, you could do:

$ grep . file | awk '/-----/ && !a{a=1;next} /-----/ && a{a=0}a'
Introduction
Chapter1: Foo
Chapter bar

Or:

$ awk '!/./{next};/-----/ && !a{a=1;next} /-----/ && a{a=0}a' file 
Introduction
Chapter1: Foo
Chapter bar

The idea here is to set the variable a to 1 if it is currently 0 or unset (!a) and if the current line matches at least 5 -. We set it back to 0 if we find another line with 5 - when a is set to 1. We then print all lines where a is set (that's what the final a does: it will print if a evaluates to non-0).

Here's the same thing written in an easier to understand way:

awk '{ 
        if(!/./){ next } 
        if(/-----/ && !a){ a=1; next} 
        if(/-----/ && a){ a=0 } 
        if(a){ print }
    }' file

1Quasimodo's answer is even simpler!

3
  • Nice answer! I agree that awk works but it takes a lot of typing compared to sed simple syntax.
    – Amazigh_05
    Dec 20, 2021 at 17:05
  • @OK-Validation heh, OK. I find this is shorter and more elegant than sed whose syntax I find particularly obscure. But that's why we have many options, everyone can choose the one they prefer! Please remember to accept whichever answer you prefer, there are some really good sed ones here!
    – terdon
    Dec 20, 2021 at 20:28
  • Whoops, sorry @OK-Validation, I see you have already been told about accepting. Since you're aware of the mechanics, feel free to wait as long as you want, I didn't mean to pressure you to accept.
    – terdon
    Dec 20, 2021 at 20:34
3

You can use:

sed -n '/^-\+$/,/^-\+$/{/^-*$/!p}' input.txt

Alternatively, you could also use sed -z:

sed -Ez 's/[^-]*-+[\n]*([^\n]*\n)[\n]*-+[^-]*/\1/g' input.txt

without -E:

sed -z 's/[^-]*-\+[\n]*\([^\n]*\n\)[\n]*-\+[^-]*/\1/g' input.txt

Note: While this works well for your example, it might have issues if - appears somewhere in the text. But I think, you could elaborate on that.

2
  • Indeed, with a line starting with - the command fails. Dec 20, 2021 at 15:47
  • Not sure, why you mean it won't work, because for your example it does. But if you're happy with your version, I'm happy too ;-)
    – pLumo
    Dec 20, 2021 at 18:55

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .