replace nth occurrence on each line like sed

3,155 views
Skip to first unread message

anokun7

unread,
Jan 13, 2008, 4:57:01 PM1/13/08
to vim_use
I assumed vim is directly compatible with sed when you use the :s
substitute commands.

What I am trying to do is to replace the nth occurrence of a
character with something else.

In sed, you could do: s/c/char/3

So in a set of lines like this

abc abc abcd
c c c c
abcd abc

it would become - after substitution:

abc abc abchard
c c char c
abcd abc

Seems like it doesnt work - when I do that in VIM, I get the first
occurence in the 3rd line replaced to become:

abc abc abcd
c c c c
abchard abc

Am I doing something wrong or is VIM not like sed at all?

Thanks


Andy Wokula

unread,
Jan 13, 2008, 5:24:19 PM1/13/08
to vim...@googlegroups.com
anokun7 schrieb:

I've also missed this feature several times.

It is mentioned in the todo list:
:h todo

- ":s/pat/foo/3": find 3rd match of "pat", like sed. (Thomas Koehler)

8 Add an argument after ":s/pat/str/" for a range of matches. For example,
":s/pat/str/#3-4" to replace only the third and fourth "pat" in a line.

- Add number option to ":s//2": replace second occurrence of string? Or:
:s///N substitutes N times.

--
Andy

Tony Mechelynck

unread,
Jan 13, 2008, 5:39:09 PM1/13/08
to vim...@googlegroups.com

Vim substitute is no more identical to sed, than Vim regexps are identical to
perl regexps. They may be similar, but Vim doesn't try to behave identically
as any other program (except Vi when in 'compatible' mode, and even then there
are documented differences). To learn how ":s" works in Vim, you should read
":help :s" and what it resends to, not "man sed". In particular, the syntax of
the ":s" command is defined as

:[range]s[ubstitute]/{pattern}/{string}/[flags] [count]

Under ":help :s_flags", no numeric flags are mentioned. I suppose your 3 is
understood as a count, i.e., replace in three lines, as mentioned under ":help
:s". The expression "replace in three lines" might be ambiguous; it may depend
on the presence or absence of the "g" flag. Without it, only the first
occurrence on its line is replaced. To replace the 3rd occurrence on all
lines, you could for instance use (untested)

:1,$s/^[^c]*c[^c]*c[^c]*\zsc/char

Use a different range to act on only part of the file.


Best regards,
Tony.
--
hundred-and-one symptoms of being an internet addict:
234. You started college as a chemistry major, and walk out four years
later as an Internet provider.

A.Politz

unread,
Jan 13, 2008, 6:31:41 PM1/13/08
to vim...@googlegroups.com
anokun7 wrote:

:call feedkeys('nnyq') | s/c/char/gc

What's the problem ? ;-)

Putting in it in a command :

com! -nargs=1 -range SedS <line1>,<line2>call SedS(<q-args>)

"%SedS/b/a/2

func! SedS( s_expr ) range
if a:s_expr !~ '\d\+$'
exec a:firstline.','.a:lastline.'s'.a:s_expr
else
let nth = matchstr(a:s_expr,'\d\+$')+0
let fk_expr = repeat('n',nth-1)."yq"
let s_expr = strpart(a:s_expr,0,strlen(a:s_expr)-strlen(nth))
for line in range(a:firstline,a:lastline)
exe 'call feedkeys("'.fk_expr.'") | '.line.'s'. s_expr .'gce'
call inputsave()
endfor
endif
endfun


Note: That's just a proof of concept, don't ask me where all the saved
keystrokes endup ( inputsave() ).

-ap

--
Ich hab geträumt, der Krieg wär vorbei.

Andy Wokula

unread,
Jan 15, 2008, 7:51:59 AM1/15/08
to vim...@googlegroups.com
A.Politz schrieb:

Nice idea. For the few occasions where this is needed, it's a really cool
workaround. Here with some improvements:

- added :sil-ence
- uses input() instead of inputsave() -- no risk of mem overflow
- prints the number of changes, like the original :s would do
- untested: the surrounding inputsave()/inputrestore() pair will be needed
when used within a mapping

com! -nargs=1 -range SedS <line1>,<line2>call SedS(<q-args>)

"%SedS/b/a/2

func! SedS( s_expr ) range
if a:s_expr !~ '\d\+$'
exec a:firstline.','.a:lastline.'s'.a:s_expr

return
endif
call inputsave()


let nth = matchstr(a:s_expr,'\d\+$')+0

let fk_expr = repeat('n',nth-1)."yq\r"


let s_expr = strpart(a:s_expr,0,strlen(a:s_expr)-strlen(nth))

let nchanges = 0


for line in range(a:firstline,a:lastline)

exe 'call feedkeys("'.fk_expr.'") | sil' line.'s'. s_expr .'gce'
sil let nchanges += input("") !~ "y"
endfor
if nchanges > &report
echo nchanges "substitutions on" nchanges "lines"
endif
call inputrestore()
endfunc

--
Andy

A.Politz

unread,
Jan 15, 2008, 9:00:09 AM1/15/08
to vim...@googlegroups.com
Andy Wokula wrote:

>A.Politz schrieb:


>
>
>>anokun7 wrote:
>>
>>
>>
>>>In sed, you could do: s/c/char/3
>>>
>>>
>>>

>>:call feedkeys('nnyq') | s/c/char/gc
>>
>>What's the problem ? ;-)
>>
>>
>>
>>
>

>Nice idea. For the few occasions where this is needed, it's a really cool
>workaround. Here with some improvements:
>
>- added :sil-ence
>- uses input() instead of inputsave() -- no risk of mem overflow
>- prints the number of changes, like the original :s would do
>- untested: the surrounding inputsave()/inputrestore() pair will be needed
> when used within a mapping
>
>

- avoid redraws per lazyredraw


com! -nargs=1 -range SedS <line1>,<line2>call SedS(<q-args>)

fun! SedS( s_expr ) range


if a:s_expr !~ '\d\+$'
exec a:firstline.','.a:lastline.'s'.a:s_expr
return
endif

let lz_save = &lz
set lz


call inputsave()
let nth = matchstr(a:s_expr,'\d\+$')+0
let fk_expr = repeat('n',nth-1)."yq\r"
let s_expr = strpart(a:s_expr,0,strlen(a:s_expr)-strlen(nth))
let nchanges = 0
for line in range(a:firstline,a:lastline)
exe 'call feedkeys("'.fk_expr.'") | sil' line.'s'. s_expr .'gce'
sil let nchanges += input("") !~ "y"
endfor
if nchanges > &report
echo nchanges "substitutions on" nchanges "lines"
endif
call inputrestore()

let &lz = lz_save
endfun

Christian Brabandt

unread,
Jan 15, 2008, 2:17:21 PM1/15/08
to vim...@googlegroups.com
Hi anokun7!

On Sun, 13 Jan 2008, anokun7 wrote:

> What I am trying to do is to replace the nth occurrence of a
> character with something else.
>
> In sed, you could do: s/c/char/3
>
> So in a set of lines like this
>
> abc abc abcd
> c c c c
> abcd abc
>
> it would become - after substitution:
>
> abc abc abchard
> c c char c
> abcd abc
>
> Seems like it doesnt work - when I do that in VIM, I get the first
> occurence in the 3rd line replaced to become:
>
> abc abc abcd
> c c c c
> abchard abc
>
> Am I doing something wrong or is VIM not like sed at all?

Why don't you filter your lines through sed?


regards,
Christian
--
:wq!

Reply all
Reply to author
Forward
0 new messages