Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

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?

share|improve this question
4  
Side note: You can perform this operation entirely within vim, not using find or xargs at all. Open vim with no arguments, then run :args **/*.txt<CR> to set vim's arguments from inside the editor. –  Trevor Powell Mar 21 '13 at 13:26
1  
@TrevorPowell: In all these years, vim never ceased to amaze me. –  DevSolar Mar 21 '13 at 13:52

5 Answers 5

up vote 29 down vote accepted

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

share|improve this answer
    
...with a flourish. Thank you very much! –  DevSolar Sep 15 '11 at 16:44

Following on from grawity's answer, xargs points stdin to /dev/null


From OSX/BSD 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

For GNU man xargs there is no flag, but we 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

share|improve this answer
    
+1 and a thousand thanks for that did the trick. –  DerMike Jul 20 '12 at 8:45
    
Hot damn that is an awesome find. Thanks for sharing! –  Jeremy Visser Jul 25 '12 at 3:08

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

$ find . -type f -name filename.txt -exec vi {} +

share|improve this answer
    
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 '13 at 18:45
    
This is the best answer and it works on both BSD/OSX/GNU/Linux. –  kevinarpe Feb 24 at 17:17

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

share|improve this answer
    
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 '11 at 20:16
    
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 '11 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 '11 at 11:00
3  
@DevSolar: Somewhat unrelated, but both find | xargs and $(find) will have big problems with spaces in file names. –  grawity Sep 20 '11 at 18:34
1  
@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 '11 at 9:54

The easiest way:

vim $(find . -name "*foo*")
share|improve this answer
1  
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 at 9:35

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.