167

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.

5

6 Answers 6

116

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

4
  • 25
    +1, and for TL;DR people just run stty sane
    – doc_id
    Feb 11, 2015 at 10:33
  • @rahmanisback: The other answers, plus Trevor's comment, all provided ways to avoid terminal breakage in the first place. I accepted grawity's answer, because my question was "why", not "how to avoid" -- that's covered by another question that actually spawned this one.
    – DevSolar
    Feb 11, 2015 at 10:41
  • @DevSolar Understood, but think about frustrated people like me who just google how to get rid of that behavior while not -unfortunately - have enough time right now to study "why", which is very interesting nonetheless.
    – doc_id
    Feb 11, 2015 at 18:34
  • 5
    when my terminal breaks, like this, i use reset instead of stty sane and it works fine after that. Apr 21, 2015 at 17:19
186

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

11
  • 2
    How would you create a bash alias out of this? $@ doesn't seem to be translating arguments correctly.
    – zanegray
    Aug 31, 2015 at 15:47
  • 3
    @zanegray -- you can't make an alias, but you can make it a function. Try: function vimin () { xargs sh -c 'vim "$@" < /dev/tty' vim; } Feb 12, 2017 at 18:02
  • For a detailed explanation of how the GNU xargs solution works, and why you need the dummy ignoreme string, see vi.stackexchange.com/a/17813
    – wisbucky
    Nov 2, 2018 at 20:42
  • @zanegray, You can make it an alias. The quotes are tricky. See solution at vi.stackexchange.com/a/17813
    – wisbucky
    Nov 2, 2018 at 20:46
  • The -J, -o, -P and -R options are non-standard FreeBSD extensions which may not be available on other operating systems. (It was not available on macOS for me because I installed xargs from homebrew (the GNU one)) May 7, 2019 at 16:56
34

The easiest way:

vim $(find . -name "*foo*")
4
  • 6
    The main question was "why", not "how to avoid it", and it's been answered to satisfaction two and a half years ago.
    – DevSolar
    Mar 8, 2014 at 9:35
  • 5
    This, of course, does not work properly when filenames contain spaces or other special characters, and is also a security risk. Jun 17, 2015 at 16:14
  • 1
    My favorite answer because it works for every command that lists files, not just "find" or wildcards. It does require a little trust, as Dejay points out. Sep 1, 2016 at 20:50
  • 2
    This is will not work with many use cases xargs is designed for: e.g., when the number of paths is very high (cc @TravisWilson) Nov 30, 2016 at 19:47
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 {} + 
3
  • 3
    Huh... the trick there is the + (instead of "the usual" \;) to get all the found files into one Vim session -- an option I keep forgetting about. You are right, of course, and +1 for that. I use vim $(find ...) simply out of habit. However, I was actually asking for why the pipe operation screws up the terminal, and grawity nailed that with his explanation.
    – DevSolar
    Mar 10, 2013 at 18:45
  • 3
    This is the best answer and it works on both BSD/OSX/GNU/Linux.
    – kevinarpe
    Feb 24, 2014 at 17:17
  • 1
    Also, find is not the only way of getting a list of files that have to be edited simultaneously by vim. I can use grep to find all files with a pattern and try editing them at the same time as well. Nov 12, 2014 at 8:41
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

6
  • 1
    Not ubiquitously available. Most of the day I am working on servers where I am not at liberty to install additional tools. But thanks for the hint anyway.
    – DevSolar
    Sep 16, 2011 at 20:16
  • 1
    If your are at liberty to do 'cat > file; chmod +x file' then you can install GNU Parallel: It is simply a perl script. If you want man pages and such, you can install it under your homedir: ./configure --prefix=$HOME && make && make install
    – Ole Tange
    Sep 20, 2011 at 9:09
  • 2
    OK, tried that - but parallel does not open all the files, it does open them in succession. It's also quite a mouthful for a simple operation. vim $(find . -name "*.txt") is simpler, and you get all files opened at once.
    – DevSolar
    Sep 20, 2011 at 11:00
  • 5
    @DevSolar: Somewhat unrelated, but both find | xargs and $(find) will have big problems with spaces in file names. Sep 20, 2011 at 18:34
  • 2
    @grawity Correct, but there is no easy way around it (that I know of). You'd have to start fiddling with $IFS, -print0 and stuff, and then you left the realm of a one-shot command line solution and reached a point where you should come up with a script... there's a reason why spaces in filenames are discouraged.
    – DevSolar
    Sep 21, 2011 at 9:54
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

1
  • As for several other answers, note that the actual question was "why", not "how to avoid it". (For which I would still point to Trevor's comment under my question as the most solid way that doesn't require scripting, aliases or anything.)
    – DevSolar
    Aug 2, 2019 at 9:25

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .