21

I saw other questions dealing with the finding the n-th occurrence of a word/pattern, but I couldn't find how you would actually substitute the n-th occurrence of a pattern in vim. There's the obvious way of hard coding all the occurrences like

:s/.*\(word\).*\(word\).*\(word\).*/.*\1.*\2.*newWord.*/g 

Is there a better way of doing this?

Gaurav Dadhania
  • 5,167
  • 8
  • 42
  • 61
  • Apparently there is in sed, but not vim `s///`: http://vim.1045645.n5.nabble.com/replace-nth-occurrence-on-each-line-like-sed-td1169460.html – keflavich Nov 04 '11 at 21:44

7 Answers7

10

For information,

s/\%(\(pattern\).\{-}\)\{41}\zs\1/2/

also works to replace 42th occurrence. However, I prefer the solution given by John Kugelman which is more simple -- even if it will not limit itself to the current line.

Community
  • 1
  • 1
Luc Hermitte
  • 31,979
  • 7
  • 69
  • 83
  • @ Luc Hermitte: could you please tell me what does the '\%' mean in your answer. It seems it's an operator that VIM cannot recognize. – Z.Zen Feb 08 '14 at 07:24
  • 2
    It's like `\(`, but it tells to not bind what is matched by the parenthesis to `\1`. See `:h /\%(`, IIRC – Luc Hermitte Feb 09 '14 at 01:55
7

You can do this a little more simply by using multiple searches. The empty pattern in the :s/pattern/repl/ command means replace the most recent search result.

:/word//word//word/ s//newWord/
or
:/word//word/ s/word/newWord/

You could then repeat this multiple times by doing @:, or even 10@: to repeat the command 10 more times.

Alternatively, if I were doing this interactively I would do something like:

3/word
:s//newWord/r

That would find the third occurrence of word starting at the cursor and then perform a substitution.

kenorb
  • 155,785
  • 88
  • 678
  • 743
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • 1
    Hrm, there are still a number of problems with this, if the first word is the word we're looking for, '3/word' will get us to the 4th occurrence. Also, when I tried the substitution after finding the n-th instance, it still substitutes the first occurrence. – Gaurav Dadhania Jun 19 '10 at 01:21
  • `3/word` will find the third occurrence starting at the cursor. Go to the top of the file with `gg` if you want to start there. Note that `3/word` is equivalent to typing `/word` three times so it definitely finds the third occurrence. – John Kugelman Jun 19 '10 at 01:31
  • 8
    -1: Neither of these work for me if all the words are on the same line (which is what I assume the OP means). In both examples the substitution still happens to the first word on the line. – Dave Kirby Jun 19 '10 at 07:59
7

Replace each Nth occurrence of PATTERN in a line with REPLACE.

:%s/\(\zsPATTERN.\{-}\)\{N}/REPLACE/
αғsнιη
  • 2,627
  • 2
  • 25
  • 38
  • Could you tell me `zs` and `{N}` are the grammar of regular expressions or the grammar of vim? – caimaoy Oct 10 '18 at 06:41
  • the Zoom anchors are Vim specifc AFAIK (other languages IDK), see [this great answer](https://vi.stackexchange.com/a/3039/7940) about `\zs` or type `:h \zs` in Vim to get information about it, the `{}` is used in most languages but in different functionalities which mostly is used for length of match or times of occurrence of a pattern or a group match `(....){N}` as like here I used. – αғsнιη Oct 10 '18 at 07:34
2

To replace the nth occurrence of PATTERN in a line in vim, in addtion to the above answer I just wanted to explain the pattern matching i.e how it is actually working for easy understanding.

So I will be discussing the \(.\{-}\zsPATTERN\)\{N} solution,

The example I will be using is replacing the second occurrence of more than 1 space in a sentence(string). According to the pattern match code->

  1. According to the zs doc,

    \zs - Scroll the text horizontally to position the cursor at the start (left side) of the screen.

  2. .\{-} 0 or more as few as possible (*)

    Here . is matching any character and {} the number of times. e.g ab{2,3}c here it will match where b comes either 2 or 3 times.

    In this case, we can also use .* which is 0 or many as many possible. According to vim non-greedy docs, "{-}" is the same as "*" but uses the shortest match first algorithm.

  3. \{N} -> Matches n of the preceding atom

    /\<\d\{4}\> search for exactly 4 digits, same as /\<\d\d\d\d>

    **ignore these \<\> they are for exact searching, like search for fred -> \<fred\> will only search fred not alfred.

  4. \( \) combining the whole pattern.

  5. PATTERN here is your pattern you are matching -> \s\{1,} (\s - space and {1,} as explained just above, search for 1 or more space)

"abc subtring def"

    :%s/\(.\{-}\zs\s\{1,}\)\{2}/,/

OUTPUT -> "abc subtring,def"

# explanation: first space would be between abc and substring and second 
# occurence of the pattern would be between substring and def, hence that 
# will be replaced by the "," as specified in replace command above. 
abhishek kasana
  • 1,462
  • 12
  • 14
1

This answers your actual question, but not your intent.

You asked about replacing the nth occurrence of a word (but seemed to mean "within a line"). Here's an answer for the question as asked, in case someone finds it like I did =)

For weird tasks (like needing to replace every 12th occurrence of "dog" with "parrot"), I like to use recursive recordings.

First blank the recording in @q

qqq

Now start a new recording in q

qq

Next, manually do the thing you want to do (using the example above, replace the 12th occurrence of "dog" with "parrot"):

/dog
nnnnnnnnnnn

delete "dog" and get into insert

diwi

type parrot

parrot

Now play your currently empty "@q" recording

@q

which does nothing.

Finally, stop recording:

q

Now your recording in @q calls itself at the end. But because it calls the recording by name, it won't be empty anymore. So, call the recording:

@q

It will replay the recording, then at the end, as the last step, replay itself again. It will repeat this until the end of the file.

TLDR;

qq
q
/dog
nnnnnnnnnnndiwiparrot<esc>
@q
q
@q
Sir Robert
  • 4,686
  • 7
  • 41
  • 57
0

Well, if you do /gc then you can count the number of times it asks you for confirmation, and go ahead with the replacement when you get to the nth :D

kprobst
  • 16,165
  • 5
  • 32
  • 53
0

Two regex do the job as initially intended:

%s/^\(.\{-}word\)\{3}\zs/* for each line, OR

%s/\(word\(\_.\{-}word\)\{15}\)\zs\(\_.*\)/*\3 for whole document, followed by:

%s/word\*/newword

Note that can change \{3} and \{15} for desired occurrence number, as well as the match word.

Alan Gómez
  • 211
  • 1
  • 6