Extract span between starttext and endtext in txt file, exactly n times

Viewed 108

I want to extract only the n first occurrences of

starttext(
some text
endtext)

in text file F.

I've tried experimenting with sed:

sed - '/starttext/,/endtext/p' inputfile

... this will give me ALL the ranges between starttext and endtext in inputfile. But I only want the first n ranges...

File F:

starttext(
sometext1
more text
endtext)
starttext(
sometext2
pineapple
endtext)
starttext(
sometext3
orange
banana
endtext)
starttext(
sometext4
some other text
endtext)
starttext(
sometext5
coconut
endtext)
starttext(
sometext6
endtext)

Fake command

sed - '/starttext/,/endtext/p' ((get the top 3 instances) inputfile

Epected output:

starttext(
sometext1
more text
endtext)
starttext(
sometext2
pineapple
endtext)
starttext(
sometext3
orange
banana
endtext)

I was asked to provide the following:

Also tell us if there can be nested ranges, or overlapping ranges, or starts with no end or ends with no start.

There is always an endtext after each starttext. Ranges are not nested or overlapping.

Also do you want to look for the start and end as regexps or strings?

Strings

And do you want to do a full-line match or partial line or something else?

I want the full line match from starttext to the full line of endtext with all the text (several lines) in between.

5 Answers

With awk:

awk -v n=3 'index($0, "starttext"){f=1; if(c++ == n) exit}
            f; index($0, "endtext"){f=0}' ip.txt

If you don't have any lines outside of these two markers (based on given sample), you can also use:

awk -v n=3 'index($0, "starttext") && c++ == n{exit} 1'

It looks like you want everything from the beginning of the file up to, and including, the nth endtext.

Not pretty but it gets the job done.

head -$(grep -nm $n endtext $file | tail -1 | grep -o [0-9]*) $file

Where $n is the number of text spans you want and $file is the text file.

$ awk -v n=3 '
    $0=="starttext(" { f=1 }
    f { print; if ($0=="endtext)") { f=0; if (++c==n) exit } }
' fileF
starttext(
sometext1
more text
endtext)
starttext(
sometext2
pineapple
endtext)
starttext(
sometext3
orange
banana
endtext)

If ed is available/acceptable with brace expansion from the bash shell.

printf '%s\n' '/^starttext(/;/^endtext)/p'{,,} q | ed -s file.txt

The comma , inside the braces duplicates what ever is at the left side. So the first count is the pattern '/starttext/;/endtext/p' and two commas ,, inside the curly braces equals 3 patterns.


The above brace expansion expands to.

printf '%s\n' '/^starttext($/;/^endtext)$/p' '/^starttext($/;/^endtext)$/p' '/^starttext($/;/^endtext)$/p' q | ed -s file.txt

Or mapfile from the bash shell

mapfile -d')' -t array < file.txt

IFS=')'; printf '%s\n' "${array[*]:0:3})"

This might work for you (GNU sed):

sed -n '/starttext/,/endtext/{H;g;s/endtext/&/3;T;s/.//p;q}' file

Gather up the matching occurrences in the hold space.

If the end delimiter matches n times (in this case 3), remove the introduced newline, print the result and quit.

Related