168

When invoking vim through find | xargs, like this:

find . -name "*.txt" | xargs vim

you get a warning about

Input is not from a terminal

and a terminal with pretty much broken behaviour afterwards. Why is that?


This question was explicitly about the why, not about the how to avoid. This was asked, and answered, elsewhere.

DevSolar
  • 4,560

6 Answers6

189

(Following on from grawity's explanation, that xargs points stdin to /dev/null.)

The solution for this problem is to add the -o parameter to xargs.  From man xargs:

-o

      Reopen stdin as /dev/tty in the child process before executing the command.  This is useful if you want xargs to run an interactive application.

Thus, the following line of code should work for you:

find . -name "*.txt" | xargs -o vim

GNU xargs supports this extension since some release in 2017 (with the long option name --open-tty).

For older or other versions of xargs, you can explicitly pass in /dev/tty to solve the problem:

find . -name "*.txt" | xargs bash -c '</dev/tty vim "$@"' ignoreme

(The ignoreme is there to take up $0, so that $@ is all arguments from xargs.)

117

When you invoke a program via xargs, the program's stdin (standard input) points to /dev/null. (Since xargs doesn't know the original stdin, it does the next best thing.)

$ true | xargs filan -s
    0 chrdev /dev/null
    1 tty /dev/pts/1
    2 tty /dev/pts/1

$ true | xargs ls -l /dev/fd/

Vim expects its stdin to be the same as its controlling terminal, and performs various terminal-related ioctl's on stdin directly. When done on /dev/null (or any non-tty file descriptor), those ioctls are meaningless and return ENOTTY, which gets silently ignored.

  • My guess at a more specific cause: On startup Vim reads and remembers the old terminal settings, and restores them back when exiting. In our situation, when the "old settings" are requested for a non-tty fd (file descriptor), Vim receives all values empty and all options disabled, and carelessly sets the same to your terminal.

    You can see this by running vim < /dev/null, exiting it, then running stty, which will output a whole lot of <undef>s. On Linux, running stty sane will make the terminal usable again (although it will have lost such options as iutf8, possibly causing minor annoyances later).

You could consider this a bug in Vim, since it can open /dev/tty for terminal control, but doesn't. (At some point during startup, Vim duplicates its stderr to stdin, which allows it to read your input commands – from a fd opened for writing – but even that is not done early enough.)

grawity
  • 501,077
34

The easiest way:

vim $(find . -name "*foo*")
trolol
  • 365
24

It should work just fine if you use the -exec option on find rather than piping into xargs.

find . -type f -name filename.txt -exec vi {} + 
Brad Koch
  • 151
11

Use GNU Parallel instead:

find . -name "*.txt" | parallel -j1 --tty vim

Or if you want to open all the files in one go:

find . -name "*.txt" | parallel -Xj1 --tty vim

It even deals correctly with filenames like:

My brother's 12" records.txt

Watch the intro video to learn more: http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
  • 5,099
0

maybe not the best but here it the script I use (named vim-open):

#!/usr/bin/env ruby

require 'shellwords'

inputs = (ARGV + (STDIN.tty? ? [] : STDIN.to_a)).map(&:strip)
exec("</dev/tty vim #{inputs.flatten.shelljoin}")

will work with vim-open a b c and ls | vim-open for instance