I've opened an empty buffer with just vim.

I have a shell script in the same directory that I can pipe SQL to. That script will print the result of the execution to stdout.

In my empty buffer, I wrote the following.

select * from foo;

I want to pass the entire contents of the file over to my script, exec-sql.sh, then append the stdout of the shell script back into my buffer.

I tried this first.

% !exec-sql.sh

This replaced the contents of my buffer with the result of the SQL query. Not what I want.

Next, I tried this.

w !exec-sql.sh

This didn't replace the SQL query in by buffer, but it also didn't append the result of the query to my buffer. This also isn't what I want.

I want both the query and the result to end up in my buffer like this.

select * from foo;

Is there a way to do this in vanilla Vim?

Update: I'd also like to just select a range to pass to exec-sql.sh, maybe by selecting some SQL statements in visual mode and piping just those statements.

:'<,'> !exec-sql.sh

I'd like to keep the selected lines, while also appending the result of executing the SQL commands right below the selected lines.

exec-sql.sh doesn't read files. You must pipe data to it. In the regular shell I have to do this: cat my.sql | exec-sql.sh.

I know how to do a regular read from an external command. But I want to do a write first, then a read. Write to the external command's stdin and then read from the external command's stdout.


Two methods you tried:

  • % !exec-sql.sh :h :range! filter {range} lines through the external program. also check :h ! .
  • w !exec-sql.sh :h :w_c execute {cmd} with [range] lines as standard input.
    Beware that w ! and w! have total different meaning.

If you want to read external command output:

 :read !exec-sql.sh

:h :read! execute {cmd} and insert its standard output below the cursor or the specified line.

If you want to pass file name to external command, add % to the end, it will be replaced with current filename.

:read !exec-sql.sh %

Note that external command knows nothing about vim buffer, you need to save it before you execute above command.

If you want to use buffer content as input for external command and add the output to the end of the buffe ( doesn't work well on vim8, see update ):

:$put =execute('%w !exec-sql.sh')

% is default range of :w, Replace it with whatever rang you like. check :h :put

At last you can create a map to send selected lines to external command and add the output to the end of the buffer.

vnoremap <leader>ex :<c-u>$put =execute('''<,''>w !exec-sql.sh')<cr>

also check wiki


:h :put Put the text [from register x] after [line], :h '> marks the end of your selection, to output after selected lines:

vnoremap <leader>ex :<c-u>'>put =execute('''<,''>w !exec-sql.sh')<cr>

There are always 2 leading blank lines in the output, if that bothers you, use :h '[ to move to there, and change it:

vnoremap <leader>ex :<c-u>'>put =execute('''<,''>w !exec-sql.sh')<cr>'[cj--------<esc>


Just tested this on vim8.1, include patches 1-1142, execute() doesn't read output form :w ! style command, redir doesn't work neither. Not sure if it's a bug.

  • Huh. So this doesn't work for me. I see the output flash, but it doesn't make it into my buffer... :/ If it's relevant, exec-sql.sh is actually a Makefile that runs sudo docker exec --interactive, but I don't pass the --tty flag, so that's why this works with plain ol' !make exec-sql. And I'm on Vim 8.1. – 425nesp May 5 '19 at 4:16
  • Yes, it doesn't work on vim8.1 include patches 1-1142. Not sure if it's a bug. – dedowsdi May 5 '19 at 5:04

Based on my answer to a similar question:

nnoremap _X  :put =\"---\n\" . system('exec-sql.sh', getline('.'))<cr>
vnoremap _X  :<C-U>'>put =\"---\n\" . system('exec-sql.sh', getline('''<','''>'))<cr>

The first mapping is to execute a single line. Just put the cursor in the line and hit _X. This takes the current line (getline(".")) and uses it as stdin for the execution of exec-sql.sh. The resulting output is put below the current line with a divider ---.

The visual mode mapping works similar, but getline('''<', '''>') fetches the list of selected lines and feeds it to exec-sql.sh.


One way to cheat is to duplicate the line I want to execute and replace the duplicate with the output.

My file starts off like this.

// notes.txt
select * from foo;
select * from foo;

Then, I highlight the second select statement in visual mode and do this.

:'<,'> !exec-sql.sh

Which leaves me with this.

// notes.txt
select * from foo;

Which is exactly what I want.

  • Consider @Ralf's answer, it works on both vim8 and neovim. – dedowsdi May 5 '19 at 5:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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