Substitute the n-th occurrence of a word in vim
Asked Answered
M

7

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?

Monitory answered 19/6, 2010 at 0:39 Comment(1)
Apparently there is in sed, but not vim s///: vim.1045645.n5.nabble.com/…Aric
C
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.

Caprice answered 19/6, 2010 at 0:48 Comment(3)
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.Monitory
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.Caprice
-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.Mccready
H
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.

Hoffman answered 19/6, 2010 at 7:24 Comment(2)
@ Luc Hermitte: could you please tell me what does the '\%' mean in your answer. It seems it's an operator that VIM cannot recognize.Trilly
It's like \(, but it tells to not bind what is matched by the parenthesis to \1. See :h /\%(, IIRCHoffman
C
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.

Caprice answered 19/6, 2010 at 0:48 Comment(3)
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.Monitory
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.Caprice
-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.Mccready
H
7

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

:%s/\(\zsPATTERN.\{-}\)\{N}/REPLACE/
Habitude answered 27/8, 2017 at 4:12 Comment(2)
Could you tell me zs and {N} are the grammar of regular expressions or the grammar of vim?Curio
the Zoom anchors are Vim specifc AFAIK (other languages IDK), see this great answer 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.Executant
R
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. 
Rankins answered 2/8, 2019 at 12:4 Comment(0)
E
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
Elegit answered 26/7, 2017 at 6:5 Comment(0)
B
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

Bastinado answered 19/6, 2010 at 0:43 Comment(1)
Yup, but I want to substitute the n-th occurrence without the confirmation (like if I had to write a script to do it).Monitory
A
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.

Autogenous answered 29/6, 2023 at 5:1 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.