28

Sometimes I need to write text and then pipe that text into another command. My usual workflow goes something like this:

vim
# I edit and save my file as file.txt
cat file.txt | pandoc -o file.pdf # pandoc is an example 
rm file.txt

I find this cumbersome and seeking to learn bash scripting I'd like to make the process much simpler by writing a command which fires open an editor and when the editor closes pipe the output of the editor to stdout. Then I'd be able to run the command as quickedit | pandoc -o file.pdf.

I'm not sure how this would work. I already wrote a function to automate this by following the exact workflow above plus some additions. It generates a random string to act as a filename and passes that to vim when the function is invoked. When the user exits vim by saving the file, the function prints the file to the console and then deletes the file.

function quickedit {
    filename="$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-f0-9' | head -c 32)"
    vim $filename
    cat $filename
    rm $filename
}
# The problem:
# => Vim: Warning: Output is not to a terminal

The problem I soon encountered is that when I do something like quickedit | command vim itself can't be used as an editor because all output is constrained to the pipe.

I'm wondering if there are any workarounds to this, so that I could pipe the output of my quickedit function. The suboptimal alternative is to fire up a separate editor, say sublime text, but I really want to stay in the terminal.

  • From within vim, issue the command :w !pandoc -o file.pdf ? (Note: the space between w and ! is essential.) – John1024 May 10 '16 at 21:35
  • Ok - that's great. I didn't know you could pipe vim output from within vim itself. This serves my purposes, but for my future knowledge, is it possible to solve my problem exactly how I intended it to be solved? – theideasmith May 10 '16 at 21:39
  • Can you post your function? What do you mean when you talk about the problem you soon encountered (second last paragraph)? I don't get that. – Lucas May 10 '16 at 21:45
  • 5
    As an aside, you should use mktemp rather than reinventing it in insecure fashion. – Wildcard May 10 '16 at 23:03
  • As a new bash user, what are the security dangers of reinventing mktemp? – theideasmith May 10 '16 at 23:41
38

vipe is a program for editing pipelines:

command1 | vipe | command2

You get an editor with the complete output of command1, and when you exit, the contents are passed on to command2 via the pipe.

In this case, there's no command1. So, you could do:

: | vipe | pandoc -o foo.pdf

Or:

vipe <&- | pandoc -o foo.pdf

vipe picks up on the EDITOR and VISUAL variables, so you can use those to get it to open Vim.

If you've not got it installed, vipe is available in the moreutils package; sudo apt-get install moreutils, or whatever your flavour's equivalent is.

  • 1
    For Mac users who've arrived here: moreutils is available in Homebrew. – vergenzt Jan 10 '17 at 23:44
  • For FreeBSD users: moreutils is available to be installed via pkg(8). – Mateusz Piotrowski May 17 at 18:57
20

You can do this from within Vim:

:w !pandoc -o file.pdf

Or even write the buffer into a complex pipeline:

:w !grep pattern | somecommand > file.txt

And then you can exit Vim without saving:

:q!

However, considering your specific use case, there is probably a better solution by using vi as your command line editor. Assuming you use bash:

set -o vi

This sets your keybindings to vi. So you can edit your commands right on the command line with basic vi keybindings by pressing <Esc> and then typing vi commands such as x, cw, etc. (You can get back in insert mode by pressing i.)

Even better, and more relevant to this question, you can open Vim to create your command line content directly. Just type <Esc>v and you will get an empty Vim buffer. When you save and exit, that is the command on your command line and it is immediately run. This is much much more flexible than editing on the command line directly as you can write a whole mini-script if you want.


So, for example, if you want to write some tricky text and pipe it into pandoc immediately, you could just type:

<Esc>v

Then edit the Vim buffer until you have something like:

cat <<EOF | pandoc -o file.pdf
stuff for pandoc
more stuff for pandoc
EOF

Then save and exit (with :x) and the whole thing will be run as a shell command.

It will also be available in your shell's command history.

  • 1
    If you prefer to keep the Emacs key bindings, you can still use Vim to edit command lines by setting the EDITOR environment variable to vim and pressing Ctrl-X followed by Ctrl-E while editing the command line. – Anthony Geoghegan May 12 '16 at 10:31
17

Running in a pipeline

Try:

quickedit() (  trap 'rm ~/temp$$' exit; vim ~/temp$$ >/dev/tty; cat ~/temp$$ )

The key is that, to be able to use vim normally, vim needs stdout to be the terminal. We accomplish that here with the redirect >/dev/tty.

For purposes of security, I put the temporary file in the user's home directory. For more on this, see Greg's FAQ Question 062. This eliminates the need to use an obscure file name.

Example:

When vim opens, I type This function succeeded. and save the file. The result on the screen looks like:

$ quickedit | grep succeeded
This function succeeded.

Even though the output of quickedit is redirected to a pipeline, vim still works normally because we have given it direct access to /dev/tty.

Running a program from within vim

As I mentioned in the comments, vim can pipe a file to a command. From within vim, for example, issue the command :w !pandoc -o file.pdf (Note: the space between w and ! is essential).

  • 2
    Nice answer. This one directly addresses the user's question, and offers a quick and elegant solution: redirect vim to /dev/tty! Simple! – Mike S May 10 '16 at 23:49
  • I'm using Zsh; trap '...' exit fails but trap '...' EXIT appears to work. I don't know much about trapping, but my general approach for temporary files is to use mktmp [--suffix=...]. Also, vim <outfile> -c '...' >/dev/tty runs as normal until the file is loaded, then executes the given command chain, including :wq if you want to skip the editing phase. I also used set | grep -a EXIT and found signals=(EXIT ... DEBUG). – John P Jul 4 '17 at 19:21
5

Make sure that vim is set as your default editor (e. g. export EDITOR=vim in your .bash_profile or .bashrc. Then, at any prompt, you can type Ctrl-X followed by Ctrl-E. This will open your current command line in your configured editor (e. g. vim). Make your edits, save and exit, and the command will be executed as though you had typed it on the command line, including pipelines and the like.

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.