Bash – Why does piping filenames into VIM break bash when I return to the shell

bashfindgrepvimxargs

I searched for a few files with find and I wanted to open all of them in tabs in Vim. So I tried this:

find . | xargs fgrep -l myExpression | xargs vim -p

This worked great, except when I was done and quit Vim everything I typed in vim was invisible, and backspace didn't work (so when I typed like ls<backspace><backspace>exit the resulting history was ls^?^?exit and not exit. It also gave me a warning that my piping was not from a terminal.

Everything was fine again when I restarted the shell, though. This was reproducible always.

Bash Version:

GNU bash, version 3.2.25(1)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2005 Free Software Foundation, Inc.

Vim Version:

VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Apr 14 2015 05:43:37)
Included patches: 1-699

Best Answer

A command line program can take input from a user via two sources: from stdin (which you are piping to), and by attaching directly to the TTY. Bad things can happen when these are mixed up. Vim does not want to read input from a pipe, it wants you, the user, directly. So let's give it the real stdin back.

As a solution, we can use a command substitution to directly pass the files as arguments to Vim:

vim -p $(find . | xargs fgrep -l myExpression)

Note that due the expansions performed by the shell, this will not handle files with spaces in their names correctly, but my Bash-fu is too weak to know an easy fix.

Also, find options | xargs some command can be written as find options -exec some command {} +, which might be considered more elegant.

Related Question