71

I want to pipe the selected text to a shell command and receive the one-line output from this shell command on the vim info/command line?

What I'm really trying to do: Pipe the selected text to a pastebin-type shell command and I want to receive the output of the shell cmd (which is the http link to the pastebin). Is this possible?

ikwyl6
  • 851
  • 1
  • 7
  • 12
  • 1
    Related questions for providing selected text as `STDIN` to shell commands: [Replacing the selected original text with the output](http://stackoverflow.com/questions/6932382/replace-vim-selection-with-output-of-shell-command) and [Piping to and from the shell, working with entire buffers](http://stackoverflow.com/questions/7867356/piping-buffer-to-external-command-in-vim) – user1129682 Feb 17 '14 at 18:01

7 Answers7

138

For multi line version you can do this after selecting the text:

:'<,'>:w !command<CR>

See the official Vim docs at :help :w_c.


You can map it to simple Visual mode shortcut like this:

xnoremap <leader>c <esc>:'<,'>:w !command<CR>

Hit <leader key>+c in visual mode to send the selected text to a stdin of the command. stdout of the command will be printed below vim's statusbar.

Real world example with CoffeeScript:

https://github.com/epeli/vimconfig/commit/4047839c4e1c294ec7e15682f68563a0dbf0ee6d

toraritte
  • 6,300
  • 3
  • 46
  • 67
esamatti
  • 18,293
  • 11
  • 75
  • 82
  • 33
    FYI: omitting `:w` will replace the selection with the command's output. – Clint Pachl Jul 16 '13 at 01:06
  • 8
    I do not understand the syntax and the mode of action of this `:w`. Is it the normal "write" command? What is the second `:` for? Can you explain it? Thanks! – schoettl Aug 26 '15 at 22:29
  • 1
    A variant of the mapping I'm using: `xnoremap c "\:'<,'>:w !" . getbufvar('%', 'run_command', &filetype) . "\"` .. This uses the buffer's `fileype` as the command, which works well with many languages (`ruby`, `python`, etc.). You can still override this by setting `b:run_command` which is useful for code embedded in Markdown files and such. – Martin Tournoij Mar 08 '16 at 00:39
  • 3
    @schoettl I was wondering the same thing! Found under `:help :w`, specifically `:help :w_c`. – Mihai Alexandru Bîrsan Nov 10 '18 at 01:55
  • 1
    This will work with line based selections but if only specific part of the line is selected this will not work – caltuntas Dec 21 '22 at 06:58
44

Simply highlight the lines using visual line select shift-v, the hit :! and type the command you wish to send the commands to. The resulting output will then replace your selected text.

When you type your command it will appear at the bottom as:

:'<,'>!somecmd

the '<,'> is indicating that the range you have visually selected will be passed to the command specified after the !

Neg_EV
  • 1,930
  • 1
  • 15
  • 23
  • 12
    This just replaces the selected text with the output from the shell cmd. – ikwyl6 Apr 09 '10 at 23:17
  • Sorry, misread the question. I thought that was what you wanted to do. – Neg_EV Apr 12 '10 at 18:48
  • 6
    This version has it's merrits. After doing this version, you could then copy/yank the commands output. (select using shift-v and hit y, or yy if it's one line, etc) undo to get the original back. (hit u) then paste (hit p) the copied command output. giving you both the original data and the pastebin url in your current buffer, without any extra vim config, and without having to use the mouse to get the commands output back. – fess . May 23 '13 at 00:03
  • 2
    Making somecmd `sh` will execute the whole line as the actual shell command. Eg. `:'<,'>!sh` – Salami Aug 18 '17 at 04:32
19

I would do it like this:

Place this function in your vimrc:

function Test() range
  echo system('echo '.shellescape(join(getline(a:firstline, a:lastline), "\n")).'| pbcopy')
endfunction

This will allow you to call this function by doing:

:'<,'>call Test()

Then you can also map that like this (just under the function declaration in your vimrc):

com -range=% -nargs=0 Test :<line1>,<line2>call Test()

