1

I got this sed regex I found:

sed '/start/!d;s//&\n/;s/.*\n//;:a;/end/bb;$!{n;ba};:b;s//\n&/;P;D'

which basically outputs the content that is between a starting point and an end point.

##1##
this is a test
with multiple
line
##z##

and replacing start with ##1##, and end to ##z## would give:


this is a test
with multiple
line

If there are multiple occurrences of the ##1## and ##z##, then it would obviously show all the in-between text of said occurrence, like so:

##1##
this is a test
with multiple
line
##z##
##1##
this is a test
with multiple
line
##z##

to


this is a test
with multiple
line


this is a test
with multiple
line

To clarify even more:

##1##
this is a test
with multiple
line
##z##
##2##
test
##z##

would obviously give only what is specified as starting point (since the ending point ##z## is always the same). So if i use ##2## instead as starting point:


test

This would give the above as output.

My goal is to convert this to a compatible regex for vim. I'm using this site and the local manual as help but, I'm unsure where to start for converting the above to vim regex.

23
  • 1
    Wow, that sed is spooky :) What should happen if there are multiple occurrences of ##1## and ##z##? Please use a more complete example.
    – Quasímodo
    Sep 8, 2020 at 20:28
  • 1
    That’s a sed program, not a regex /pedant. Converting it to a vim program could certainly be done naïvely, but there may be a more idiomatic translation. Do you have a preference? Also, can you share where you found the ses program (if you didn’t create it wholesale)?
    – D. Ben Knoble
    Sep 8, 2020 at 22:35
  • 1
    Do you want the result to be left in the buffer in the end? (Effectively deleting everything except for the matched blocks?) Do you care very specifically about the number of blank lines between and around the blocks? (Please edit the question to update your more exact specifications.)
    – filbranden
    Sep 9, 2020 at 3:55
  • 1
    Also consider whether this is potentially an XY problem. What is it that you're actually trying to accomplish? What's the context for this text operation? If you share more, you might find more direct or more useful solutions to the actual problem you're trying to solve.
    – filbranden
    Sep 9, 2020 at 3:57
  • 2
    @NordineLotfi and no, that is not what the sed script is doing. If you just want to print text between two patterns, the sed script can be a whole lot simplified. What your sed script is actually doing is appending a lot of thing to the pattern space in order to replace line breaks, which by default sed won't do because it works linewise Sep 10, 2020 at 7:28

3 Answers 3

2

An extremely simple function which does the job far more idiomatically:

function! Between(start, end) abort
  let [start_line, _] = searchpairpos(a:start, '', a:end, 'bWn')
  let [end_line, _] = searchpairpos(a:start, '', a:end, 'Wn')
  if start_line is# 0 || end_line is# 0
    return
  endif
  call setline(start_line, '') " or: execute start_line 'delete'
  call setline(end_line, '') " or: execute end_line 'delete'
endfunction

Name it whatever you want, and then do, e.g.,

call Between('##1##', '##z##')

(which you could bind to a key, perhaps).

To work on the whole file, you might be able to use :global (or even just :%call Between(...)), or you may need a loop wrapping this function and traversing the file. In the case of the latter, I would use search(a:start) to find the next start and then + to be inside the nested region; then I would call the function. This is faster than calling the function on every line.

2
  • I noticed this works only when i visually select or place the cursor in the in-between text, and then run call Between('##1##', '##z##'). Would be great if it does it without needing to place the cursor. Sep 10, 2020 at 3:08
  • @NordineLotfi you could always add execute '/' escape(a:start, '/') '/+' or similar to the beginning of the function; that would position the cursor.
    – D. Ben Knoble
    Sep 10, 2020 at 13:04
3
:g/END\|\%^/,/START\|\%$/s/.*//

This global command marks all lines matching END or the beginning of file (\%^). Then it proceeds to delete everything from the marked lines up to START or the end of file (\%$) by using the substitution command s/.*//.

A side-by-side example:

Before           |After
________________________________
test             |
START            |
this is a test   |this is a test
with multiple    |with multiple
line             |line
END              |
0                |
START            |
0101             |0101
END              |
11               |
111              |

If you don't want the blank lines, you can replace s/.*// by d in the global command.

2
  • Hm, I didnt even consider the lines between delimiters. Whoops.
    – D. Ben Knoble
    Sep 9, 2020 at 23:29
  • @D.BenKnoble Yeah, but the example didn't have any either, so I think your solution is also acceptable.
    – Quasímodo
    Sep 10, 2020 at 11:48
3

Just for fun, remember you can always do

:%!sed '/start/\!d;s//&\n/;s/.*\n//;:a;/end/bb;$\!{n;ba};:b;s//\n&/;P;D'

And that’s still “vim” :P

3
  • Just tried this on Vim 8.1, show E34: No previous command Sep 10, 2020 at 2:22
  • 3
    I think Vim tries to do something with the exclamation marks, so they need to be escaped.
    – Quasímodo
    Sep 10, 2020 at 11:49
  • 1
    @Quasímodo is right; vim will substitute bangs for previous commands for you
    – D. Ben Knoble
    Sep 10, 2020 at 13:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.