Execute shell command without filtering from Vim - vim

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.

Related

In vim, how to insert shell command output when executing contents of visual block

With Vim, I often execute lines of a bash script while editing it,
using :'<,'>w !bash after selecting the lines with visual block.
Now I want the output of command to be inserted in the script.
How can I do that?
If you want your code to be replaced with its output, you can simply remove the w from your command. That leaves you with:
:'<,'>!bash`
which acts like a filter for the selected lines. See :help :!.
If you want to insert the output of your code below itself like so:
echo 1
1
you will need to be a little bit creative:
:'<,'>t'>|'[,']!bash
:[range]t[address] copies lines in [range] below line [address]. Here, we copy the visually selected lines below themselves.
| separates Ex commands.
'[,']!bash works like above: it filters the lines in the given range through bash. Here, the range covers the latest changed lines, in this case the lines we just copied. See :help '[.
Possible improvements…
Make the command less noisy with :help :silent:
:'<,'>t'>|sil'[,']!bash
Prepend a comment character:
:'<,'>t'>|sil'[,']!bash|sed 's/^/\# /'
Turn it into a visual mode mapping:
xnoremap <key> :t'>\|sil'[,']!bash\|sed 's/^/\# /'<CR>

Yank the first word from each line in vim

I'm using vim and I'd like to copy the first word for a range of consecutive lines. The only thing that I could think of is :4,32yw (for lines 4-32), but it didn't work. Any help is appreciated in advance, thanks!
You can do it, but it might be a little bit more complicated than using a simple command as you did.
What you tried to run, is normal mode commands as execute commands. That won't work as you did it. Commands in the normal mode (like pressing yw) won't work es ex-commands.
To run the normal mode commands in the ex-command mode, you should use the normal command (for more information, you can see :help normal).
However, even if you would change you command to use the normal mode command (something like :4,32 normal! yw) it would still not work, since this would run for every line, each time running over the previous yanked value.
You can do the desired action, using a vim register that would append the word in each loop (for more info about registers you can read :help registers).
To do it, you should change the command to be something like :3,32 normal! ^"Ayw.
To break down this command:
: - enter ex-command mode.
3,32 - The range to run the command over.
normal! - The actual command to run. Run it without any custom mapping, to avoid mappings that run over the wanted action.
^ - Go to the start of the line.
"A - Run the yank into the a register, appending the data to the previously saved data in it.
yw - Yank the current word.
Later, to print the copied values, you should use "ap (in the normal mode) at the desired location.
Drawbacks
Note that the command inserts the new word into the registers, ignoring the first value of the register. It means that if the register wasn't empty once you run this command, all the words would be appended to the current value of the register.
To clear the previous value of the register, you can run the command:
:let #a=''
before running you yank command.
Automation
In case you want to do it many times, you might want to use a simple function that would do it all for you, instead make you run both commands every time.
A simple function that would do this:
function! CopyFirst(register) range
execute "let #" . a:register. "=''"
execute a:firstline . "," . a:lastline . "normal! ^\"" . toupper(a:register) . "yw"
endfunction
Usage:
:3, 32 call Copyfirst('a')
"ap
This function uses a function range (more info of this feature in :help func-range):
" Get the first word of the line for a given range of lines
function! GetFirstWord() range
let #a=""
execute a:firstline.",".a:lastline."g/.*/y A"
echo split(substitute(#a," *\\(\\w\\+\\).\\{-}\\n","\\1 ","g"))
endfunction
The first line cleans register a. Second line yanks the lines inside the range into register a. The last line prints a List with the first words of each line. Sample input file:
some sample
text abc
123 456
function
yy xx
Sample calling and output:
:2,4call GetFirstWord()
['text', '123', 'function']

How do I re-select a range in vim? [duplicate]

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.

Reusing the previous range in ex commands in VIM

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.

Custom vi command: How to insert current date with a variable's content?

I'm new to the vi editor and I would like to create a simple custom command in .vimrc that inserts something like 2012-03-13 22:21:17.0 +0100 / Daniel.
Actually, my command (in .vimrc) is as follows:
command! InsertTime :normal a<C-R>=strftime('%F %H:%M:%S.0 %z')<CR>
I also set a variable:
let myname="Daniel"
InsertTime inserts the date perfectly. But how can I concatenate it with the content of my variable?
To concatenate, vim scripts use . caracter. So try this one :
In vimrc:
let myname="Daniel"
command! InsertTime :normal a<C-R>=strftime('%F %H:%M:%S.0 %z') . "/" . myname<CR>
no tested there.
Since you said you're new to "vim" I am going to assume you don't know any of the things I'm about tell you. Mucho sorry if you already know them.
If you're going to do this a lot (insert the line "%F %H:%M:%S.0 %z / Daniel"), instead of defining a command, which you have to invoke with a :command_name, define a macro and/or an input macro that can be invoked with just two or three character.
To define an input macro, do the following at the ':' prompt, or add it to your $HOME/.exrc or $HOME/.vimrc file (without the preceding ':'):
:map <C-X><C-X> Go<ESC>!!date '+\%F \%H:\%M:\%S.0 \%z'<CR>A / Daniel<ESC>
Now when you're in "vi" (but not in input mode), typing control-Xcontrol-X will:
G go to last line in file; replace this with the "motion" keys sequence appropriate for your use (or nothing at all if you want to append the line right after the cursor)
o open a new line
<ESC> escape out of input mode
!!date ... invoke the date command, replace the current line with its stdout (output)
A append at the end of the line (now having the "date")
/ Dan... verbatim intput text
<ESC> escape out of input mode
control-Xcontrol-X can be some unusual sequence that you'd normally not use for anything, nor used by any "vi" operation that you might use. I use as the first character, because in "vi", decrements the next integer on the line after the cursor, if any. That is something I hardly ever do. I define my macros to be invoked with <C-X><C-B>, <C-X><C-D>, <C-X>s1, etc.
To create an input macro, well, that's another whole long subject, and I'm tired of typing today, so, another day. :)

Resources