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
  • 53,069
  • 19
  • 162
  • 212
abeger
  • 795
  • 2
  • 8
  • 9

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,440
  • 5
  • 29
  • 38
  • 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](http://superuser.com/questions/336016/). – 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
  • 6,993
  • 4
  • 23
  • 31
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
  • 24,736
  • 27
  • 129
  • 199
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,423
  • 15
  • 15
  • ...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,602
  • 2
  • 17
  • 21