created 2008 · complexity basic · author Hongleong · version 7.0
Often, when dealing with a data file in text format (e.g. csv), we need to massage the data to modify, filter or rearrange it. Using a clever trick to recursively use Vim's complex repeats (i.e. "macro recording"), such tasks can be done easily without having to be a REGEXpert.
Let's say we have a text file with the following contents:
dr-------- 20906 drwx------ 20913 drwxr-x--- 20704 drwxr-xr-x 21104 lrwxrwxrwx 20606 -------r-- 21004 -rw-r----- 20716 -rwxrwx--- 21102
For some reason, we want to move the 5-digit number at the end of the line to after the last "r" character on the same line, and we want to repeat it for the whole file. Notice that all these numbers begin with "2" and there can be multiple "r" per line.
Here's the end-result that we want:
dr20906-------- dr20913wx------ drwxr20704-x--- drwxr-xr21104-x lrwxrwxr20606wx -------r21004-- -rw-r20716----- -rwxr21102wx---
Yes, this can be done via :substitute
with back references (Try: %s/\v(.+r)(.+)\s(\d{5})/\1\3\2/
), but that can be intimidating for some. An easier, more flexible and often quicker method to accomplish the same task is to use "complex repeats", which really aren't too complex.
Here's how:
- Place the cursor on the first character of the first line of the file.
- Hit the following keys to record the repeat-sequence:
qqq
: Start and immediately end recording for registerq
-- this essentially empties registerq
qq
: Start recording actions into registerq
f2
: Find the "2" character to the right on this line (cursor will jump there)D
: Delete and yank the 5-digit number into memory (rest of line)Fr
: Find the "r" character to the left on this line (cursor will jump to the last "r")p
: Paste the number that was yanked earlier after the "r" character- Enter : Move the cursor to the first non-whitespace character of the next line
@q
: Execute the contents of registerq
(itself!), which is for now, empty, so nothing will happen. The trick here is that when we run it again with "@q" after you have ended the recording, it will call itself after processing each line! This recursive loop will process all the subsequent lines in that file, until it hits the end-of-file. This is the essence that makes complex repeats so flexible and powerful.q
: End the recording
- Hit
@q
to apply the same manipulation to the rest of the file from the 2nd line onwards. Enjoy the art of flying cursor and automatic text manipulation on your screen.
With this knowledge, you can now quickly perform complex editing of any structured text with relative ease.
This works much the same way as typing 9999@q
to execute the 'q' register as many times as the count you pass, except that you do not need to think about the size of your file before doing so to make sure enough 9s are added.
Ending the recursion[]
Normally, a macro recorded in this way will repeat until it reaches the end of the file. But, you can easily take advantage of the fact that a macro will stop running if it encounters an error to end the recursion when you want. For example, suppose you have a list of file names (perhaps you obtained it with :r !ls *.c
), and you want to verify that every one of these file names occurs in another buffer (a makefile, for instance). You could do this quickly and easily as follows:
- Format the file list to one file name per line, with each file name starting at beginning of the line
- Split a new window with the file you wish to contain all the file names (for a total of two windows)
- Position the cursor on the first line of the file list
- Record a recursive macro as follows:
qfq qf 0y$ <c-w>w /\V<c-r>"<cr> <c-w>w j@f q
The result is that register 'f' contains a macro that will search for every line of the list of file names, until it reaches the end of the list of file names or until it encounters an error such as "pattern not found: {filename}". If you run this macro on the file list, you will either see each file name highlighted in turn until the end of the list, or you will see a pattern not found error and (if 'hlsearch' is on) the missing file will be highlighted in the list.
If you don't wan't to rely on an error in this way to end the recursion, you could also do :if cond | exe 'normal @q' | endif
instead of @q
while recording the macro to provide a base case to end the recursion.
Adding recursion to an existing macro[]
Forget to add the recursion as a final step, or want to debug first before running the macro on every line in the file? An easy solution is, after you have the macro doing everything but the recursive call, just append it to the register. Assuming that you've recorded the macro into the 'q' register, just do this:
qQ @q q
This works using the uppercased character to append in the corresponding register, see Best Vim Tips: Appending to registers.
Note[]
One may wonder, "why is it necessary to empty out a register prior to recording into it?" A sequence such as qf
starts recording into an empty "f" register by definition. However, without emptying the register with qfq
prior to recording, the register will not be empty when you add @f
to your macro, and thus whatever is currently stored there will be executed, instead of doing nothing until you actually run the macro, as intended.
See also[]
Comments[]
As said under 'Note' this approach is a little tricky because it is easy to 'miss' prior contents of registers, which can become even more tricky when using viminfo (who isn't?).
Therefore, I vastly prefer recording the macro in non-recursive fashion (just leave off the trailing @f). Then, repeat your macro by doing general repeat (normalmode:) 100@q or 100@@ to repeat the last executed macro 100 times. This has the added benefit of being able to monitor/guide the process.
In many cases, it's both easier and faster to use the :normal command (which runs normal-mode commands from the command mode) in order to invoke macros. As an ex command, :normal can take a range of lines. In the example given at the top of the page, one could set the macro with qqf2dwFrpq
-- and then move to the next line and perform the macro on the remainder of the file with :.,$normal @q
. Since this doesn't use recursion, it's much faster -- especially noticeable on large files (though, of course, it still won't be as fast as a regex or a specialized external command).