Invoking vi through find | xargs breaks the terminal. Why


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.

Best Answer

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.)