I have a text file like this (using gVim on Windows)

foo bar baz quux 
corge grault garply 
waldo fred plugh 
[...150 more lines...]
xyzzy thud

I want to add a number to each line in the file. Not using :set number, but to add the number as text preceding each line, as follows, so the number is part of the file.

1. foo bar baz quux 
2. corge grault garply 
3. waldo fred plugh 
[...~150 more lines...]
155. xyzzy thud

There are some relevant answers on this StackOverflow question.

  • 1
    Perhaps.. Or is this more general?
    – muru
    Commented Feb 14, 2015 at 22:25
  • It's similar, but I wouldn't know what are permanent line numbers. Secondly the other question is about all lines (and the answer does that) for gVim on Windows specifically and this is simple numbered list for one paragraph only in plain vim.
    – kenorb
    Commented Feb 14, 2015 at 22:42
  • 3
    Well, I guess that post uses "permanent" ot indicate that the buffer is to be modified, and that the numbers aren't some purely visual thing (same as you). The reason for specifying gvim on Windows is to avoid external utilities like cat or nl, which can do number lines, but aren't generally available on Windows (as OP indicates from their comment about awk). The top two solutions are pure Vim. Lastly, all lines vs one para is just a matter of range selection. Clearly not a big issue.
    – muru
    Commented Feb 14, 2015 at 22:45
  • Still not convinced, as you can get Cygwin, MSYS and SUA on Windows, and the last answer (cat -n) covers that case, but not for paragraphs, I suppose.
    – muru
    Commented Feb 14, 2015 at 22:51
  • Then there is no point of having OS-dependent tags, if you can always install cygwin/msysgit.
    – kenorb
    Commented Feb 15, 2015 at 1:42

8 Answers 8


In pure Vim fashion:

:%s/^/\=line('.').". "


:%s/^/            " the substitution will be applied to the beginning of every line
\=                " the rest of the replacement part is an expression
line('.').". "    " the expression returns the current line number concatenated with a dot and a space

See :help \= and :help line().

Using an expression in the replacement part is very powerful and FWIW a pretty good point of entry to vimscript.

  • How can I add this very useful command to a keymap in vimrc?
    – cosmicraga
    Commented Jul 20, 2019 at 8:26
  • 1
    To get to the vim help topic for substitution: :help sub-replace-expression
    – akurtser
    Commented Sep 1, 2019 at 13:00

