Matt Thompson
Matt Thompson

Reputation: 659

Vim: Replace first word on each line of selection with number in list

I have a large file containing a selection with jumbled numbers. It's supposed to be a seqeunce 1, 2, 3, ... but a few lines got screwed up. I would like to change something like

foo

bar

1 foobar

12345 foobar

6546458 foobar

4 foobar

to

foo

bar

1 foobar

2 foobar

3 foobar

4 foobar

I know I can use something like 3,$ to select the lines I care about and put = range(1,1000) to make new lines starting with the numbers I want, but I want to put these numbers on lines that currently have data, not new lines. The jumbled numbers are several characters long, but always one word. Thanks.

Upvotes: 1

Views: 1953

Answers (3)

Markus Jarderot
Markus Jarderot

Reputation: 89221

/^\d\+\s  -- Searches for the first occurrence
ciw0<Esc> -- Replaces the word under cursor with "0"
yiw       -- Copies it
:g//norm viwp^Ayiw
          -- For each line that matches the last search pattern,
          --   Replace the current word with copied text,
          --   Increment it,
          --   Copy the new value.

(<Esc> is just Esc. ^A is entered as Ctrl+V, Ctrl+A)

Upvotes: 1

Peter Rincker
Peter Rincker

Reputation: 45147

Do the following:

:let i=1
:g/^\d\+/s//\=i/|let i=i+1

Overview

Setup some variable (let i=1) to use as our counter. On every line that starts with a number (:g/^\d\+/) we execute a substitution (:s//\=i/) to replace the pattern with our counter (\=i) and then increment our counter (let i=i+1).

Why use :g at all? Why not just :s?

You could do this with just a substitution command however the sub-replace-expression, \=, needs an expression to evaluate to a value (see :h sub-replace-expression). As let i = i + 1 is a statement it will not be useful.

There are a few ways to overcome this issue:

  • Create a function that increments a variable and then returns it
  • Use an array instead and then alter the number inside (in-place) then return the value out of the array. e.g. map(arr, 'v:val+1')[0]
  • If you only have 1 substitution per line then do the :g trick from above

Full example using in-place array modification:

:let i=[1]
:%s/^\d\+/\=map(i,'v:val+1')[0]

Personally, I would use whatever method you can remember.

More help

:h :s
:h sub-replace-expression
:h :g
:h :let
:h expr
:h map(
:h v:val

Upvotes: 4

Birei
Birei

Reputation: 36272

You can use the following function:

function Replace()
    let n = 1 
    for i in range(0, line('$'))
        if match(getline(i), '\v^\d+\s') > -1
            execute i . 's/\v^\d+/\=n/'
            let n = n + 1 
        endif
    endfor
endfunction

It traverses the whole file, check if each line begins with a number followed by a space character and does the substitution with the counter that increments with each change.

Call it like:

:call Replace()

That in your example yields:

foo
bar
1 foobar
2 foobar
3 foobar
4 foobar

Upvotes: 0

Related Questions