39

Is there any chance to write the content of the current vim buffer to stdout?

I'd like to use vim to edit content that was passed via stdin - without the need of a temporary file to retrieve the modified content (on Linux/Unix).

Is it possible that a plugin/script - that act on quit or save put the buffer content to stdout?

kenorb
  • 155,785
  • 88
  • 678
  • 743
hooblei
  • 3,210
  • 2
  • 20
  • 17
  • you don't say what platform, but `:w! /dev/stdout` "works" on linux where "works" means "but the line discipline is raw so it doesn't really work". – msw Jul 10 '10 at 14:32
  • 1
    :w! /dev/stdout - works with gvim but useless with vim ... hm – hooblei Jul 10 '10 at 14:49
  • the question is ill-conceived, the general answer is no; why must you avoid temporary files? – msw Jul 10 '10 at 15:01
  • 6
    Ill-conceived question? - i just asked for a way to write the vim buffer content into stdout and why not avoid temorary files? Without temporary files, there is one thing less to deal with - programm crashes tmp files remain etc. But it's ok, if the answer is no way, i'll have to use temporary files. To modify the content, user interaction is needed - like the crontab -e case from miedwar - so sed, awk, perl is not a option. – hooblei Jul 10 '10 at 15:29
  • 1
    possible duplicate of [Pipe Vim buffer to stdout](http://stackoverflow.com/questions/10686183/pipe-vim-buffer-to-stdout) – kenorb Feb 14 '15 at 12:54
  • Related [How to edit a command output and execute it?](http://stackoverflow.com/questions/36933754/how-to-edit-a-command-output-and-execute-it) – Wallebot Apr 29 '16 at 10:50

11 Answers11

28

Since you use Linux/Unix, you might also be interested in trying out moreutils. It provides a command called vipe, which reads from stdin, lets you edit the text in $EDITOR, and then prints the modified text to stdout.

So make sure you set your editor to Vim:

export EDITOR=vim

And then you can try these examples:

cat /etc/fstab | vipe
cut -d' ' -f2 /etc/mtab | vipe | less
< /dev/null vipe
CervEd
  • 3,306
  • 28
  • 25
jabirali
  • 1,315
  • 7
  • 13
  • thanks - vipe uses temporary files too but moreutils i did not know yet and some of these utils look very useful – hooblei Jul 11 '10 at 17:32
  • 6
    Nice to learn about `moreutils`. However vipe uses temporary files, which does not fit my application of "decrypt file, modify content, encrypt result". – not-a-user Jan 28 '14 at 07:48
  • `moreutils/vipe` didn't work for me. its behavior is inconsistent on chained pipe operations which causes content of the target file to be lost. – karakays Mar 31 '19 at 10:24
26

I think :w !tee would work perfectly,

weynhamz
  • 1,968
  • 18
  • 18
  • 3
    You can even use pipe in this command. For example, I define the command 'Copy' to copy specific lines to the clipboard using [gpaste](https://github.com/Keruspe/GPaste/) `command -range=% Copy :,w !tee | gpaste` – weynhamz Aug 17 '12 at 08:58
  • 1
    if I forget to open file without sudo, this allows me to write it `:w !sudo tee someFileThatNeedsSudo` –  Oct 25 '17 at 02:31
  • 2
    `:w !tee` does not work with Vim 8 or NeoVim. Tested on macOS and Linux: `echo hello | vim '+wq! !tee'`. Passing `-e` also doesn’t work, and adding a colon after `+` makes no difference either. – Konrad Rudolph Jun 20 '19 at 11:16
13

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

Here is the simplest working example:

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

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

And here are some string parsing examples:

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

Here is a simple example using ex which is equivalent to vi -e:

ex -s +%p -cq /etc/hosts

Related:

Community
  • 1
  • 1
kenorb
  • 155,785
  • 88
  • 678
  • 743
  • I didn't know you could execute commands with +{command}. I'm glad I found this, since I was having trouble reading from stdin and writing to stdout at the same time. – Daniel Que Oct 06 '16 at 03:07
  • Seems like `%print` turns empty lines into a line with a single space? Do you know how to work around this? For example, `echo -n | vim -es '+%print' '+:q!' /dev/stdin | cat -etv` and `echo | vim -es '+%print' '+:q!' /dev/stdin | cat -etv` outputs `$`. `cat -etv` shows all the invisible characters, so `$` == ``. `echo -n` is an empty input, `echo` is input with a single newline. – dosentmatter May 21 '18 at 20:37
7

Reading from stdin:

echo "hey" | vim  -

When you :w you'd still have to give it a filename.

Programs that use vim as their EDITOR, like crontab -e pass it a filename so that user can just :x and not worry about filenames.

EDIT

You could also do something like this:

mkfifo /tmp/some_pipe
echo "hey" > /tmp/some_pipe ; cat /tmp/some_pipe

And from another process (or terminal)

vim /tmp/some_pipe

Beware that writing to a pipe will block until something reads from it, and reading will block untill something writes to it, so it might be safer to use regular files.

miedwar
  • 7,248
  • 1
  • 16
  • 15
  • 2
    I use this to read and write encrypted files via vim. To open a PGP file: `pgpv < secret.pgp | vim -`. After editing: `:w !pgpe -co secret.pgp`. In this situation, don't forget to disable swap, backups, vimrc, history, etc. – Clint Pachl Apr 24 '19 at 09:23
2

vim can be used in pipes as full-featured filter with explicit names for stdin and stdout as in following example:

echo xxx                                             |
vim                                                  \
  -esnN -i NONE                                      \
   +'1 s /x/y/g | 1 s /^/Hallo / | x! /dev/stdout'   \
   /dev/stdin                                        |
cat -n

Abbreviating /dev/stdin with - won't work, even not with -v flag.

xyxyber
  • 109
  • 1
  • 7
  • I noted, that this solution may not work for all implementations of vim in all environments under all circumstances, but mostly it works well. – xyxyber Oct 26 '15 at 00:10
  • This is intriguing but I am not sure how the the "1 s " is doing the sustitution or how the "x!" writes to stdout. (I did try looking up in vim help) – stephenmm Dec 16 '15 at 18:52
  • I tried it just now under Cygwin, Debian Jessie, Knoppix 7.4, Mageia 5 and Raspbian Jessie and it worked in all this systems in the bash. The result of the substitution is "Hallo yyy", blanks are allowed in vim commands between line range, command and comman parameters, the bar is the command separator in vim. – xyxyber Dec 18 '15 at 10:51
  • Under some system configurations problems can occur, if vim is used via CGI. But I didn't found the difference between this systems. The same scripts work on some systems perfectly and on others not. They can be avoided with storing to an intermediate temporary file. – xyxyber Dec 16 '16 at 23:02
  • Under systems with a running X server the extra flag -X prevents from waiting for a X connection. – xyxyber Aug 15 '18 at 19:55
2

Using :print will write to stdout, particularly if vim is run with both the -E and -s options, which cause it to run noninteractively and silently. See :h -E and :h -s-ex:

The output of these commands is displayed (to stdout):
                        :print                          
                        :list
                        :number
                        :set      to display option values."

Use :%print to print the whole current buffer.

jmckernon
  • 101
  • 1
  • 4
2

Try something like:

{ FROMCMD | vim - 8>&1 >&9 | tac | tac | TOCMD; } 9>&1

and use ':w !cat >&8' when you are finished editing

'tac | tac' ensures that TOCMD doesn't receive any data until you quite vim, this is only useful if TOCMD writes to the terminal so that it doesn't make a mess while vim is still running.

Notice that running 'less' as your TOCMD (or any other program that do some terminal manipulation) is not going to work as expected because even if you delay the data, the processes still start "at the same time" and may access the terminal at the same time.

bingo
  • 21
  • 1
2

This post gave me an idea: copying the buffer/or selection to clipboard:

  1. Install xclip (any linux distribuition package manager should have it.)

  2. Do this vim command:

:w !xclip

If you want to copy only some lines, select tem first with (normal mode) V, then issue the command above.

Iacchus
  • 2,671
  • 2
  • 29
  • 24
1

You can also use pipe.vim. It does use a temporary file, but at least you don't have to worry about it in your own script.

To the second part of your question, you use the -s command line option with vim to remap :w to something else (like :w !tee).

studgeek
  • 14,272
  • 6
  • 84
  • 96
1

You can use the ex-mode (p)rint command to print any set of lines you want to stdout.

print all lines:

:1,$p

Also prints all lines (% is a shorthand for the range 1,$)

:%p

print lines 4-10:

:4,10p

print next line containing FOO

/FOO/ p

print all lines containing FOO

g/FOO/ p
Ray
  • 1,706
  • 22
  • 30
1

What helped resolve matters for me is adding

setenv SHELL /bin/bash
exec bash -l

to .bashrc, otherwise vim can launch the wrong shell. Also, a script called save, which encrypts stdin and saves the result to $1, so the command in vim becomes :w !save afilename.

qfunq
  • 11
  • 2