Related
Is it possible to save a recorded vim macro and apply that macro via a script non-interactively?
For example, suppose I had 100 similar files that looked like this:
variable number of lines
foo(a)
some more stuff
variable number of lines
bar(e)
In vim, I could record and playback a macro to change those files to something like this:
variable number of lines
foo(a, b)
some more stuff
variable number of lines
bar(e)
The macro would be ugly to read, and could be something like this:
ESCqa/foo
/)
i, bESCj0
V/bar
k>ESCq
Is there a way to save that macro and apply it non-interactively, e.g. so it could easily be applied to 100 files without interactively opening each file in vim?
This is what I've already tried:
Saving my macro as a, then writing apply_a.ex script:
norm #a
w
And finally ex target.txt < apply_a.ex
This doesn't change the file contents -- it appears the macro a is not recognized.
From :help starting
-w {scriptout} All the characters that you type are recorded in the file
"scriptout", until you exit Vim. This is useful if you want
to create a script file to be used with "vim -s" or
":source!". When the "scriptout" file already exists, new
characters are appended. See also |complex-repeat|.
{scriptout} cannot start with a digit.
{not in Vi}
-s {scriptin} The script file "scriptin" is read. The characters in the
file are interpreted as if you had typed them. The same can
be done with the command ":source! {scriptin}". If the end
of the file is reached before the editor exits, further
characters are read from the keyboard. Only works when not
started in Ex mode, see |-s-ex|. See also |complex-repeat|.
{not in Vi}
Is this what you need?
'Sorry for not formatting properly, I am on android'
Have you ever used 'bufdo' or heard of it?
You could open all your files at once and record in your macro something like :bn which goes to next. Bufer and at the end :wall which means write all and finally :qall
Here one example:
Or you can do something like:
vim *
let #a='define your macro including :wn'
normal n #a
:qall
Where * represents all files you need
:wn means 'write current buffer and go next
n #a is n times macro a
:qall means quite all buffers
Is it possible to reuse the range of ex commands in VIM?
As an example, I can write (copy) lines 4 to 10 from my current file to a new file using the following command:
:4,10w foo/bar.txt
But what I really want to do is move the lines to a new file. I can do this like so:
:4,10w foo/bar.txt
:4,10d
But it's a bit annoying to have to type 4,10 both times.
So I have two questions:
Generally, Is there a way to reference the previously used range in ex commands?
Specifically, if there is not a way to do (1), is there any easier way to cut and paste a number of lines from one file into a new one.
I usually use cat to do this:
:4,10!cat > foo/bar.txt
This works because you're piping the lines through cat and replacing them with the resulting output, which is nothing. And of course you can append to the end of an existing file by doing >> instead of >.
I am unaware of an answer to (1), but to answer (2), there are a number of different ways of doing it that don't require reselecting the lines by hand. In visual mode this will work:
4GV10G
:w foo/bar.txt
gvd
because gv reselects the previous selection, which is almost what you want, without using an ex range.
But you could just turn the problem on its head, and try:
:4,10d
:sp foo/bar.txt
pZZ
to cut, then paste into a new file, then close it.
Other than using the Vim history (:Cursor Up, q:) and removing the previous command so that just the range is kept, there's no way to re-use the last range, no magic variable.
If I used this move lines combination more often, I would write a custom command for it:
command! -bang -range -nargs=1 -complete=file MoveWrite <line1>,<line2>write<bang> <args> | <line1>,<line2>delete _
You need to specify the range only once and save typing.
You can write something like this for other combinations, too. The main challenge is specifying all the command attributes (bang, range, completion), and, later, remembering the custom command name.
Generally, what I do is delete the lines from the one file, switch to the other file, and paste.
Also, I generally use marks. Instead of typing the actual numbers, I hit mb to mark the beginning line, then go to the end line and hit d'b to delete back to the line marked as b. But you can use mb to mark a begin line, and me to mark an end line, then run an ex command:
:'b,'e w somefile.txt<Enter>
Of course you can use any letters from a through z for your marks; I usually use b and e but you can use what you like.
How I would move the lines:
m'b
<navigate to end line>
d'b
:n somefile.txt<Enter>
p
Ctrl+^
Ctrl+^ switches from the current open file to the previous open file. (You could also just open a pane and switch panes, if you prefer. Panes don't work in plain vi but do work in vim.)
The above assumes that you have set the autowrite option on. With autowrite, the :n command and Ctrl+^ both just write the current file and then switch files, instead of complaining that the file has been changed without you saving it. You can also do the above and just explicitly write the file before using :n or Ctrl+^.
By the way, I use Ctrl+^ so much that I mapped it onto K. Easier to type, but I got in that habit long ago when I used to have to sometimes use a dumb terminal that couldn't type Ctrl+^.
By the way, when you delete lines, they go into the "unnamed buffer". In vim, the unnamed buffer is preserved when you switch files. In original vi, the unnamed buffer is cleared. So the above won't work with old vi. You can make it work by deleting into a named buffer, then pasting from the named buffer; that works in any version of vi.
m'b
<navigate to end line>
"ad'b
:n somefile.txt<Enter>
"ap
Ctrl+^
The above deletes into the buffer named a, then pastes from a in the other file. This does work in vim of course; it's just that you don't need it.
Here's a command-line mapping that achieves this. I've bound it to CTRL-G CTRL-U, since it performs a similar action as CTRL-U. (But you can change that, of course!)
" c_CTRL-G_CTRL-U Remove all characters between the cursor position and
" the closest previous |:range| given to a command. When
" directly after a range, remove it.
" Useful to repeat a recalled command line with the same
" range, but a different command.
let s:singleRangeExpr = '\%(\d\+\|[.$%]\|''\S\|\\[/?&]\|/[^/]*/\|?[^?]*?\)\%([+-]\d*\)\?'
let s:rangeExpr = s:singleRangeExpr.'\%([,;]'.s:singleRangeExpr.'\)\?'
let s:upToRangeExpr = '^\%(.*\\\#<!|\)\?\s*' . s:rangeExpr . '\ze\s*\h'
" Note: I didn't take over the handling of command prefixes (:verbose, :silent,
" etc.) to avoid making this overly complex.
function! s:RemoveAllButRange()
let l:cmdlineBeforeCursor = strpart(getcmdline(), 0, getcmdpos() - 1)
let l:cmdlineAfterCursor = strpart(getcmdline(), getcmdpos() - 1)
let l:upToRange = matchstr(l:cmdlineBeforeCursor, s:upToRangeExpr)
if empty(l:upToRange)
return getcmdline()
else
call setcmdpos(strlen(l:upToRange) + 1)
return l:upToRange . l:cmdlineAfterCursor
endif
endfunction
cnoremap <C-g><C-u> <C-\>e(<SID>RemoveAllButRange())<CR>
as a plugin
My CmdlineSpecialEdits plugin has (among many others) this mapping as well.
You can also do something like this to write the contents of the anonymous register to file2.txt
:4,10d | :call writefile(split(##, "\n", 1), 'file2.txt')
You can do the deleting first, and then open a new tab and paste the contents - so :4,10d, then :tabe foo/bar.txt, followed by p... does that sound better?
In Vim 8 and NVIM 0.3.7 as of writing, you can actually edit your command list and hit enter to execute.
:4,10w foo/bar.txt
q:
q: is to enter interactive ex command
Once you open the interactive command list, you can then edit it and press enter to execute.
I love moopet's answer though, it's efficient.
Is it possible to reuse the range of ex commands in VIM?
As an example, I can write (copy) lines 4 to 10 from my current file to a new file using the following command:
:4,10w foo/bar.txt
But what I really want to do is move the lines to a new file. I can do this like so:
:4,10w foo/bar.txt
:4,10d
But it's a bit annoying to have to type 4,10 both times.
So I have two questions:
Generally, Is there a way to reference the previously used range in ex commands?
Specifically, if there is not a way to do (1), is there any easier way to cut and paste a number of lines from one file into a new one.
I usually use cat to do this:
:4,10!cat > foo/bar.txt
This works because you're piping the lines through cat and replacing them with the resulting output, which is nothing. And of course you can append to the end of an existing file by doing >> instead of >.
I am unaware of an answer to (1), but to answer (2), there are a number of different ways of doing it that don't require reselecting the lines by hand. In visual mode this will work:
4GV10G
:w foo/bar.txt
gvd
because gv reselects the previous selection, which is almost what you want, without using an ex range.
But you could just turn the problem on its head, and try:
:4,10d
:sp foo/bar.txt
pZZ
to cut, then paste into a new file, then close it.
Other than using the Vim history (:Cursor Up, q:) and removing the previous command so that just the range is kept, there's no way to re-use the last range, no magic variable.
If I used this move lines combination more often, I would write a custom command for it:
command! -bang -range -nargs=1 -complete=file MoveWrite <line1>,<line2>write<bang> <args> | <line1>,<line2>delete _
You need to specify the range only once and save typing.
You can write something like this for other combinations, too. The main challenge is specifying all the command attributes (bang, range, completion), and, later, remembering the custom command name.
Generally, what I do is delete the lines from the one file, switch to the other file, and paste.
Also, I generally use marks. Instead of typing the actual numbers, I hit mb to mark the beginning line, then go to the end line and hit d'b to delete back to the line marked as b. But you can use mb to mark a begin line, and me to mark an end line, then run an ex command:
:'b,'e w somefile.txt<Enter>
Of course you can use any letters from a through z for your marks; I usually use b and e but you can use what you like.
How I would move the lines:
m'b
<navigate to end line>
d'b
:n somefile.txt<Enter>
p
Ctrl+^
Ctrl+^ switches from the current open file to the previous open file. (You could also just open a pane and switch panes, if you prefer. Panes don't work in plain vi but do work in vim.)
The above assumes that you have set the autowrite option on. With autowrite, the :n command and Ctrl+^ both just write the current file and then switch files, instead of complaining that the file has been changed without you saving it. You can also do the above and just explicitly write the file before using :n or Ctrl+^.
By the way, I use Ctrl+^ so much that I mapped it onto K. Easier to type, but I got in that habit long ago when I used to have to sometimes use a dumb terminal that couldn't type Ctrl+^.
By the way, when you delete lines, they go into the "unnamed buffer". In vim, the unnamed buffer is preserved when you switch files. In original vi, the unnamed buffer is cleared. So the above won't work with old vi. You can make it work by deleting into a named buffer, then pasting from the named buffer; that works in any version of vi.
m'b
<navigate to end line>
"ad'b
:n somefile.txt<Enter>
"ap
Ctrl+^
The above deletes into the buffer named a, then pastes from a in the other file. This does work in vim of course; it's just that you don't need it.
Here's a command-line mapping that achieves this. I've bound it to CTRL-G CTRL-U, since it performs a similar action as CTRL-U. (But you can change that, of course!)
" c_CTRL-G_CTRL-U Remove all characters between the cursor position and
" the closest previous |:range| given to a command. When
" directly after a range, remove it.
" Useful to repeat a recalled command line with the same
" range, but a different command.
let s:singleRangeExpr = '\%(\d\+\|[.$%]\|''\S\|\\[/?&]\|/[^/]*/\|?[^?]*?\)\%([+-]\d*\)\?'
let s:rangeExpr = s:singleRangeExpr.'\%([,;]'.s:singleRangeExpr.'\)\?'
let s:upToRangeExpr = '^\%(.*\\\#<!|\)\?\s*' . s:rangeExpr . '\ze\s*\h'
" Note: I didn't take over the handling of command prefixes (:verbose, :silent,
" etc.) to avoid making this overly complex.
function! s:RemoveAllButRange()
let l:cmdlineBeforeCursor = strpart(getcmdline(), 0, getcmdpos() - 1)
let l:cmdlineAfterCursor = strpart(getcmdline(), getcmdpos() - 1)
let l:upToRange = matchstr(l:cmdlineBeforeCursor, s:upToRangeExpr)
if empty(l:upToRange)
return getcmdline()
else
call setcmdpos(strlen(l:upToRange) + 1)
return l:upToRange . l:cmdlineAfterCursor
endif
endfunction
cnoremap <C-g><C-u> <C-\>e(<SID>RemoveAllButRange())<CR>
as a plugin
My CmdlineSpecialEdits plugin has (among many others) this mapping as well.
You can also do something like this to write the contents of the anonymous register to file2.txt
:4,10d | :call writefile(split(##, "\n", 1), 'file2.txt')
You can do the deleting first, and then open a new tab and paste the contents - so :4,10d, then :tabe foo/bar.txt, followed by p... does that sound better?
In Vim 8 and NVIM 0.3.7 as of writing, you can actually edit your command list and hit enter to execute.
:4,10w foo/bar.txt
q:
q: is to enter interactive ex command
Once you open the interactive command list, you can then edit it and press enter to execute.
I love moopet's answer though, it's efficient.
I have a file where I store snippets of vim commands. When I need a snippet, I yank it and then execute it with #". The snippets are stored as a script, one line per command, like this:
:s/foo/bar/g
:echo "hello"
:s/1/2/g
Edit: I removed normal mode commands from the example, as they were not part of the problem.
Now this procedure doesn't work anymore: when executing the snippet, it just stops at the first line as if waiting for a newline.
Is there an option somewhere affecting how # is executed? I'm pretty sure it was working some time ago...
Substituting the newline with a ^M character works but makes the file more difficult to handle.
Additional information:
Here's another symptom: when I yank a snippet, if I execute it with #" it stops at the first line as I just explained. But if I execute it with :# it works. But the help file doesn't seem to imply any difference in how the two commands treat the register's content...
I don't think the problem is ^M vs. ^J. Vim macros will treat either one as a valid end-of-line character for recorded macros. I think the problem is extra newlines.
In your example, there's at least one spurious newline after 2j, and unless you're particularly careful when copying the snippet, there's probably another one after 10k as well. These extra newlines are like pressing <Enter> in Normal mode -- they move the cursor down one line.
Here's what I think you want the snippet to look like:
:s/foo/bar/g
2j:s/1/2/g
10k
(Even that's a little misleading -- you'd still have to be careful not to copy the newline after the 10k.)
Why do these extra newlines make such a big difference? Well, for one thing, they cause you to be at least one line away from where you expect to be, which throws off anything you want to do on a particular line (like execute the :s// command).
More importantly, however -- and this is what I think is happening in your example -- is that Vim stops macro playback if the macro attempts to use <Enter> on the last line of a buffer. (I'm guessing Vim considers it an error, and any error causes a macro to stop running.)
Here's an example. Suppose you've got this snippet stored in register x:
4j
:echo "Done"
(Notice the newline after 4j.)
Furthermore, suppose you have the following five lines (and only these five lines) in a buffer:
line 1
line 2
line 3
line 4
line 5
If you now press #x on line 1, the :echo "Done" never executes. Vim moves the cursor down 4 lines to line 5, then attempts to move down one more line because of the extra newline, but it can't. The macro stops executing at that point, before the :echo command gets a chance to run.
However, it works if you change the x register to this:
4j:echo "Done"
So to return to your original example, I'll bet what's happening is that the extra newline after 2j is attempting to move your cursor somewhere it can't go, and that causes the macro to stop. The bottom line of the screen contains the last command executed (:s/foo/bar/g), which makes it look like Vim is waiting for you to press Return.
Finally, I'd strongly recommend using another method to store and execute Vim command sequences. The technique you're using is tolerable for simple cases, but it's fragile and doesn't scale well. Vim has a full scripting language that includes functions and custom commands, and it can be used to do all the things you're doing now, but in a much more robust fashion. Vim scripting is a big topic, but I'd start here:
:help script
Be sure to read about the :normal command, which lets you execute Normal-mode commands (like 2j and 10k) within scripts.
Good luck!
I finally found the culprit. Somehow I had a command mapping on <C-J> in my .vimrc file. When read with the default cpoptions, this turned into a mapping on <NL>.
How I found out: I noticed that when starting vim with -u ~/.vimrc, it would indeed execute yanked snippets. I generated a session file with and without that commandline option and compared them. This way I found out that a different set of cpoptions where used to read the same .vimrc file, so that in one case the mapping was indeed on <C-J>, in the other it was converted into a mapping on <NL>!
If someone has a similar problem, I suggest to look carefully at the currently set command mappings, with :cmap.
I want to select a block of text (for example, V%) and use the text as input to a shell command (for example, wc or pbcopy) - but I don't want to alter the current buffer - I just want to see the output of the command (if any) then continue editing without any changes.
Typing V%!wc translates to :'<,'>!wc and switches the block of text for the output of the wc command.
How do you pipe a chunk of text to an arbitrary shell command without affecting the current buffer?
Select your block of text, then type these keys :w !sh
The whole thing should look like:
:'<,'>w !sh
That's it. Only took me 8 years to learn that one : )
note: typing : after selecting text produces :'<,'> a range indicating selection start and end.
Update 2016: This is really just one use of the generic:
'<,'>w !cli_command
Which basically lets you "send" arbitrary parts of your file to external commands and see the results in a temporary vi window without altering your buffer. Other useful examples would be:
'<,'>w !wc
'<,'>w !to_file my_file
I honestly find it more useful to alter the current buffer. This variety is simply:
'<,'>!wc
'<,'>!to_file my_file
One possibility would be to use system() in a custom command, something like this:
command! -range -nargs=1 SendToCommand <line1>,<line2>call SendToCommand(<q-args>)
function! SendToCommand(UserCommand) range
" Get a list of lines containing the selected range
let SelectedLines = getline(a:firstline,a:lastline)
" Convert to a single string suitable for passing to the command
let ScriptInput = join(SelectedLines, "\n") . "\n"
" Run the command
let result = system(a:UserCommand, ScriptInput)
" Echo the result (could just do "echo system(....)")
echo result
endfunction
Call this with (e.g.):
:'<,'>SendToCommand wc -w
Note that if you press V%:, the :'<,'> will be entered for you.
:help command
:help command-range
:help command-nargs
:help q-args
:help function
:help system()
:help function-range
Update: my answer is nonsense.
#pixelearth's answer is good, but I had a little trouble understanding what he did exactly, so I wrote the following. This sequence of commands let's you execute wc -l on your visual selection. wc -l simply counts the number of lines passed to it.
In Vim go into Visual Mode using v
Select a few lines by going down: jjjj
Type : which Vim will translate to :'<,'>
Type w !wc -l, your complete commandline should now be :'<,'>w !wc -l
Press Enter to get the result of your command (in this example it would be 4)
Press Enter to continue editing
I don't understand what exactly happens at step 3 and 4 but I do know that it works.
I know it's not the ideal solution, but if all else fails, you could always just press u after running the command to undo the buffer change.