One nice thing about Vim macros is that they can recurse (they can invoke themselves):

  1. Clear out register q: qqq
  2. Add the number to the first line: ggI1. (don't forget the space!)
  3. Move back to start of line and start recording a macro: 0qq
  4. Copy the number: yW
  5. Move down a line and paste the number: +P
  6. Move back to the start of the line and increment the number: 0<c-a>
  7. Move back to the start of the line (so the macro doesn't break when it gets to double figures!): 0
  8. Call the macro once, to make it recursive. At this point, there is still nothing in register q, so nothing will happen: @q.
  9. Save the macro: q
  10. Call the macro one more time, and watch the sparks fly!: @@

The macro will then continue invoking itself until it reaches the end of the file.

You can use the recursive macro trick for lots of other similar problems, so it's a good one to be aware of.

If you don't want to use a recursive macro for some reason, you can omit steps 1 and 8, and use a count to run the macro multiple times, e.g. 100@q will run macro q 100 times.

  • 1
    Powerful stuff, I bow to your mastery. Macros are like black magic to me...
    – roblogic
    Commented Feb 11, 2015 at 12:09
  • 1
    @ropata, a macro is only a sequence of (mostly) normal mode commands.
    – romainl
    Commented Feb 11, 2015 at 13:37
  • 1
    @romainl I think it's better to think of it as a sequence of keystrokes.
    – Rich
    Commented Feb 11, 2015 at 13:47
  • 2
    @Rich, it can be a sequence of many things, including ex commands.
    – romainl
    Commented Feb 11, 2015 at 21:34
  • 2
    @romainl Yes, that's why I think it's best to think of it as keystrokes. It plays back exactly what you type on the keyboard (including, as you say, ex commands), as if you had typed it all in manually.
    – Rich
    Commented Feb 12, 2015 at 9:46

Add numbers to all lines

It's possible to use :%!nl -ba or :%!cat -n commands which will add line numbers to all the lines.

On Windows, you've to have Cygwin/MSYS/SUA installed.

Add numbers to selected lines

To add numbers only for selected lines, please select them in visual mode (v and cursors), then when finished - execute the command: :%!nl (ignore blank lines) or :%!cat -n (blank lines included).


To remove extra spaces, select them in visual block (Ctrl+v) and remove them (x).

To add some characters (., :, )) after the numbers, select them in visual block (Ctrl+v), then append the character (A, type the character, then finish with Esc).


I like to use the vim global command to accomplish tasks like this. This applies to adding iteration to the beginning of a line or modify a symbol in the text. It looks more complicated than the other solutions, but is a pretty flexible pattern to use when you have it handy, and is easy to modify without a lot of thought.

First, pick your range (which lines you want to apply this). I usually use marks (e.g. ma on the first line and mb on the second, but you can also use line numbers or visual selection), then enter a modification of the following command (currently tweaked for your use case)

:let i=1|'a,'bg/^/s/^/\=i.". "/|let i=i+1


:let i=1

This sets up variable i with a start value. Usually lists start with 1, so I'm setting i to 1.


The bar starts a new command


This sets the range of the next command. I'm going from mark a to mark b, which would be set on the first line and last line of your list.


This is the global command. It searches the file (or range) for a given regular expression, and will execute the rest of the command line on each of the lines that matched. I'm matching every line by searching for "beginning of line". If you had text like

Item some txt
other text

Item second item
Item third

and only want to put these labels in front of Item and ignore the other lines, do g/Item/ or g/^Item/ instead (assuming the literal Item text)

s/^/\=i.". "/

This executes the regular expression to replace the beginning of the line with the value of i concatenated with a .. Generally you can do this to anything (replace the label Item with the number, for example).

|let i=i+1

Even though the bar starts a new command, it sets up a second command to run within the global command, instead of after the global is complete. The result is we increment i before the next line is processed by g. Here is another place of flexibility. The modification of i can be anything (increment by 2, call a function that generates the next element of the Fibonacci sequence, whatever).

  • in your example one could in principle use :let i=1| %s/^/\=i.". "/ | let i=i+1, however, this won't increment the variable i. Why is this the case? So that everything works, one has to use either 'a,'bg/^/s/^/\=i.". "/ or even simpler no range at all g/^/s/^/\=i.". "/' Commented Jan 7, 2022 at 14:39

A modification of romainl's answer:

:%s/^\(\d\+\. \)\?/\=line('.').". "

This will not just add line numbers, it will also replace exiting line numbers it they're already there. Of, if you've inserted a line mid-way, it will renumber everything as expected.

This works by replacing any number followed by a . and a space at the start of the line with a new number. This will obviously break if you have a line that already starts with this pattern, so use with thought.

The added part:

  • ^ - Start of line
  • \( - Start new subgroup
  • \d\+ - Match a digit once or more
  • \. - Match a dot (.) and a space .
  • \) - End subgroup
  • \? - Make the group optional, so that it works as before if there's no number on this line yet.

Bonus hint:
To remove the line numbers, you can use the same pattern with the repalce part empty:

:%s/^\(\d\+\. \)\?//
I1. <esc>^qqyWjP^<C-a>q

This numbers the first two lines, and you can press @q to number subsequent lines (or type ex. 18@q if you want to number 20 lines total).


I1. <esc>  Number the first line
hqq        Go back to the start of the line and start recording a macro
yWjP       Copy the line number to the next line
^<C-a>     Increment the next line's line number
q          Finish recording

The benefit of this is that it doesn't require any external commands, which is useful if you're working with Vim on Windows, for example.

  • After typing 1. <esc>h you are on the second column, not first column. I would replace the h with 0, after which I think your solution should be very good. Commented Feb 15, 2015 at 9:26
  • @KarlYngveLervåg Whoops, that was a typo. Thanks, fixed it.
    – Doorknob
    Commented Feb 15, 2015 at 14:18
  • No problem. However, you still have not updated the explanation. Also: On many keyboards, ^ waits for a second character in order to allow typing combinations like ^a -> â. I still agree its the best solution, but I think this should be mentioned as well. Commented Feb 16, 2015 at 10:48

I think the chosen answer is the best, but in the sprit of variety, I'll offer an alternative using an external program:

:%!cat -n

This will filter your entire buffer (as denoted by %) through the external program, cat, where the -n flag prepends each line of input with a line number.

This, of course, requires that you have cat installed, which is true for (probably) all Unix-like systems.

Check out :help :range! for more details on filtering through external programs.

  • 2
    I realize that the asker is using gVim on Windows, so this solution probably won't work there. However, I think it still provides some opportunity for others to learn from it.
    – tommcdo
    Commented Feb 11, 2015 at 14:28
  • If you've installed msysgit and added that to your PATH (IIRC it's an install option), this solution should also work on Windows. Commented Feb 11, 2015 at 14:40
  • 5
    cat -n is not POSIX, but nl is, so it might be a better option.
    – muru
    Commented Feb 11, 2015 at 14:43

A bit hackish solution might be the following (everything written between < and > are to be inserted after pressing Ctrl+v):

:%normal :redir @"<Enter>:-=<Enter>:redir END<Enter>I<C-R>".<Tab><Esc>kdd


:%normal {commands}

runs normal mode command on each line specified by the range, in this case every line

:redir @"

redirects every output made by ex commands to the unnamed buffer.


is an ex command that outputs the current line number (with a preceding newline unfortunately)

:redir END

stops redirecting to the unnamed buffer


inserts the content of the unnamed buffer with a . and a tab to to front of each line and exits from insert mode.


goes one line up and removes the newline which is the result of the :.= command.

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.