3

I am interested in recursively grepping for a string in a directory for a pattern and then cp'ing the matching file to a destination directory. I thought I could do something like the following, but it doesn't seem to make sense with linux's find tool:

find . -type f -exec grep -ilR "MY PATTERN" | xargs cp /dest/dir

Is there a better way of going about this? Or a way that even works would be a solid start.

imaginative
  • 1,971
  • 10
  • 32
  • 48

4 Answers4

12

Do man xargs and look at the -I flag.

find . -type f -exec grep -ilR "MY PATTERN" {} \; | xargs -I % cp % /dest/dir/

Also, find expects a \; or + after the -exec flag.

Sammitch
  • 2,111
  • 1
  • 21
  • 35
1

xargs reads its standard input and puts them at the end of the command to be executed. As you've written it, you would end up executing

cp /dest/dir sourcefile1 sourcefile2 sourcefile3

which is backwards from what you want.

You could use the -I option to specify a placeholder for xargs, like this: xargs -I '{}' cp '{}' /dest/dir.

Also, find takes care of the recursion, so your grep does not need -R.

Final solution:

find . -type f -exec grep -il "MY PATTERN" '{}' + | xargs -I '{}' cp '{}' /dest/dir
200_success
  • 4,771
  • 1
  • 25
  • 42
  • I commented below also, does -exec need to be terminated? I'm getting `find: missing argument to `-exec`, not sure where I'd put `{} \;` when there is a pipe to xargs involved. – imaginative Sep 13 '13 at 00:22
  • Thanks, corrected. I've used `+` instead of `\;` for efficiency, since GNU find supports it. – 200_success Sep 13 '13 at 01:31
  • @imaginative There is no fixed "above" and "below" on Stack Exchange; the relative position of posts change both because of voting, activity and user preference (see the "active", "oldest", "votes" sorting tabs just below the question). Hence, it is best to be explicit about which answer you refer to (by linking or at least specifying the full name of the answerer works well in my experience). For example, for me the only "below" currently is Neil H Watson's unvoted and uncommented answer. I'm *guessing* you are referring to Sammitch's answer, which is now on top. – user Sep 13 '13 at 09:23
1

I don't think you need find. Recursive grep:

grep -rl "MY PATTERN" .
Neil H Watson
  • 448
  • 1
  • 5
  • 18
1

None of the examples above takes into account the possibility of grep's -l generating duplicated results (files with the same name, but in different places), and therefore find's exec's hook overwriting files in the destination directory.

An attempt to solve this:

$ tree foo
foo
├── bar
│   ├── baz
│   │   └── file
│   └── file
├── destdir
└── file

3 directories, 3 files

$ while read a; do mv $a foo/destdir/$(basename $a).$RANDOM; done < <(grep -rl content foo)

$ tree foo
foo
├── bar
│   └── baz
└── destdir
    ├── file.10171
    ├── file.10842
    └── file.25404

3 directories, 3 files
dawud
  • 15,096
  • 3
  • 42
  • 61