I have a file with text that looks like
{\o H}{\o e}{\o l}{\o l}{\o o} {\o W}{\o o}{\o r}{\o l}{\o d}
I want to make the text look like this
{\o Hello} {\o World}
There are probably multiple tools I can use to solve this problem, but I am trying to use vim substitutions. So far, I have
:%s/{\\o \(.\{-}\)}{\\o \(.\{-}\)}/{\\o \1\2}/g
The patterns here are
.\{-}
match some characters, non-greedy
{\\o .\{-}}
match the regex string {\o .*}
but being non-greedy with the .*
and \( ... \)
creates the capture groups for backreferencing.
When I run this substitution once, I get
{\o He}{\o ll}{\o o} {\o Wo}{\o rl}{\o d}
I could run this command two more times to get
{\o Hell}{\o o} {\o Worl}{\o d}
and then
{\o Hello} {\o World}
but I would really love if there was a way to do this with one command, i.e., to tell vim substitution to keep passing over the file until there are no more matches. It seems to me that this ought to be possible.
Does anyone know what magic I need to add to achieve this? It doesn't look like there's a flag that achieves this. Maybe some trick I don't know with labels?
You can use a sub-replace-expression, \=
, to execute a second substitution inside the replacement part of :s
:%s/\({\\o \S}\)\+/\='{\o '.substitute(submatch(0), '{\\o \(\S\)}', '\1', 'g').'}'/g
The basic idea is to capture a "word", e.g. {\o H}{\o e}{\o l}{\o l}{\o o}
, and then do a second substitution on the captured "word", submatch(0)
. The second substitution will remove the starting {\o
and trailing }
via substitute(submatch(0), '{\\o \(\S\)}', '\1', 'g')
. This leaves just the letters, Hello
in this example. Once every letter is extracted from the word we add back in the starting {\o
and trailing }
.
For more help see:
:h :s
:h sub-replace-expresion
:h substitute(
:h submatch(
:h literal-string
:h /\S
You can also use 2 separate substitutions. One to remove all the {\o
and }
. The other substitution will add them back in, but word-wise this time.
:%s/{\\o \(.\)}/\1/g
:%s/\S+/{\\o &}/g
This is arguably the an easier method assuming you do not have this text mixed in with other text that should not be in this format.
You can also do this in one command by using |
:
:%s/{\\o \(.\)}/\1/g|%s/\S+/{\\o &}/g
Here's a much simpler solution:
Recursive macros. The basic idea of a recursive macro, is a macro that calls itself until an error occurs. When you run a substitute command and there are no matches, this will throw an error. So here's the basic layout of what you need to do.
Clear macro 'q'. This is essential so that it doesn't do anything weird on the first run through. You can do this with
qqq
or
:let @q=""
Either one works.
Start recording. This is just:
qq
Run your substitute command, then call the macro. This would be
:%s/{\\o \(.\{-}\)}{\\o \(.\{-}\)}/{\\o \1\2}/g<cr>@q
Since @q
is the command to run a macro. This won't do anything while you are recording, since you have already cleared out register 'q'. Lastly,
Stop recording, and call the macro.
q@q
This will loop the set of keystrokes you just pressed over and over until there are no matches.
To put this all together:
qqqqq:%s/{\\o \(.\{-}\)}{\\o \(.\{-}\)}/{\\o \1\2}/g<cr>@qq@q
That's 5 'q's at the beginning. You could also assign register 'q' from the command line, with:
:let @q=':%s/{\\o \(.\{-}\)}{\\o \(.\{-}\)}/{\\o \1\2}/g<cr>@q'
Then hit
<cr>@q
As a side note, there is a dedicated vim-site that I highly recommend.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With