How do I get sed to print a range of lines until it sees two consecutive blank lines?
I'd like to use a sed script like this
#n
/^TODO/,/^$/p
except that sed should stop printing the range after开发者_高级运维 it comes across two consecutive blank lines, rather than just one blank line. Then it should continue scanning for the next range of interest. In other words, the end of range of interest is defined by two blank lines. I'm not even sure that an address range can handle this sort of requirement, so if there's another way to do this, please let me know.
This should stop when it encounters two consecutive blank lines regardless of the odd/even pairing of the blank lines with non-blank lines:
Don't print the two blank lines:
sed -n 'N;/^\n$/q;P;D'
Print one of them:
sed -n 'N;/^\n$/{P;q};P;D'
Print both of them:
sed -n 'N;/^\n$/{p;q};P;D'
Edit:
And here's how you'd make that work in your range:
sed -n '/^TODO/,${N;/^\n$/q;P;D}'
Edit 2:
Per dan's (the OP) comments and edited requirements, this seems to work to find the pattern in a range that ends in two blank lines, multiple times in a file:
sed -n '/^TODO/,/^$/{H;N;/^\n$/{b};p}'
This may work for you:
sed -n '/TODO/{:a;N;/\n\n$/s///p;Ta}' file
This will strictly adhere to the OP question and only print the range less the two newlines if each range includes two consecutive newlines.
If an end of file will also signify the end of such a range, use:
sed -n '/TODO/{:a;p;n;/^$/!ba;n;//b;x;p;x;ba}' file
N.B. The use of the hold space to supply a single blank line when it is not followed by another.
Also the first solution can print either two newlines:
sed -n '/TODO/{:a;N;/\n\n$/!ba;p}' file
Or one newline:
sed -n '/TODO/{:a;N;/\n\n$/s//\n/p;Ta}' file
Don't use an address range
Instead, look for the start of the TODO block, then loop until you find the pair of blank lines.
Here is a test file. After that is a comparison of some of the proposed solutions.
cat >tricky_todo.txt <<EOF
TODO block 1 line 1 (last line)
THIS LINE SHOULDN'T BE PRINTED #1
THIS LINE SHOULDN'T BE PRINTED #2
TODO block 2 line 1
block 2 line 2
block 2 line 3
block 2 line 4
block 2 line 6
block 2 line 7
block 2 line 8 (last line)
TODO block 3 line 1
block 3 line 2
block 3 line 3
block 3 line 4
block 3 line 5 (last line)
TODO block 4 line 1
block 4 line 2
block 4 line 3
block 4 line 4
block 4 line 5 (last line)
THIS LINE SHOULDN'T BE PRINTED #3
THIS LINE SHOULDN'T BE PRINTED #4
EOF
Output from @Paused's sed script
Unfortunately it is pretty much impossible to get an address range to work for this task. Too many unwanted lines, and not all of the wanted lines :-(
sed -n '/^TODO/,/^$/{H;N;/^\n$/{b};p}' <tricky_todo.txt
TODO block 1 line 1 (last line)
THIS LINE SHOULDN'T BE PRINTED #1
TODO block 2 line 1
block 2 line 2
block 2 line 3
block 2 line 4
block 2 line 6
TODO block 3 line 1
block 3 line 2
block 3 line 3
block 3 line 4
block 3 line 5 (last line)
TODO block 4 line 1
Output from my script
Caveat: if the TODO block is the last thing in the file, it MUST end in two blank lines as specified in the OP or it will be dropped.
sed -n '/^TODO/{:a;N;s/\n\n$//;Ta;p}' <tricky_todo.txt
TODO block 1 line 1 (last line)
TODO block 2 line 1
block 2 line 2
block 2 line 3
block 2 line 4
block 2 line 6
block 2 line 7
block 2 line 8 (last line)
TODO block 3 line 1
block 3 line 2
block 3 line 3
block 3 line 4
block 3 line 5 (last line)
TODO block 4 line 1
block 4 line 2
block 4 line 3
block 4 line 4
block 4 line 5 (last line)
Tip of the hat to @potong
FWIW, when I came up with the script above, I had somehow overlooked potong's original answer (which had a minor flaw). When I mentioned it, potong completely rewrote that answer using other strategies. Because of that and the caveat on my script, I am going to include a corrected version of potong's original answer here:
sed '/^TODO/!d;:a;$!{N;s/\n\n$//;t;ba}' <tricky_todo.txt
An "awk"ward solution?
Awk is certainly powerful, but I personally tend to use sed because it's more like the beloved vi, which I use on a daily basis. Nevertheless, here we go:
awk -vRS="\n\n\n" -vFS="\n" '/TODO/{s=0;for(n=1;n<=NF;n++){if(s==1||$n~/^TODO/){print $n;s=1}}}' <tricky_todo.txt
use sed for simple substitution.
$ more file
1
2
blah blah
TODO
blah blah
4
5
6
7
8
9
10
11
end
$ awk -vRS="\n\n" '/TODO/{print;exit}' file
1
2
blah blah
TODO
blah blah
4
5
Building on SiegeX's answer:
cat > lines <<'EOF'
a
c
d
EOF
sed '/^$/N; /^\n$/q' lines
sed 'N;/^\n$/q;P;D'
Proof of Concept
$ cat double_blank.txt foo bar baz blah one two three
Result
$ sed 'N;/^\n$/q;P;D' double_blank.txt foo bar baz
精彩评论