32

Something I find myself doing a lot is running a find command and then editing all of them in vi, which looks something like this:

> find . "*.txt"
./file1.txt
./file2.txt
./path/to/file3.txt

> vi ./file1.txt ./file2.txt ./path/to/file3.txt

Is there a clever & simple way to do this all in one command line?

Giacomo1968
  • 55,001
abeger
  • 795

5 Answers5

47

This should do the trick:

find . -name "*.txt" -exec vim {} + 

Use Vim, it's better for your health. :-)

The oft-overlooked + option to -exec makes all filenames (up to line length limits) appear in one line, i.e. you still get all the files opened in one vim session (navigated with :n for next and :N for previous file).

With vim -p you get a file tab for each file. Check :help tab-page-commands for more details.

With vim -o you will get horizontally split windows for each file, vim -O vertically split windows. Check :help window-move-cursor for more details.

Note that the previous version of this answer, vim $(find . -name "*.txt"), does not work with spaces in filenames, and has security implications.

Piping into xargs vi gives a Warning: Input is not from a terminal, plus a terminal with completely bogus behaviour afterwards. User grawity explained why in a comment below, and with a bit more explanation in this question.

DevSolar
  • 4,490
  • No need to escape the *, globbing won't happen inside the double quotes. – Kusalananda Sep 15 '11 at 15:57
  • Just realized that myself when putting it to the test... thanks. – DevSolar Sep 15 '11 at 15:59
  • 4
    Don't know how much you know about vim, but it took me /forever/ to figure out that :n shows the next file and :N shows the previous one. – zpletan Sep 15 '11 at 16:17
  • 1
    @DevSolar, your solution was better than mine (now deleted) which tried to execute vi through xargs, which doesn't work since Vi won't get a terminal for input/output properly. – Kusalananda Sep 15 '11 at 16:17
  • 4
    @DevSolar: Vim expects its stdin to be the same as its controlling terminal, and performs various terminal-related ioctls on stdin directly. (You could consider this a bug. Vim certainly can open /dev/tty and call ioctl() on that; it's just too lazy to do it.) When invoked by xargs, vim receives /dev/null as its standard input, which just ignores terminal-control ioctls. – u1686_grawity Sep 15 '11 at 16:18
  • @grawity: That explains a lot. Thank you very much indeed! You fully deserve a "correct answer" for that, so I made it a proper question. – DevSolar Sep 15 '11 at 16:27
  • 3
    @zpletan: vim -p if you want file tabs. (gt and gT to navigate, or click with your mouse.) – u1686_grawity Sep 15 '11 at 16:45
  • 2
    FYI, to avoid the warning when piping into vim, just specify - as the first argument, as in echo foobar | vim -. – Konrad Rudolph Sep 15 '11 at 18:47
  • 2
    @Konrad Rudolph: find . -name "*.txt" | vim - gives you a vim session on an unnamed file containing the names of the found files, which is not what the OP asked for... – DevSolar Sep 16 '11 at 07:06
  • @DevSolar Well, you still need to use xargs (of course). – Konrad Rudolph Sep 16 '11 at 07:17
  • @Konrad Rudolph: "Too many editor arguments". Sorry, it just doesn't work. Have you tried it yourself? – DevSolar Sep 16 '11 at 07:41
  • @DevSolar Ah, I see the problem: vim - tells vim to get the input from STDIN instead of a file. Conversely, with find you want to pass multiple arguments to vim – quite the opposite. Sorry. – Konrad Rudolph Sep 16 '11 at 09:46
  • @Konrad Rudolph: Yeah, it wasn't what I was looking for in this post, but piping command output into a vi buffer is also something I was wondering about. Thanks! – abeger Sep 16 '11 at 16:29
5

Or run vim and from there:

:args **/*.txt
Benoit
  • 7,033
1

To edit all *.txt, you can just run: vim *.txt. To edit files returned by find, read futher.


On Unix/macOS, you can use find with BSD xargs (see: man xargs), e.g.

find -L . -name "*.txt" -type f -print0 | xargs -o -0 vim

-o (xargs): Reopen stdin as /dev/tty in the child process before executing the command.

Related: Terminal borked after invoking Vim with xargs at Vim.SE.

kenorb
  • 25,417
1

Additionally, if you wanted them opened one at a time, you can also use find -exec or use a simple for loop. Edited per ceving's comment.

find . -name "*.txt" -exec vi {} \;

or

OLDIFS=$IFS
IFS=$(echo -en "\n\b")
for i in `find . -name "*.txt"`
    do
        vi $i
    done
IFS=$OLDIFS
OldWolf
  • 2,433
  • ...but why would you want to do that? – DevSolar Sep 15 '11 at 16:29
  • @DevSolar The first is to point out the capability in find, the second is a general purpose loop. Maybe you want to do something to every file before you edit it. – OldWolf Sep 15 '11 at 18:12
  • Have you ever tried the second? First: the find returns the current directory because -name is missing. And second: the command fails miserably as soon as a file name contains a space. -1 for ill-advised answer. – ceving Sep 16 '11 at 08:38
  • Actually, the subject of spaces in filenames applies to my answer just as well. I cannot even think of a way to handle them properly without turning it from a command line into a little script of its own. There is a good reason why spaces in filenames are discouraged. – DevSolar Sep 16 '11 at 11:34
  • @ceving A valid point. I presumed the original posters find was valid for his needs. Editing. – OldWolf Sep 16 '11 at 14:01
1

If you've already typed out your find command I find that it can be much easier to use xargs to open the search results:

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

The -p option tells vim to open each file in a new tab. I find this more convenient than just using buffers, but if you don't then just leave off that option.

Cory Klein
  • 1,692