I want to rename multiple files (file1 ... filen to file1_renamed ... filen_renamed) using command find command:

find . -type f -name 'file*' -exec mv filename='{}' $(basename $filename)_renamed ';'

But getting this error:

mv: cannot stat ‘filename=./file1’: No such file or directory

This not working because filename is not interpreted as shell variable.

share|improve this question
up vote 19 down vote accepted

The following is a direct fix of your approach:

find . -type f -name 'file*' -exec sh -c 'x="{}"; mv "$x" "${x}_renamed"' \;

However, this is very expensive if you have lots of matching files, because you start a fresh shell (that executes a mv) for each match. And if you have funny characters in any file name, this will explode. A more efficient and secure approach is this:

find . -type f -name 'file*' -print0 | xargs --null -I{} mv {} {}_renamed

It also has the benefit of working with strangely named files. If find supports it, this can be reduced to

find . -type f -name 'file*' -exec mv {} {}_renamed \;

The xargs version is useful when not using {}, as in

find .... -print0 | xargs --null rm

Here rm gets called once (or with lots of files several times), but not for every file.

I removed the basename in you question, because it is probably wrong: you would move foo/bar/file8 to file8_renamed, not foo/bar/file8_renamed.

Edits (as suggested in comments):

  • Added shortened find without xargs
  • Added security sticker
share|improve this answer
    
In the case the x is useless: find . -type f -name 'file*' -exec mv {} "{}_renamed" \; xargs version have the same efficiency like the first example/ – Costas Sep 5 '15 at 11:09
    
The x is there only to directly fix the asker's approach. – Thomas Erker Sep 5 '15 at 11:17
1  
(1) It is very dangerous to use {} directly in a shell (sh -c "…") command — you should always pass it in as an argument.  (2) Not all versions of find support the {}_renamed construct.  (3) I don't understand your statement that xargs is useful for removing files (in contrast to renaming them). – G-Man Sep 5 '15 at 12:53
    
@G-Man: The difference with xargs is not mv vs. rm, but use of {} vs. without. The former is similar to mv file1 file1_renamed; mv file2 file2_renamed while the latter is rm file1 file2. – Thomas Erker Sep 5 '15 at 13:02
    
@Costas: The first version starts sh and mv per file (unless mv is a shell builtin); the second version only starts one mv per file, – Thomas Erker Sep 5 '15 at 13:18

After trying the first answer and toying with it a little I found that it can be done slightly shorter and less complex using -execdir:

find . -type f -name 'file*' -execdir mv {} {}_renamed ';'

Looks like it should also do exactly what you need.

share|improve this answer
1  
With find implementations that support -execdir and {} not as a whole, it's also the safest. You may want to add a -i to mv though (and -T if your mv supports it) – Stéphane Chazelas Feb 9 '16 at 17:33
    
@StéphaneChazelas even better, instead of relying on mv for a prompt, or in addition to it, you can (no doubt depending on whether your implementation of find supports it) also use -okdir which will output the command to be executed before executing it. – Matijs Feb 9 '16 at 19:25

I want to expand on the first answer and note that this won't work to append to the filename since the ./ path prefix is present in the filename argument.

Modifying Thomas Erker answer, I find this one a more generic approach

find . -name PATTERN -printf "%f\0" | xargs --null -I{} mv {} "prefix {} suffix"

xargs options:

--null Indicates that each argument passed through stdin ends with a null character (\0). This way the filename can contain spaces, otherwise each word will be threated as a different parameter for the mv command.

-I replace-str Every ocurrence of replace-str will be replaced with the argument read from stdin. So, you may change it for other string if you need it so.

share|improve this answer

Another approach is to use a while read loop over find output. This allows access to each file name as a variable that can be manipulated without having to worry about additional cost / potential security issues of spawning a separate sh -c process using find's -exec option.

find . -type f -name 'file*' |
    while IFS= read file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done

And if the shell being used supports the -d option to specify a read delimiter you can support strangely named files (e.g. with a newline) using the following:

find . -type f -name 'file*' -print0 |
    while IFS= read -d '' file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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