I want to rename multiple files (file1 … fileN to file1_renamed … fileN_renamed) using 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.
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
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
findwithoutxargs - Added security sticker
Method 2
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.
Method 3
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
Method 4
I was able to do something similar with the for, find, and mv.
for i in $(find . -name 'config.yml'); do mv $i $i.bak; done
This finds all the config.yml files and renames them to config.yml.bak
Method 5
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" | xargs --null -I{} mv {} "prefix {} suffix"
xargs options:
--null Indicates that each argument passed through stdin ends with a null character (). 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.
Method 6
I thought it will be nice if here will be provided the option to rename the file but to changing only the part of it’s name. For example the file’s extension.
What the point to change the file name if you only able to add something to it’s name?
find . -name "*.txt" -exec sh -c 'x={}; mv "$x" $(echo $x | sed 's/.txt/.bat/g')' ;
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0