13

First of all, with this being my first post here, I'd just like to say that I've found VIM to be a great tool and the forum here to be very helpful in finding answers to questions, with lots of helpful people providing invaluable assistance. I'm still very new to VIM, so pretty much everything I've learned about it has come from here.

My question is: I know how to reverse ALL the lines in a file (:g/^/m0 among other ways), but is there a way to reverse every 4 lines in a file, so that

line1
line2
line3
line4
line5
line6
line7
line8
...

becomes

line4
line3
line2
line1
line8
line7
line6
line5
...

You can assume that there will always be an exact multiple of 4 lines in such files.

1
  • 2
    About your "p.s": if you insert 4 spaces in front of a line it is interpreted as code (you can select the text and use the format buttons on the top). You can find more details on the interrogation icon on the top.
    – mMontu
    Jan 29, 2016 at 11:01

4 Answers 4

15

The command :Reverse explained at Vim Wiki can be used for this (you may include it in your .vimrc to make it permanent):

command! -bar -range=% Reverse <line1>,<line2>g/^/m<line1>-1|nohl

Then you can record a macro to run the command on every four lines:

qmV3j:Reverse<cr>4jq
1000@m

Explanation:

  • qm: 'q' in normal mode starts (and stops) recording a macro in a given register (register 'm', in this case, but it could be any other letter)
  • V: enter visual mode and select the current line
  • 3j: expand the visual selection to the next 3 lines
  • :Reverse<cr>: run the Reverse command on the selected lines (<cr> here stands for the enter key)
  • 4j: go to the next unchanged lines
  • q: stops recording the macro
  • 1000@m: run the macro recorded at register 'm' 1000 times (you may increase this number if your file is larger than 4000 lines)

Edit:

As mentioned in the comments, you could use a recursive macro instead of using a count:

qmV3j:Reverse<cr>4jq
qM@mq
@m
  • qM: if the register specified for q is uppercase the macro is appended to the register (which is also useful when you realize when you missed the last steps on a complex macro)
  • @m: run the macro on register m
  • q: stop appending the macro

Despite it is created as a macro, you can create a command for this if it is a common task on your workflow:

function! Reverse4()
   let reg_m = @m
   let @m = '<c-r><c-r>m'
   normal! @m
   let @m = reg_m
endfunction
command! Reverse4 call Reverse4()
  • function! Reverse4()/endfunction: defines a new function
  • let reg_m = @m: saves the current contents of register m
  • let @m = "<c-r><c-r>m": insert the macro in the register m -- note that <c-r> is the vim notation for Ctrl+r and that you should type this, not copy/paste, so your line will be similar to let @m = 'V3j:Reverse^M4j@m' and it will contain a special character (^M)
  • normal! @m: the normal command run its argument as it has been typed in normal mode, so it will run the recursive macro
  • let @m = reg_m: restores the contents of the register m, so you don't have to remember that this register is being used on this function and avoid using it

  • command! Reverse4 call Reverse4(): create a new command for this function

Depending on your needs you could enhance it, e.g.: pass an argument to the command and function so it would work for any number of lines instead of being fixed in groups of 4 lines.

6
  • Instead of the 1000@q hack, you can also use a recursive macro: qmqqm ... @mq@m.
    – Doorknob
    Jan 29, 2016 at 13:06
  • When I tried to run the "qmV3j:Reverse<cr>4jq", it said "E492: Not an editor command". I must be doing something wrong. I'm using - and have only ever used - GVIM, by the way. I should have mentioned that in the first place. Jan 29, 2016 at 16:51
  • Ah, never mind. I figured it out - I am not supposed to type ":" before the "qmV3j:Reverse<cr>4jq" part. It worked! Thank you very much, mMontu. If I may ask - how do I include the ":Reverse" command in my .vimrc? Jan 29, 2016 at 17:27
  • 3
    @ablewasiereisawelba To make the command available permanently, simply write the line command! -bar -range=% Reverse <line1>,<line2>g/^/m<line1>-1|nohl somewhere in your vimrc.
    – saginaw
    Jan 29, 2016 at 17:33
  • @saginaw Oh, I didn't realize it was that straightforward. Thank you. Jan 29, 2016 at 17:39
9

As with all repetitive actions, if you can do something once with basic edit operations, then you can easily do it lots of times by recording a macro.

(In this case I shall use a recursive macro, but you could just record a non-recursive one and play it back multiple times by hammering @@ or by using a count.)

ggqqqqqddjjpkddkkPjddpjj@qq@q

Broken down

  1. gg: Move to the start of the file.
  2. qqq: Clear out register q. We'll see why this is necessary in step 5.
  3. qq: Start recording a macro to register q
  4. ddjjpkddkkPjddpjj: A series of operations that reorders the first four lines simply by deleting and pasting them manually. (This might look complicated at first glance, but don't be deceived. This is very basic stuff that you'd have learned in the first 5 mins or so of vimtutor: j, k, dd, p, P)
  5. @q: Call the macro! At this point, register q contains nothing (because we cleared it in step 2), so nothing will happen.
  6. q: Stop recording the macro.
  7. @q: Replay the recursive macro as many times as necessary.

N.B. The above won't produce the desired results if the file contains a number of lines that is not exactly divisible by four. In order to stop the macro early if there aren't four lines left, we need to carry out a Vim normal-mode command that will fail, before we start applying the edits in step 4. We can do this by attempting to move down a line three times (and then back up) before we start moving lines around: jjj3k

Thus:

