How can I get the line numbers of all matching strings in a buffer?
Like in this buffer, I want to get the list [1,6,50] of all line numbers with matching strings.

1:  test ....

6:  test ....

50: test ....
  • 3
    Welcome to our site :) I suspect that you are facing a XY problem: what do you actually want to do with this list once you get it? Because there is possibly an easier way to do it than parsing the line number and then reusing these numbers. For example using this command :execute 'grep test ' . expand('%') | copen would get the lines matching your pattern in the quicklist window which is pretty useful. (The limitation being that you need your buffer to be saved in a file)
    – statox
    Jul 21, 2020 at 13:32
  • @statox, over the years I had quite a few cases where I needed to obtain the indices of the elements that match a predicate. Unfortunately Vim doesn't offer any simple solution. Jul 21, 2020 at 14:05
  • 2
    Something like let mylist = [] | g/^test/call add(mylist, line('.')) should work okay. But @statox is right: 9 out of 10 it's XY problem.
    – Matt
    Jul 21, 2020 at 14:12
  • 1
    @Matt this is also worth an answer :+1:
    – statox
    Jul 21, 2020 at 14:14
  • 1
    Still don't see what's problem with just :g. Internally it does two passes and works just okay. Do not reinvent the wheel.
    – Matt
    Jul 21, 2020 at 14:25

2 Answers 2


Doing two manual passes over the text is rarely needed. Most often one just does


And that's enough. The reason is that :g is implemented in two passes, so it's not a problem if you, say, add a few lines after each match.

However, if you really want those numbers in a List, you can achieve this with the same pattern:

let temp = []
g/^test/call add(temp, line('.'))
  • Thanks. i got it work. Jul 21, 2020 at 14:33

A convoluted (and untested) way I sometimes use. The idea is to use map() to associate line numbers to the actual (matching) lines, then filter() to keep only the matching lines, then map() again to return only the line numbers

" Note: This code uses lambdas and vim 8.{recent} methods.
:echo getline(1, '$')->map({l, v -> [l+1, v =~ 'test']})->filter({k,v -> v[1]})->map({k,v -> v[0]})

Another convoluted approach consists in using a function that permits to apply changes as matches are found (still untested)

:let g:lines = []
:call getline(1, '$')->map({k,v -> v =~ 'test' ? add(g:lines, k+1) : g:lines})
:echo g:lines

" or 
:call map(getline(1, '$'), {k,v -> v =~ 'test' ? add(g:lines, k+1) : g:lines})

Note: we could also use :global which is much simpler (see Matt's answer) but as it messes search register and cursor position, I usually avoid it when writing plugins.

  • I tried the second on neovim. i got trailing characters Jul 21, 2020 at 14:26
  • 1
    @xiaodonghuan Vim and Neovim are not 100% compatible. You should add the appropriate tag.
    – Matt
    Jul 21, 2020 at 14:31
  • @xiaodonghuan, I've fixed the second approach, and added the old method-less syntax. Jul 21, 2020 at 14:49
  • Note that the way lambda functions are used in Vim9 is slightly changed.
    – Barzi2001
    Apr 27, 2023 at 8:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.