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.

  • 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 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 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 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 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 – Christian Brabandt Sep 10 at 7:28
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.

| improve this answer | |
  • 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. – Nordine Lotfi Sep 10 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 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.

| improve this answer | |
  • Hm, I didnt even consider the lines between delimiters. Whoops. – D. Ben Knoble Sep 9 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 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

| improve this answer | |
  • Just tried this on Vim 8.1, show E34: No previous command – Nordine Lotfi Sep 10 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 at 11:49
  • 1
    @Quasímodo is right; vim will substitute bangs for previous commands for you – D. Ben Knoble Sep 10 at 13:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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