So you can call the function doing this:

:'<,'>Test

Note: :<','> are range selectors, in order to produce them just select the pertinent lines in visual mode and then go to command mode (pressing the colon key)

matias
  • 565
  • 4
  • 12
  • 1
    Great answer and it works. The only thing is it doesn't interpret new lines in vim on the pastebin. So if I have multiple selected lines in vim, it shows up as one big line on paste bin. Is there a way to fix that? – ikwyl6 Apr 09 '10 at 23:19
  • 2
    And for me it produces this: http://pastebin.com/8XuqTr1K («\» at the end of each line). I'm using pastebin-0.6.1 from Gentoo repository and zsh. – ZyX Apr 10 '10 at 12:33
  • I'm using sprunge.us and a script I made myself: while read data; do paste="$paste $data" done curl -sF "sprunge=$paste" http://sprunge.us | xclip && echo `xclip -o`?sh – ikwyl6 Apr 10 '10 at 13:05
  • Maybe you should use `"$paste\n$data"`? – ZyX Apr 12 '10 at 11:50
  • 1
    I fixed my problem with the newline: I changed the '\n' to '\r' in the Test() function above. Now it shows actual new lines on the pastebin. – ikwyl6 Jun 07 '10 at 01:16
  • 2
    Ok, to add another question to this: Do you know how I can get the http link that is shown on the vim system command line by this script to be copied to the system buffer? (ie: "*y) – ikwyl6 Oct 24 '10 at 03:50
  • 1
    I changed this a bit since I was getting trailing '\' characters on each each line to: `echo system('pbcopy', join(getline(a:firstline, a:lastline), "\n"))` – Sam Jacobson Oct 23 '16 at 09:33
7

Maybe you should use something like

:echo system('echo '.shellescape(@").' | YourCommand')

Starting from some vim-7.4 version it is better to use

:echo system('YourCommand', getreg('"', 1, 1))

. This is basically the only way to keep NUL bytes untouched should they be present in the file. Passing @" in one or the other way will transform NUL bytes into NL (newline).

ZyX
  • 52,536
  • 7
  • 114
  • 135
  • This seems good. One minor thing, is after selecting text you have to 'yank' with 'y', before running that command. Just selecting text in visual mode and running that command gives `E481: No range allowed` – davr Apr 06 '10 at 00:36
  • This basic idea can be simplified to `:call system('YourCommand', @")`. A mapping will do the necessary yank for you. All together, it looks like: `map y "9y:call system('YourCommand', @")` The main point is that `system()` takes a 2nd argument, which is written to the program on stdin, so it avoids all escaping issues. – Tim Siegel Apr 23 '16 at 01:26
  • 1
    @TimSmith You are right, but `@"` is not the best idea. See updated answer. – ZyX Apr 23 '16 at 12:59
2

@matias 's solution is not work well for me, because it seems shellescape will append \ to each line.

So I use sed to accomplish this, and it works just fine!

"dump selected lines
function! DumpLines() range
  echo system('sed -n '.a:firstline.','.a:lastline.'p '.expand('%'))
endfunction

com! -range=% -nargs=0 Dump :<line1>,<line2>call DumpLines()
Harry Lee
  • 992
  • 8
  • 7
2

An imperative way to do it is to:

  1. yank your selection
  2. drop into command mode with :
  3. ! + paste the register in the command-line like <Ctrl> r "

So: y : ! <Ctrl> r "

papiro
  • 2,158
  • 1
  • 20
  • 29
1

Another answer:

function Pastebin() range
    let savedreg=@"
    silent execute a:firstline.",".a:lastline."yank"
    python import vim, subprocess
    python p=subprocess.Popen(["pastebin"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    python p.stdin.write(vim.eval('@"'))
    let @"=savedreg
    python p.stdin.close()
    python retstatus=p.poll()
    python print p.stdout.read()
endfunction

Requires python support. Use it just like matias' function.

ZyX
  • 52,536
  • 7
  • 114
  • 135