29

How can I reverse the order of lines so that the first line appears at the end and the last line appears first? (Could be all the lines in a buffer, an address range, or a linewise visual mode selection.)

I'd like to transform

rat
ox
tiger
⋮
dog
pig

into

pig
dog
⋮
tiger
ox
rat

without resorting to an external command such as tac.

3
  • Any suggestions for better tags on this question? Feb 22 '15 at 19:49
  • 1
    maybe a new 'pure-vi' or similar tag? I've seen several questions that would benefit from a tag that would indicate a desire to have no external tools involved. Should I ask about that on Meta?
    – John O'M.
    Feb 22 '15 at 20:19
  • 1
    @Carpetsmoker (and anyone else interested in following this) the tag question is now on meta meta.vi.stackexchange.com/questions/1229/…
    – John O'M.
    Feb 22 '15 at 21:36
35

The power of global will work here:

:g/^/exe "normal ddggP"

Or, more simply (thanks @tommcdo)

:g/^/move 0

The first will match every line and for each line, delete it and paste it to the top of the file. As it moves through the file, it reverses the text.

The second similarly matches every line and moves it to the top of the file.

Note: Both of these work on the whole file and will not apply correctly to reversing a subset of the lines. See Ingo Karkat's answer for a solution that works within a range.

Description:

g global command
/^/ match any line that has a beginning (i.e. all lines)
exe execute the following string
"normal perform normal mode commands
dd delete line
gg move to top of file
P paste above current position

move 0 moves the current line to below line 0 (which puts it at position 1, or the first line of the file)

8
  • 6
    Instead of the :normal command, we can use the Ex command :move 0, which moves the line to the beginning of the buffer.
    – tommcdo
    Feb 22 '15 at 20:21
  • 1
    Also :execute is only necessary when the command needs to be built dynamically, e.g. :execute 'normal' g:user_command.
    – tommcdo
    Feb 22 '15 at 20:23
  • @tommcdo good points! I'm in the habit of using :execute because I often end up appending other Ex commands after the existing one later, and it's more convenient for me to have the :exe there already than to have to go back and insert it later. Unfortunately, that habit leaked into this answer where it doesn't apply as much.
    – John O'M.
    Feb 22 '15 at 20:34
  • 1
    More explanation about my use of :execute: since it takes a string, it provides a clear delineation for where the normal-mode commands end, even though I'm not constructing the string, it's easier for me to find balanced quotation marks than to look for <esc> or whatever to terminate the mode. Again, this is personal preference and habit. :-)
    – John O'M.
    Feb 22 '15 at 20:41
  • 3
    This will work for a range btw: :9,11g/^/move 8 ... The last number needs to be the start of the range minus 1 (adapted from Ingo's answer). Feb 22 '15 at 21:05
13

This one-liner (for your ~/.vimrc) defines a :Reverse command; you can also use the :global part directly, but the syntax of the :move (which iteratively shifts the lines to before the start of the range, thereby reversing it) isn't easy to memorize:

:command! -bar -range=% Reverse <line1>,<line2>global/^/m<line1>-1
1
  • 1
    As a FYI for readers, the <line1> & <line2> are required to make this work on a range, ie: :7,9Reverse (they're features of command, not global or move). The simpler :command! -bar -range=% Reverse :global/^/m 0 will also work, but only for the entire buffer... Feb 22 '15 at 21:02
8

Pure Vim:

:g/^/m0

Explanation:

According to :help multi-repeat, :g and its cousin :v work in a two-pass manner.

The first pass of :g marks every line matching {pattern}, while the second pass (apparently performed starting at the file's beginning and proceeding to the end) performs the [cmd]. The above use of :g takes advantage of the order the lines are processed in (which is probably okay, though probably not technically guaranteed).

It works by first marking every line, then moving the first marked line to the top of the file, then moving the second to the top of the file (above the line moved previously), then the third marked line (again above the previously moved line), and so on until the last line in the file is moved to the top, effectively reversing the file.

Note that if :g processed lines in any order other than from top to bottom, this command would not work.

Source: Reverse all lines and Power of g at vim wikia.

Few examples using external commands:

  • tac (part of GNU coreutils - cat reversed):

    :%!tac                                                                                                                                                                                                                                                              
    
  • tail on BSD/OSX (not POSIX-compliant):

    :%!tail -r
    

    -r The -r option causes the input to be displayed in reverse order, by line.

    Check: man tar for more details.

For more ideas, see:

10
  • 3
    Isn't :g/^/m0 the same as :g/^/move 0, which is John's answer?
    – muru
    Feb 23 '15 at 13:15
  • @muru I think so, but this one is shorter (according to vim wikia) and I've added different explanation with few additional examples of using command lines.
    – kenorb
    Feb 23 '15 at 13:17
  • Yes, I upvoted because of the other commands (I came to post tac as well). But I suspect the downvote was because of the answer being repeated.
    – muru
    Feb 23 '15 at 13:18
  • I'm aware that tac was mentioned by OP, but all other similar questions would be duplicate of this anyway, so it's good to mentioned it again. John took this cmd from @tommcdo comment, I've taken it initially from DerMike, but I think he taken it simply from wikia, so I've gave credits to vim wikia, so it's not completely duplicate as the explanation is completely different.
    – kenorb
    Feb 23 '15 at 13:23
  • It adds more value, as it's much shorter version with proper explanation and I'm also crediting the right sources. Using shell commands is very simple and convenient. If people don't agree, they can simply down-vote, no big deal.
    – kenorb
    Feb 23 '15 at 16:03
6

In the spirit of functional VimL:

:call setline(1, reverse(getline(1, line('$'))))
  • getline(1, line('$')) returns a list of all the lines in the buffer. '$' is a special argument for line() which indicates the last line in the buffer.
  • reverse(...) reverses the input list, in-place. One would need to use reverse(copy(...)) if the input list shouldn't be modified.
  • setline(1, ...) replaces the specified line with the second argument. When the second argument is a list, the same number of lines as the length of the list is replaced with the content of the list.

If you like, you can also define a command which takes a range (default % entire buffer)

:command! -bar -range=% Reverse call setline(<line1>, reverse(getline(<line1>, <line2>)))
1
  • 1
    I like this answer. It also doesn't highlight stuff (if hlsearch is enabled) like the :g/ command from the other answers... Performance is perhaps worse, though? Since it getline(1, line('$')) gets the entire buffer in memory. reverse() seems to be in-place, so that should take very little memory as such... Feb 23 '15 at 15:35
5

Per the Vim Documentation usr_12.txt - Clever Tricks

12.4 Reverse line order

The :global command can be combined with the :move command to move all the lines before the first line, resulting in a reversed file. The command is:

:global/^/m 0

Abbreviated:

:g/^/m 0

The ^ regular expression matches the beginning of the line (even if the line is blank). The :move command moves the matching line to after the mythical zeroth line, so the current matching line becomes the first line of the file. As the :global command is not confused by the changing line numbering, :global proceeds to match all remaining lines of the file and puts each as the first.

This also works on a range of lines. First move to above the first line and mark it with mt. Then move the cursor to the last line in the range and type:

:'t+1,.g/^/m 't
1

Using relative numbers. Paragraph starts at line 13 and spams more 4 lines

 :13,13+4g/^/m12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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