With sed, I think the easiest way is to use two sed processes:
echo 'on one off abcd on two off' | sed 's/\<on\>[[:space:]]*/\non\n/g; s/[[:space:]]*\<off\>/\noff\n/g' | sed -n '/^on$/,/^off$/ { //!p; }'
one
two
This falls into two parts:
sed 's/\<on\>[[:space:]]*/\non\n/g; s/[[:space:]]*\<off\>/\noff\n/g'
puts the on and off on easily recognizable, single lines, and
sed -n '/^on$/,/^off$/ { //!p; }'
prints just the stuff between them.
Alternatively, you could do it with Perl (which supports non-greedy matching and lookarounds):
$ echo 'on one off abcd on two off' | perl -pe 's/.*?\bon\b\s*(.*?)\s*\boff\b.*?((?=\bon\b)|$)/\1\n/g; s/\n$//'
one
two
Where the
s/.*?\bon\b\s*(.*?)\s*\boff\b.*?((?=\bon\b)|$)/\1\n/g
puts everything between \bon\b and \boff\b (where \b matches word boundaries) on a single line. The main trick is that .*? matches non-greedily, which is to say it matches the shortest string necessary to find a match for the full regex. The (?=\bon\b) is a zero-length lookahead term, so that the .*? matches only before another on delimiter or the end of the line (this is to discard data between off and on).
The
s/\n$//
just removes the last newline that we don't need or want.