9

I have a number of lines in a file, and I would like to copy each line 11 times (turning each line into 12 lines), and increment the last "1" in each line so that the 12 lines have "1" through "12", where the "1" initially was. There may be other occurrences of "1" in each line, but the "1" I want to increment will always be the last occurrence in each line. Another way to look at it is that the last "1" is always after "/nt/" - as in "/nt/1" (and it will always be the only occurrence of "/nt/1" in each line).

So, for example, if I have:

1stlineblahblahblah/nt/1blah
2ndlineblahblahblah/nt/1blah
3rdlineblahblahblah/nt/1blah

I want to turn it into:

1stlineblahblahblah/nt/1blah
1stlineblahblahblah/nt/2blah
1stlineblahblahblah/nt/3blah
1stlineblahblahblah/nt/4blah
1stlineblahblahblah/nt/5blah
1stlineblahblahblah/nt/6blah
1stlineblahblahblah/nt/7blah
1stlineblahblahblah/nt/8blah
1stlineblahblahblah/nt/9blah
1stlineblahblahblah/nt/10blah
1stlineblahblahblah/nt/11blah
1stlineblahblahblah/nt/12blah
2ndlineblahblahblah/nt/1blah
2ndlineblahblahblah/nt/2blah
2ndlineblahblahblah/nt/3blah
...

I had previously found the command:

:for i in range(0,12) | put ='1stlineblahblahblah/nt/'.i.'blah' | endfor

works for this purpose, but I would have to manually run this command for each line, and type each line in (or copy-and paste it) myself. Is there a way to take the lines that are already in the file, and just run one command that turns each line into twelve, in the manner that I've described?

Thanks in advance to anyone who can help me with this. I also just wanted to note that this is my second question here, and I was pleased to have gotten several quick and effective solutions to my first question, for which I was most grateful.

5 Answers 5

8

Here's a substitution that solves the problem:

:%s/\(.*\)1\(.*\)/\=join(map(range(1, 12), 'submatch(1) . v:val . submatch(2)'), "\n")

The substitution matches each line that contains "1" and captures the text before {c1} and after {c2} the last "1". For each matched line, the range of numbers from one to twelve {n} are mapped to create twelve lines of the form {c1}{n}{c2}. Each group of twelve lines replaces its associated, originally matched line.

See :h sub-replace-expression.

1
  • 2
    Wow, great one-line command. I remember that you also found an elegant one-line solution for the first question I asked a few weeks ago. Very nice work. Thank you very much. Also, I'm guessing there's a way to put this in the _vimrc file so that I can execute an easy-to-remember command or function without typing the entire line above (or having to scroll through the list of previous commands), but I'm not yet familiar enough with the _vimrc syntax to know how to do it. Commented Feb 17, 2016 at 2:51
10

You could do this by

  1. recording a macro, then
  2. using the global ex command to execute the macro n number of times for each line in the file.

After recording the macro, undo the changes done while recording, or there will be n + 1 additional lines for the first line, and n for consecutive lines.

Record the macro to the a register with

qayyp$?\d<CR><C-A>q

This records into register a (qa...q) the following command:

  • yyp: duplicate the current line
  • $: move to end of line
  • ?\d<CR>: search backwards for a single digit
  • <C-A>: increment digit under the cursor by one

When the macro is recorded, remove the changes made while recording it either by undoing (uu) or by deleting the current line (dd). Then repeat the macro 11 (or any number of) times for each line in the file with the global command:

:g//normal 11@a
1
  • 2
    It worked! Very nice, thank you. I had a problem initially with the Ctrl-a highlighting everything instead of adding, because I'm using gVim in Windows... but I did a quick search and found this thread - stackoverflow.com/questions/289681/… - where I used the suggestion of putting nnoremap <kPlus> <C-a> nnoremap <kMinus> <C-x> in the _vimrc so that I can use the "+" and "-" keys for adding and subtracting. Also, the global command needed to be :g//normal 11@a for me, but that was easily figured out. Thank you! Commented Feb 17, 2016 at 2:01
6

Two ways:

Use a macro!

Starting with

1stlineblahblahblah/nt/1blah
2ndlineblahblahblah/nt/1blah
3rdlineblahblahblah/nt/1blah

With your cursor on the first line

qqyyp$?\d<CR><Ctrl-a>q
10@q

Which does:

qq Start recording a macro into the q register

yyp yank the current line, and paste it below

$?\d<CR> Go to the end of the line, and find the first digit looking backwards

<Ctrl-a> Increment the number

q Stop recording the macro.