ggqqqqqjjj3kddjjpkddkkPjddpjj@qq@q
10
  • Why do you explicitly clear out register q? If you just record into it, whatever was in there will get overwritten anyway....
    – Wildcard
    Jan 29, 2016 at 14:34
  • 5
    @Wildcard Because it's a recursive macro, the first time you call the content of register q, it must be empty, otherwise it will mess up your editions.
    – saginaw
    Jan 29, 2016 at 14:39
  • 1
    Very nice. I tried this interactively without trying to type the exact sequence of your command and wound up using: qqqggqqjjjkddkkPjddjpkddkkPjjjj@qq@q
    – Wildcard
    Jan 29, 2016 at 14:44
  • 1
    @ablewasiereisawelba where I can just use the one-line custom command each time I need to do this -- note that you doesn't have to re-create the macro every time you need to use it, as it is possible to store it as a function/command on your vimrc.
    – mMontu
    Feb 1, 2016 at 11:20
  • 1
    @ablewasiereisawelba I'm glad you found the "Edit" useful! As this was your first post here maybe you are unaware of how StackExchange notifications works: when you post a comment, the author of the answer/questions receives a notification; other persons will receive a notification only if you include the @<nick>. Thus only Rich received notifications about your last messages.
    – mMontu
    Feb 12, 2016 at 10:12
7

You can also do this with an Ex command using sed as an external filter:

:%!sed -n 'h;n;G;h;n;G;h;n;G;p'

This version will ignore (delete) any extra lines beyond a multiple of 4. To keep in the last set of less than 4 lines (reversed), use:

:%!sed -n '$p;h;n;G;$p;h;n;G;$p;h;n;G;p'

The % here means "Every line in the buffer."

The ! command means "Run the following command with the specified lines as input, and replace the specified lines with the output of the command." (It's called a filter; very handy for such things as sorting, e.g., :%!sort will sort all the lines in your file; :2,8!sort will sort lines 2-8, etc.)

sed is the stream editor tool and is found on all Unix-like systems. The key concepts of sed used here are the "pattern space" (which by default just contains each line of the input in turn), and the "hold space" (which is where you can stick extra text while using sed to save it while processing other lines of input).

-n is an option to the sed command to suppress its default actions of printing the pattern space (because in this case we only want to print when we explicitly say so.)

$p in the sed command means "If you are on the last line of sed's input, print (the pattern space)."

h means "stick the current contents of the 'pattern space' in the 'hold space', overwriting whatever's there."

n means "replace the contents of the 'pattern space' with the next line from the input."

G means "append to the 'pattern space': a newline followed by the contents of the 'hold space'."

Taken all together, the sed command stores up four lines of output, reversing them as it stores them, and then prints them. The $p commands added in the second version ensure that if the last line of the file is reached other than on a multiple of 4 lines, the lines are still printed.


For an alternate, interactive approach still without using any Vim-specific features and also without using an external filter:

:4

to go to the fourth line.

:.m -4 | +3m . | +2m . | +5

to reverse the previous four lines (1-4) and leave your cursor on line 8.

.m -4 moves the current line to just after the line four lines back (leaving the cursor on the moved line).

+3m . moves the line that is 3 lines after the current line, to just after the current line, leaving the cursor on the moved line. +2m . of course works the same.

+5 places the cursor five lines down from where it is.

Repeat as desired.

In Vim you can repeat this whole command with @:, then repeat again with @@. In POSIX vi or ex you would need to insert :.m -4 | +3m . | +2m . | +5 as a line of text, delete it to a named buffer (register), and then execute that named buffer (register).

So in ex mode, reversing lines interactively using POSIX-specified features only, and starting with 17 lines of text:

Entering Ex mode.  Type "visual" to go to Normal mode.
:0a     # Append following text after "line 0" (i.e. insert at start of file).
.m -4 | +3m . | +2m . | +5
.       # End text insertion
:d k    # Delete that line to register k
line1   # This is a printout of the current line
:4      # Move to line 4
line4
:@k     # Execute register k to reverse lines 1-4
line8
:@@     # Execute register k again
line12
:@@     # Execute register k again
line16
:@@     # Execute register k again
line17
:%p     # Print the whole buffer (just to see what was done)
line4
line3
line2
line1
line8
line7
line6
line5
line12
line11
line10
line9
line16
line15
line14
line13
line17
:wq     # Save and quit

Further reading:

5
  • Could you explain a little more your solution? Jan 29, 2016 at 12:04
  • 3
    @vappolinario, I added some further explanation. Does that help? :)
    – Wildcard
    Jan 29, 2016 at 12:35
  • Wow, very long answer. :) I have sed, actually, but I've just used it with one simple script that I found somewhere - probably on this board - that was a solution to a problem I had. However, when I tried to run your suggested line, it says "'sed' is not recognized as an internal or external command, operable program or batch file." I'm not sure if I need to have sed in the vim folder or what. Jan 29, 2016 at 16:58
  • 1
    @ablewasiereisawelba, if you're running on a Windows box and not using Cygwin (or MobaXterm or similar), you could try the other method I gave: 4G:.m -4 | +3m . | +2m . | +5<Enter>@: then @@ repeated until your file is fully processed. I recommend installing MobaXterm, though. :)
    – Wildcard
    Jan 29, 2016 at 17:20
  • @Wildcard Oh okay, I'm checking out MobaXterm now. :) Jan 29, 2016 at 17:33
7
:g/^/exe 'm .-' . substitute(line('.') % 4, '^0$', '4', '')

Plain English: For every line, move the current line up lnum % 4, unless lnum % 4 == 0, in which case move the current line up 4.

Also, to reverse every n lines, replace the 4's in the above command with n.

1
  • 2
    Very nice one-line command. Thank you, djjcast. Feb 2, 2016 at 17:37

Your Answer

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

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