This can be looked at as a search-replace where the search is multi-level or nested. That is, first you need to match part of the line (the quoted string in this case). Then you need to match part of that string and do the replacement on that (underscores for us).
Basic regexes weren't really meant to handle nested searches (there are methods, often complex, depending on the regex variant). Fortunately, Vim has something called the sub-replace expression and it is commonly used to solve these problems:
:s/"\zs[^"]\+\ze"/\=substitute(submatch(0), '_', ' ', 'g')/
In the search part we identify quoted strings then segregate the text from the quotes with the \zs
...\ze
pair. IOW, the matched text is the string inside the double quotes.
"
: match an opening dub-quote
\zs
: anything before this is not included in substitution
[^"]\+
: match one or more characters not including double quote
\ze
: anything after this is not included in substitution
"
: match a (closing) dub-quote
In the replacement part we use the special operator \=
which tells Vim to treat everything that follows as an expression, evaluate it, and use the result as the replacement of the matched text.
Specifically we do a substitute()
on text returned by submatch(0)
which is the full matched string. The other params should be pretty self-explanatory but here is the function spec:
substitute({expression}, {pattern}, {substitution}, {flags})
The return value and replacement text is what we're looking for: the string between the quotes but with spaces in place of underscores.
Bringing a rocket launcher to a knife fight?
A few people have opined that this is too complicated. While I wouldn't use it for one-off editing of a line or two, respectfully, non-Euclidean geometry is complicated. For this all you need is enough of a grasp of regexes and \zs
..\ze
to match the part(s) of the line to which you want to apply substitution. Then...
- Type in the pattern
- Type or paste this:
/\=substitute(submatch(0), '', '', 'g')/
- Fill in params 2 and 3 with replacee and replacer text
Bonus Material
At the risk of contradicting the last section...
Since there seems to a bit of interest around this topic I figured I'd add a couple variations of the above. We all like examples.
Solving without \zs or \ze
In the original solution I use the Vim specific \zs
..\ze
token pair because knowing how to use those gives a lot of flexibility (with respect to sub-replace expressions and generally speaking when using regex in Vim). In reality though OPs specific case doesn't need them...
s/"[^"]\+"/\=substitute(submatch(0), '_', ' ', 'g')/
We just include the quotes in the matched string and it's fine because the substitution only affects underscores.
A slightly harder problem
Our input string is
INCORRECT_EMAIL_MOBILE_COMBINATION("_incorrect_email_mobile_combination_");
...and we want to replace the underscores inside the double-quotes but only those that have word characters on both sides. So we want to end up with...
INCORRECT_EMAIL_MOBILE_COMBINATION("_incorrect email mobile combination_");
Yes, \zs
..\ze
would come in handy again here but let's pretend we need a portable regex...
Solution:
s/"_\([^"]\+\)_"/\='"_' . substitute(submatch(1), '_', ' ', 'g') . '_"'/
First we wrap the part of the string to be modified in a capture group \(..\)
. This allows us to isolate the string from the quotes and outer underscores when we do the substitution by using submatch(1)
(returns string contained in first capture group) instead of submatch(0)
(returns entire matched string).
Of course we will lose the quote-underscore pairs if we stop at the substitute()
call. But we can just insert them into the sub-replace expression literally by (single) quoting them and using the concatenation operator (.
)...problem solved. Okay but what if the surrounding characters were not fixed? Let's say the quotes could be either single or double, ['"]_
.._['"]
Then we'll have to use more capture groups and change the literal string to submatch()
calls with appropriate indices.
Switching to very magic mode (\v
) so the pattern is a little easier to read...
s/\v(['"]_)([^"]+)(_['"])/\=submatch(1) . substitute(submatch(2), '_', ' ', 'g') . submatch(3)/
:s
works on line ranges so the minimal unit for substitute would be a single line not a part of it. – Maxim Kim Jul 23 '19 at 6:45