I want to use find to find certain files and then use xargs with cp and sed to move these files to another folder and rename them. I know how to do this in a loop, but that is not what I am looking for.

I tried:

find ${FOLDER} -iname "*${SOME_STRING}*" | xargs -I {} cp {} $(echo {} | sed 's/ABC/XYZ/g')

Why is this not working and what would be a solution?

  • How is it not working? What is it doing and what do you want it to do? Your command doesn't include cp, it will only print names. Is that what you want? Please edit your question and show us an example we can test and the result you are expecting. – terdon Nov 7 '15 at 18:15
  • 1
    How would the echo in that variable assignment know what {} means ? It just passes it literally to sed so nothing gets replaces hence the result of that assignment is {} and if you set -x before running that command you'll see the shell executes xargs -I '{}' cp '{}' '{}' which results in cp: ‘./blah-blah’ and ‘./blah-blah’ are the same file etc The proper way would be smth like find . -name whatever -exec sh -c 'cp "$0" "${0//ABC/XYZ}"' {} \; which handles all kind of filenames. – don_crissti Nov 7 '15 at 20:02

Try this. It doesn't need xargs, just find, sed and copy. It won't change any files, but will print a list of shell commands to copy the files. So if you like the commands it prints, then you run it again and remove the # before the sh -x on the last line and let the shell execute the commands.

find "${FOLDER:-.}" -iname "*${SOME_STRING:-ABC}*" |
sed "
    s/^/'/                      # surround filename with single quotes
    h                           # save filename in the sed hold space
    s/ABC/XYZ/g                 # change filename
    H                           # append to hold space (separates with newline)
    g                           # get original and modified from hold
    s/\n/ /g                    # replace the newline with a space
    s/^/cp -i/                  # make it into a copy command
"  #| sh -x                     # optionally pipe to shell

Why is this approach better than xargs in this case? Because this method starts only 3 processes total (find, sed, and sh) in addition to the required cp command for each file.

To use xargs, processing each filename would require more processes than the single required cp command.

It's much more efficient to let the one invocation of sed generate all of the cp commands, than to invoke a new sed process for each filename.

| improve this answer | |
  • This is an interesting solution. Thanks for the explanation. – Vincent Nov 7 '15 at 19:38
  • Fails for files like: My brother's 12" ABC-records worth >100$.txt, and touch Y-"$(perl -e 'print pack("c*",1..9,11..46,48..255)')" – Ole Tange Nov 12 '15 at 22:11
  • This answer really struck a chord with me, thank-you for thinking outside the box. – nidal Sep 24 '19 at 16:35

Using GNU Parallel it looks like this:

find ${FOLDER} -iname "*${SOME_STRING}*" |
  parallel cp {} '{= s/ABC/XYZ/g =}'

The {= =} construct contains a Perl expression that changes $_.

One of the advantages is that GNU Parallel does The Right Thing even if you have files called:

My brother's 12" records worth >100$.txt

or horrors like:

touch Y-"$(perl -e 'print pack("c*",1..9,11..46,48..255)')"
| improve this answer | |

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.