33

Something I've found useful in other editors is the ability to:

  • take the selected text
  • run an external command and pass the selection to its stdin
  • take the external commands stdout and replace the current selection with it.

This way you can write useful text tools which operate on the selection using any language that can do basic io.

How can this be done with vim using the selection? ... a single character, word, paragraph... etc.

(Directly in the command line, or via a key binding?)


Note

Commands such as !sort work on the line-level, the reason I'm asking this question is because I would like to operate on the selection. (i.e., the text that would be removed if x was pressed).

0

5 Answers 5

26

I find that often the easiest way is to use visual mode with v (or capital V to select whole lines), and select the text you want to pipe.

Then type:

:!cat

This doesn't do anything useful, as such. A useful command I use often is:

:!python -m json.tool

to format JSON.

You can also just type (without visual mode):

:<range>!command

For example, for the entire file:

:%!python -m json.tool

Or for the current line:

:.!python -m json.tool
8
  • Another really useful tool to use with this is the sort command.
    – Cody Poll
    Feb 4, 2015 at 16:24
  • 4
    @CodyPoll What's wrong with Vim's :sort? Feb 4, 2015 at 16:25
  • 5
    @Carpetsmoker The external sort command has a lot more options and (depending on locale, etc.) produces a different order.
    – derobert
    Feb 4, 2015 at 16:34
  • 3
    This doesn't work for selecting a single word in a line. (see question mentions word level selection)
    – ideasman42
    Feb 5, 2015 at 4:40
  • @ideasman42 Right ... I think I originally read this question as "lines or words" instead of "lines and words" ... In any case, doing this with words is non-obvious, UNIX programs work on lines, and Vim is no exception. I did some experimenting and sort of have a solution, but it doesn't work very well ... Maybe my approach is wrong, so I'll just leave it be for a few days and get back to it later. Feb 13, 2015 at 0:25
8

I wrote a plugin called express.vim that can help with this. The plugin defines an operator g= that lets you evaluate a VimScript expression over a motion (or Visual selection). It prompts for an expression, in which you can use v:val as a placeholder for the text covered by the motion (or, again, Visual selection).

In this case, a useful expression would use the system() function, which runs an external command.

system('tr a-z A-Z', v:val)

(Here I used tr as a simple example external command.)

The way to use express.vim for this problem would be to select the text with Visual mode, then type g= followed by that expression, then hit Enter:

g=system('tr a-z A-Z', v:val)<CR>

I'll admit, it's not easy to use. (In fact, I rarely use this plugin myself!) I'm considering adding a shortcut to invoke an external command, such as starting the expression with !. I'll post an update here if I end up doing that.

EDIT

I've modified express.vim to treat expressions beginning with ! as external commands. So, sticking with the tr example, the above becomes a lot simpler. After a Visual selection, type the following:

g=!tr a-z A-Z<CR>

Explanation:

  1. Invoke the express operator with g=
  2. Enter !tr a-z A-Z as the expression
  3. Press Enter
0
5

You are looking for the filter commands in Vim. See :help filter. Here is the relevant documentation:

!{motion}{filter}   Filter {motion} text lines through the external
                    program {filter}.
!!{filter}          Filter [count] lines through the external program
                    {filter}.
{Visual}!{filter}   Filter the highlighted lines through the external
                    program {filter} (for {Visual} see |Visual-mode|).
                    {not in Vi}

:{range}![!]{filter} [!][arg]               *:range!*
                    Filter {range} lines through the external program
                   {filter}.  ...[See documentation for details]

So typing 5!!sort will sort the next 5 lines starting from the cursor.

3
  • Is it possible to filter a single word? (not line level)
    – ideasman42
    May 13, 2016 at 8:25
  • Yes. The third option (visual mode) works for whatever is highlighted (words, partial line, multiple lines, rectangular block, etc.).
    – Sameer
    Jun 27, 2016 at 18:19
  • 5
    The third option also filters entire lines. If you select a single word and filter it, for example, through tr [:lower:] [:upper], than vim will upcase not just this word but the entire line.
    – maria s
    Jan 26, 2017 at 13:30
1

Note that it can be done with:

  1. visually select whatever you wish
  2. s^R=system('tr a-z A-Z', @")[:-2]^M (with ^R being CTRL+R, and ^M being ENTER)

It could also work with anything that does selection + replace like ci"^R=....

4
  • When running this on a multi line text selection (after pressing v and moving the cursor) I get the error E16: Invalid range\nE16: Invalid range\nE476: Invalid command (on Linux so tr and other basic commands exist).
    – ideasman42
    Jan 26, 2017 at 19:04
  • Have you pressed s that goes into insert mode or :s the command? Jan 26, 2017 at 21:26
  • Suspect not, but also not sure how you mean, perhaps the example could show how to bind this to a key?
    – ideasman42
    Jan 26, 2017 at 21:46
  • Do you hit directly s from the visual mode, or do you hit : first? There is no : involved in the sequence of keys I've written. A mapping would be: vnoremap µ s<c-r>=system('tr a-z A-Z', @")[:-2]<cr>. Jan 27, 2017 at 9:42
0

There is a vim plugin called vis.vim which allows this functionality. I found it on this stack overflow post: https://stackoverflow.com/questions/40072761/vim-send-visual-block-to-external-command#answer-40083050

1
  • 3
    Welcome to Vi and Vim! Good answers include more than just a link and a vague description; they should stand on their own. You can edit to improve your answer, if you wish :)
    – D. Ben Knoble
    Jul 12, 2020 at 19:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.