This leaves you with:

1stlineblahblahblah/nt/1blah
1stlineblahblahblah/nt/2blah
2ndlineblahblahblah/nt/2blah
2ndlineblahblahblah/nt/3blah

With the cursor on the second line. Simply repeat this macro as many times as desired (For example, repeat it ten times with 10@q). If you want to automate this process for each line, execute it globally across each line:

:g//normal 11@q

Alternatively, with a newer version of vim: Paste the line you want however many times, visual block select the digit you want to increment, and press g <Ctrl-a>. This should increment all the numbers in the visual block as you desire. This is a more manual process, however.

2
  • 1
    Awesome, I didn't know about the g <c-a> way. Thanks for sharing.
    – kba
    Commented Feb 17, 2016 at 1:11
  • 1
    @fruglemonkey The first part of your answer is the same as the one above, but thank you nonetheless. The second part, I don't think works for me (unless I'm doing something wrong), so I must not be using the right version. Commented Feb 17, 2016 at 2:22
5

I find it easiest to use macros for one-shot tasks like that. Just start recording a macro with q + name of register and do the task once. I see one repeatable task here:

  • Select the line
  • yank
  • paste (will put you in the next line)
  • go to end of line
  • go back one word
  • increment by one

Now record the required keystrokes to register a, in normal mode:

qayyp$b<c-a>.

Delete the line again to test whether executing the macro 11 times yields the right result for one line:

11@a

It does! So lets undo again and automate the whole process in a second macro b:

qb11@aj

Now you can execute @b until the end of the file and it should produce the output you want. It sounds complicated but it really isn't once you're used to the typical editing patterns.

8
  • I'm not sure if I'm doing something wrong, but it doesn't seem to be working. I followed what you wrote, but perhaps I misunderstood something or didn't execute something properly. (I'm getting the first number of the line incremented, not the last number of the line.) Thank you for replying. Commented Feb 17, 2016 at 2:36
  • 3
    I think the first macro should be qaVyp$b<c-a>. Or better, just qayyp$b<c-a>. Commented Feb 17, 2016 at 7:08
  • 1
    Thanks, @KarlYngveLervåg, you're right, I fixed the macro.
    – kba
    Commented Feb 17, 2016 at 8:10
  • 2
    Argh :( thanks for being thorough @ablewasiereisawelba. I guess the macro qqyyp$?\d<CR><Ctrl-a>q by @fruglemonkey and @jjaderberg is better since it searches the last number. I'll change it for completeness.
    – kba
    Commented Feb 17, 2016 at 9:00
  • 2
    @kba I just thought that if anyone takes the time and effort to answer my question, I could at least try his solution - even if I had already found a working solution from someone else. I appreciate your effort - and if it makes you feel any better, you're still miles ahead of me. :) Commented Feb 17, 2016 at 10:08
2

Save the following macro and use it for each line:

qqyy11p/\/\zs1<CR><C-v>10jg<C-a>q

The main difference over the other answers is that this makes use of g<C-a> on a column selection that increments each line with a different number. Check :help v_g_CTRL-A for more information. I've also used \zs to select the pattern start position, such that the cursor stops at the number after the search.

Please note that you need a recent version of Vim for this to work.

6
  • 1
    @fruglemonkey proposed that as well. It's a cool feature, though it's fairly recent (by vim standards), since 7.4.754 which hasn't shipped to all distros yet.
    – kba
    Commented Feb 17, 2016 at 21:22
  • Missed it sorry
    – Vitor
    Commented Feb 17, 2016 at 21:48
  • 1
    @Vitor I just have gVim 7.4 so I don't think I can do the g<C-a> that @fruglemonkey and you referred to, but thanks for responding. I should note that when trying your macro - just before I get to the g<C-a> part, it isn't the last "1" character that I'm looking to increment that gets block-highlighted, but instead it's the character before the first "1" character that gets block-highlighted. (Also, I think it would have be yy11p and 10j in the macro, since I'm looking to add 11 lines - for a total of 12.) Commented Feb 18, 2016 at 13:36
  • 1
    Thanks for your feedback. I've updated the answer accordingly. Decided to use \zs, but I could just as well simply have added a l after <CR>.
    – Vitor
    Commented Feb 19, 2016 at 11:41
  • 1
    You probably have some settings in your vimrc that affect the regular expression search. Try it again starting Vim with vim -u NONE. I've just done that and this works perfectly! In fact, there's no way the search would match the first 1 because it requires a preceding slash.
    – Vitor
    Commented Feb 20, 2016 at 23:44

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.