3

I have a file that looks as follows:

aa,bb,cc,dd,ee,ff,gg
aa,bb,cc,dd,hh
aa,bb,cc,dd,ii,jj
etc.

I would like to (preferably using one command) change occurrences of a , by a : from the 5th , until the last one.

I would thus get:

aa,bb,cc,dd,ee:ff:gg
aa,bb,cc,dd,hh
aa,bb,cc,dd,ii:jj
etc.

In other words, is there a way in vim to skip the first 4 (or n) occurrences of a pattern match and replace all other pattern matches (with the substitute command for example)?

I tried this:

1,$s/\(\([^,]*,\)\{4\}\)\([^,]*\),/\1\3:/g

but this only changes the 5th occurrence...

2

You could also try the following substitution command:

%s/\v(^(.{-},){4}.*)@<=,/:/g

The pattern is:

\v(^(.{-},){4}.*)@<=,

Which can be broken down like this:

\v(^(.{-},){4}.*)@<=,
| |||  |   |  |  |  |
| |||  |   |  |  |  +--- a comma
| |||  |   |  |  +--- the previous subpattern must be matched before what follows
| |||  |   |  +--- `.*` any text
| |||  |   +--- repeat the previous subpattern `.{-},` 4 times
| |||  |
| |||  |
| |||  +--- `.{-},` a minimum of text before a comma
| ||+--- capture the `.{-},` subpattern to apply the multi `{4}` to it
| |+--- beginning of a line (anchor)
| +--- capture the `^(.{-},){4}.*` subpattern to apply the multi `@<=` to it
+--- very magic mode

The @<= multi doesn't add anything to the pattern, it just asks the regex engine to make sure that at least 4 commas are found before the commas you want to substitute with a colon.

Also, there are 2 subpatterns you're capturing but you don't use any backreference to them (\1, \2, ..., \9), so you could tell Vim that you don't want it to count them as sub-expressions by prefixing a % sign before each open parenthesis:

%s/\v%(^%(.{-},){4}.*)@<=,/:/g

According to the help (:h /\%(), it makes the pattern a little bit faster.


I'm not sure, but more generally, it should work for other patterns too.

Suppose you wanted to replace all the occurrences of foobar, but ignoring the first N ones on any given line. Then, you could adapt the previous substitution command like this:

%s/\v(^(.{-}foobar){N}.*)@<=foobar/replacement/g
4

You'll have to play with /\=+submatch() and substitute(). First :s will select what is before the 5th coma (to ignore it until the zone start :h /\zs), then substitute() will replace the remaining comas.

Something like (untested)

%s/\v([^,]*,){4}\zs([^,]*),/\=substitute(submatch(0), ',', '::', 'g')/

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.