25

I'd like to use Vim in the middle of a pipe. This existing post looks like what I'd like to do, except I was hoping to do it without Python's help -- only with bash. [It it helps, the environment is the bash shell in the Terminal IDE app on Android.]

Please, I know how to pipe a buffer through a command from inside Vim. That's great, but not what I want here. I want to exit Vim and pass the active buffer to stdout.

FWIW, I also know how to pass another command into Vim as input. Again, that's not what I'm trying to get here.

Community
  • 1
  • 1
Mike
  • 811
  • 1
  • 10
  • 15
  • possible duplicate of [vim - write buffer content to stdout](http://stackoverflow.com/questions/3219479/vim-write-buffer-content-to-stdout) – Ciro Santilli OurBigBook.com Sep 06 '14 at 16:15
  • the possible duplicate is what you probably actually want to do. You want to start vim first, then read something into your buffer, then do something, then output it to do something else. no need for adding another fancy shell tool (vipe). – erikbstack Dec 29 '14 at 10:59

5 Answers5

38

Take a look at vipe which is part of moreutils. It allows you to use any editor as part of a pipe.

 ls -al | vipe | less

To use it with vim just make sure to set it as your default editor in your bashrc or cshrc or whatever shell you use.

 EDITOR=vim

UPDATE: If you want a bash only solution you could use a script like this

 #!/bin/bash
 # create temporary file
 TMPFILE=`mktemp /tmp/vipe.bashXXXXXXXX`
 cat > ${TMPFILE}
 vim ${TMPFILE} < /dev/tty > /dev/tty
 cat ${TMPFILE}
 rm ${TMPFILE}

For a more portable version please replace

 vim ${TMPFILE}

with

 ${EDITOR} ${TMPFILE}
Sebastián Grignoli
  • 32,444
  • 17
  • 71
  • 86
dwalter
  • 7,258
  • 1
  • 32
  • 34
  • Your bash-only solution doesn't work for me when piping into `sed -e 's/^/XXX:/'`; I get a messed-up Vim editor when jumping around. – Ingo Karkat May 21 '12 at 16:11
  • Awesome. I don't have it fully working yet, but I think it's just a path thing with the Terminal IDE environment. (I copied in the bash script you provided.) – Mike May 21 '12 at 16:16
  • 1
    I made it work, replacing this line: ` vim ${TMPFILE}` with ` vim ${TMPFILE} < /dev/tty > /dev/tty` – Sebastián Grignoli Oct 17 '15 at 13:58
  • this is awesome, thank you! @SebastiánGrignoli, why does that work and what does it do? – CervEd May 03 '21 at 09:08
  • @SebastiánGrignoli this is really good. It works fine for me except when there is no stdin. Is there a way to modify this script to work when there is no stdin? As in "no command before vipe". The obvious work around being to add `echo '' |` before vice but there must a way to have the script handle this case. – Mig Mar 29 '22 at 09:27
6

To print buffer to shell standard output, vim needs to start in Ex mode, otherwise it'll open "normal" way with its own window and clear any output buffers on quit.

Here is the simplest working example:

$ echo foo | vim +%p -escq /dev/stdin
foo

which is equivalent to:

$ echo foo | vim -es '+%print' '+:q!' /dev/stdin
foo

Special file descriptor to standard input needs to be specified (/dev/stdin) in order to prevent extra annoying messages.

And here are some examples with parsing strings:

$ echo This is example. | vim '+s/example/test/g' '+%p' -escq! /dev/stdin
This is test.
$ echo This is example. | vim - '+s/example/test/g' '+%p' -escq!
Vim: Reading from stdin...
This is test.

Related:

kenorb
  • 155,785
  • 88
  • 678
  • 743
  • Unfortunately this doesn’t seem to work with nvim, nor up to date vim 8 (on macOS). – Konrad Rudolph Jun 20 '19 at 11:06
  • @KonradRudolph It won't as I think Neo Vim deliberately [removed the Ex mode](https://github.com/neovim/neovim/issues/1089) from its code base (saying it's not popular and useless feature), but I could be wrong. Otherwise if it's still there, it could work in a different way. – kenorb Jun 20 '19 at 12:39
  • I have no idea regarding the status of this issue but Ex mode definitely exists in NeoVim. Anyway, I’ve done further testing and this solution also doesn’t work in Vim 8 on Linux. See my follow-up question at https://unix.stackexchange.com/q/526036/3651 – Konrad Rudolph Jun 20 '19 at 12:50
  • 1
    Actually never mind that question. [Your `ex` solution on Vim.SE](https://vi.stackexchange.com/a/789/10024) works. Namely, this: `ex -s "$@" '+%p|q!' /dev/stdin`. – Konrad Rudolph Jun 20 '19 at 12:59
5

You cannot simply put vim inside a pipe, because then Vim cannot display its UI.

ls | vim - | more # Does not work

One way to solve this is to use gvim -f - inside the pipe, as it opens in a separate window. You need to write the file via :w >> /dev/stdout and then :quit!.

Alternatively (and the only way in a console-only non-graphical environment), you could write your own script / function vimAndMore that takes the command that should be following vim in the pipe as an argument, and goes like this:

vimAndMore()
{
    TMPFILE=/tmp/pipecontents

    # Slurp stdin into the temp file.
    cat - > "$TMPFILE" || exit $?

    # Reconnect stdin to the terminal, so that Vim doesn't complain with "Warning:
    # Input is not from a terminal", and the terminal is kept intact.
    exec 0</dev/tty

    # Launch the editor.
    "${EDITOR:-vim}" "$TMPFILE" || exit $?

    # Carry on with the pipe.
    cat "$TMPFILE" | exec "$@"

    rm "$TMPFILE"
}

And change the pipe to this:

ls | vimAndMore more

If multiple pipeline steps are following, you have to use an explicit shell invocation:

ls | vimAndMore sh -c 'tr a-z A-Z | more'
Ingo Karkat
  • 167,457
  • 16
  • 250
  • 324
  • This doesn't work on Ubuntu 22.04 for me: https://asciinema.org/a/539855 . Vim reads the input, prints `Vim: Warning: Output is not to a terminal` and then I must navigate/operate blindly. When I finally press `:wq`, Vim exits and the next pipe gets activated. – saulius2 Nov 23 '22 at 19:27
  • Another, a similar logic works for me (): https://stackoverflow.com/a/10686830/1025073 . It just uses `vim "$TMPFILE" < /dev/tty > /dev/tty` without any `exec`s. I guess your solution is just missing one redirection line or two. Still I am not sure what exactly. Cheers:) – saulius2 Nov 23 '22 at 20:16
  • PS. Here I am commenting about the alternative, console-only way. – saulius2 Nov 23 '22 at 20:20
  • 1
    @saulius2 There was a typo in the final invocation; like the comments mention, the command has to be passed as an argument, not piped into. I posted this solution because what's now the accepted answer didn't work for me, but in the meantime it has been fixed, so I would recommend you use that, as it's less cumbersome to invoke. – Ingo Karkat Nov 25 '22 at 06:48
2

From my very limited experience, it's not possible to make Vim write to stdout. I don't think you can easily put Vim in a pipe.

You could try the command vipe from this package.

romainl
  • 186,200
  • 21
  • 280
  • 313
1
> ls -1 fred*.c | vim -

will result in Vim opening an unnamed file containing a list of files fred*

Perhaps I misunderstood your question though...

Alternative interpretation:

See this page which describes a technique to

Without saving the current buffer you want to send its contents to an interpreter (python, scheme, whatever), and you want that interpreter to run in a freshly spawned xterm. A new xterm window is useful because running the shell within Vim does not allow simultaneously inspection of program output and the code buffer, and Vim's shell is weak in several respects.

Fredrik Pihl
  • 44,604
  • 7
  • 83
  • 130