macosgreppipexargscp

How to copy files found with grep on OSX


I'm wanting to copy files I've found with grep on an OSX system, where the cp command doesn't have a -t option.

A previous posts' solution for doing something like this relied on the -t flag in cp. However, like that poster, I want to take the file list I receive from grep and then execute a command over it, something like:

grep -lr "foo" --include=*.txt * 2>/dev/null | xargs cp -t /path/to/targetdir


Solution

  • Less efficient than cp -t, but this works:

    grep -lr "foo" --include=*.txt * 2>/dev/null |
      xargs -I{} cp "{}" /path/to/targetdir
    

    Explanation:

    For filenames | xargs cp -t destination, xargs changes the incoming filenames into this format:

    cp -t destination filename1 ... filenameN
    

    i.e., it only runs cp once (actually, once for every few thousand filenames -- xargs breaks the command line up if it would be too long for the shell).

    For filenames | xargs -I{} cp "{}" destination, on the other hand, xargs changes the incoming filenames into this format:

    cp "filename1" destination
    ...
    cp "filenameN" destination
    

    i.e., it runs cp once for each incoming filename, which is much slower. For a large number (e.g., >10k) of very small (e.g., <10k) files, I'd guess it could even be thousands of times slower. But it does work :)

    PS: Another popular technique is use find's exec function instead of xargs, e.g., https://stackoverflow.com/a/5241677/1563960