3

I know that with this command:

:yyp!!bash

I can execute current line and print the output in the next line (actually it copies the current line to the next line, executes it, and replaces it with the output), to get from this:

echo "Hello"

to this (after the command above):

echo "Hello"
Hello

I also know that I can use this command:

:w !bash

To execute the selected lines (plural) and show the output in the window below the editor.

What I want is to be able to select multiple lines, execute it in bash, and print the output in vim. How can I do that?

So desired behavior is to get from:

echo \
    -e "Hello\nWorld"
echo "Hi"

to:

echo \
    -e "Hello\nWorld"
echo "Hi"
Hello
World
Hi
11
  • 1
    Define “doesnt work”—also see my recent answer on sending lines to a terminal. Finally, consider using a custom operator
    – D. Ben Knoble
    Commented Mar 18, 2019 at 16:38
  • vi.stackexchange.com/a/19210/10604
    – D. Ben Knoble
    Commented Mar 18, 2019 at 16:38
  • Uh, the "doesn't work" part is only the bonus part. And in this case doesn't work means it just blink with no change. It doesn't seem to perform the command sequence as if I did that manually.
    – justhalf
    Commented Mar 18, 2019 at 17:00
  • There’s a typo there: yp is not yyp
    – D. Ben Knoble
    Commented Mar 18, 2019 at 17:00
  • Ah, I didn't know that shortcut doesn't work the same way as when we are selecting some lines? That command is supposed to be executed after I select a line (so just y is enough to copy). If I change it to yy and did not select a line, it works. My question still stand then, how to select a few lines, and then use a shortcut to perform the command.
    – justhalf
    Commented Mar 18, 2019 at 17:03

2 Answers 2

3

When you have the lines selected you could just use the following:

yPgv:!bash

If you want to have that as a mapping you can do like that:

:xnoremap x yPgv:!bash<CR>

To be clear, do NOT use x here but insert your desired mapping. x would be a horrible idea, the cut function is pretty useful.

Breakdown:

y will copy the selected text

P paste before

gv reselect last visual selection

:!bash send it to bash

As you see, you had all the hard parts already there, which made it easy for me. Just a little lesson in vim trickery needed.

Explanation

The ! commands means filter all selected lines through an external program. In this case bash. see :h ! for information about that. So :! bash means send the lines to bash, and replace them with the output from bash.

You used :w !bash which is a write command (see :h :w). It does execute bash with the selected lines as the standard input. So vim just writes and does not directly read it back.

There is simple way I know of to get the results line by line. You could however call this command for every line. This would require some vimscript (see here for a starting point).

4
  • Ah, ok. I'm actually still not sure which part replaces the input with the bash output. What's the difference between :w !bash and :!bash? Why does the latter replaces the editor with the output?
    – justhalf
    Commented Mar 20, 2019 at 8:50
  • Also, currently using this command vim seems to wait until bash is complete before putting the output into the editor. Is there a way to pipe the output of the bash command as it go? So we don't have to wait until completion to see some outputs being written back into the editor. Perhaps I should ask a separate question.
    – justhalf
    Commented Mar 20, 2019 at 8:52
  • @justhalf please read the new Explanation part in my answer
    – B.G.
    Commented Mar 20, 2019 at 9:15
  • Thanks for your updated answer! What I'm looking for is unfortunately not about executing line by line. See my new question for details.
    – justhalf
    Commented Mar 20, 2019 at 9:23
3

I would propose the following:

nnoremap _X  :put =system(getline('.'))<cr>
vnoremap _X  :<C-U>'>put =system(join(getline('''<','''>'),\"\n\").\"\n\")<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(".")), executes it (system(...)) and puts the returned result below the current line.

The visual mode mapping works similar, but

  • `getline('''<', '''>') fetches the list of selected lines
  • join(..., "\n")."\n" joins the list into one string
  • finally put puts the result below the last selected line, due to the leading `'>'
4
  • That seems much more complicated than the solution above. Do you know what are the advantages of doing this over using the other? Is it more generalizable somehow?
    – justhalf
    Commented Mar 20, 2019 at 9:45
  • Advantage is that you don't copy anything to a register, so if you yanked something before, it's still there. The first mapping is not complicated. The second looks complicated as the double quotes need escaping, due to Vim handling them as comment sign.
    – Ralf
    Commented Mar 20, 2019 at 9:53
  • 2
    This solution uses vimscript which leads to a more elegant solution. The reason why I choose another approach (vim normal mode commands) is, that I was able to use a lot of your prework (which has somewhat of an educational reason). It is correct that this solution leaves the registers intact. This will be an advantage in most of the cases! This solution is not more complicated than mine, I would say even easier but uses some functions of vim which you don't use everyday
    – B.G.
    Commented Mar 20, 2019 at 12:21
  • 1
    Maybe a stupid question, but what is _X exactly?
    – Shanteva
    Commented Aug 22, 2019 at 20:04

Your Answer

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

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