I am trying to do something like

vi `grep -Rl 'keyword'`

But I want to support spaces in the file names.

vi "`grep -Rl 'keyword'`"

Does not do what I need, it turns the grep output in a single argument with \n in it.

I tried

vi "`grep -Rl 'keyword'`| xargs"

And while it looks better, the result still is a single argument (now with spaces instead of \n).

Is there a good way to do this? Bash is fine, bonus points for being POSIX compliant.


I am aware the -R option is not available for all grep implementations and that bash has the nice nestable "$()" notation for ``.

  • Why do you need to pipe this after vi? Why not just use grep -Rl "keyword" /path/to/files/*? – Nasir Riley Mar 29 at 22:01
  • I am not piping to vi, I am using the output as arguments to vi, hence vi will open all the files listed by grep. (the -l option only ouputs file names). The idea is that the files containing keyword should be modified... edit modified the question title to no longer mention pipe... – Fictional Mar 29 at 22:05

Using find:

find . -type f -exec grep -q 'pattern' {} \; -exec vi {} +

This would look into each regular file in or below the current directory for a line that matches pattern. For these found files, vi would be started.

If there are several hundreds of these files, vi would be called for batches of files. Change -exec vi {} + to -exec vi {} \; to call vi once for each file.

We use grep -q as we're not actually interested in the output of grep at all, only its exit status.

This would allow for any valid Unix filename, and is completely POSIX compliant (recursive grep is not).

  • Great, this works a charm :) Did not realize you could use multiple exec-s on find, nor that the exit code was checked there... – Fictional Mar 30 at 9:11
  • @Fictional Yes, each -exec works as a "test" on the pathname that find is looking at, just like -type and -name etc. – Kusalananda Mar 30 at 9:17

How about

grep -Rl 'pattern' | xargs -d '\n' vi

I don't know how POSIXy the -d option is.

If you have GNU tools, this is better to allow any file name:

grep -Rlz 'pattern' | xargs -0 vi
  • The last one does not work for me. The first one is nice though, and is a more general case solution. Which is nice. – Fictional Mar 30 at 9:18

Another option, in bash, and assuming that you don't have so many matching filenames that you would exceed $(getconf ARG_MAX), would be to loop over the expansion of ** under globstar and build up an array of filenames that match:

shopt -s globstar
for entry in **
  [ -f "$entry" ] || continue
  grep -q 'pattern' -- "$entry" && matching_files+=("$entry")
vi -- "${matching_files[@]}"

The loop will find non-hidden files and directories at and underneath the current directory; the first test we do is to see if we have a plain file. If so, we run a quiet/silent grep against it for the desired pattern. If the file contains the pattern, we add its filename to the matching_files array. At the end, we run vi on the safely-expanded list of entries in the array.

If you also want to search hidden files and directories, set shopt -s dotglob.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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