84

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.

1
  • 1
    Why are you using basename? Do you want ./2017/photos/holiday/dscn1234.jpg to be renamed to ./dscn1234.jpg_renamed in the top level directory? Commented Oct 6, 2020 at 9:02

8 Answers 8

100

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
10
  • 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
    Commented Sep 5, 2015 at 11:09
  • The x is there only to directly fix the asker's approach. Commented Sep 5, 2015 at 11:17
  • 4
    (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). Commented Sep 5, 2015 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. Commented Sep 5, 2015 at 13:02
  • 1
    and if you then wanted to change the _renamed files back to their original names, how would you do that?
    – mcmillab
    Commented Sep 20, 2018 at 2:45
28

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.

6
  • 2
    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) Commented Feb 9, 2016 at 17:33
  • 1
    @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
    Commented Feb 9, 2016 at 19:25
  • Worth mentioning that -depth is also a good idea if you are going to also touch directory names. Commented Jan 12, 2019 at 21:24
  • 1
    Argh, just found that -execdir does have one very annoying downside, find refuses to do anything if PATH contains any relative paths... askubuntu.com/questions/621132/… find: The relative path XXX is included in the PATH environment Commented Jan 13, 2019 at 14:17
  • $ find ./packages -type f -name '*.ddeb' -execdir mv {} {}.deb + --> find: Only one instance of {} is supported with -execdir ... +; find --version --> find (GNU findutils) 4.7.0. info "(find) Multiple Files" says: Only one '{}' is allowed within the command, and it must appear at the end, immediately before the '+'.
    – user30747
    Commented Jun 25, 2021 at 15:14
16

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
1
  • I had to search for files with specific prefix and drop them, this helped me a lot find . -name "prefix*" -maxdepth 1 | while IFS= read file_name ; do; mv $file_name "${file_name/prefix//}"; done
    – Shiva
    Commented Sep 20, 2022 at 16:13
7

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.

3
  • Why do the pringf "%f\0" instead of a print0?
    – slm
    Commented Jul 8, 2018 at 14:21
  • 2
    @sim, because -print0 will produce ./ prefixes if PATTERN contains shell metacharacters which get in the way when renaming to something that prefixes the original names. (e.g. rename 0 - foo.txt to 00 - foo.txt, 1 - bar.txt to 01 - bar.txt etc.)
    – das-g
    Commented Aug 7, 2018 at 14:49
  • 1
    an inefficient solution using sh and basename to add a prefix: find . -name "job-info.json" -type f -execdir sh -c 'x="{}"; mv $x _.$(basename $x)' \; Commented May 9, 2023 at 18:45
7

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

4
  • 1
    this has the advantage of being able to use variable expansion eg. ${i:r}. Nice answer.
    – Salami
    Commented Aug 10, 2019 at 18:46
  • Yeah, you can pretty much put any expression in the for and perform a bulk operation. Commented Aug 13, 2019 at 17:20
  • 2
    This solution doen't work if the file name include white space.
    – nebulabox
    Commented Mar 19, 2021 at 5:31
  • 2
    Flaws: Bash pitfall number 1, unquoted variables. Commented Apr 15, 2021 at 21:25
7

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')' \;
2
  • Could be expensive, starting a new shell for each file. Commented Jun 12, 2021 at 20:33
  • 1
    this is crazy expensive but in my case with 117 files matched, ran in 1.9 sec and saved me a lot of tedium
    – sbeam
    Commented Feb 16, 2022 at 16:22
1

if you are trying to replace the file extension in different folders use the following:

find . -name "*.OLD_EXTENSION" -exec sh -c 'file="{}"; mv  "$file" "${file%.OLD_EXTENSION}.NEW_EXTENSION"'  \;
2
  • 1
    Note that since you inject the found pathname directly into the code of the inline sh -c script, this will fail if the pathname contains double quotes. It may also allow for code injection by crafting directory names or filenames that break out of the double quote, contain a ; character, and then any shell code, alternatively names that looks like variable expansions or command substitutions (e.g. $(reboot)). See also Is it possible to use `find -exec sh -c` safely?
    – Kusalananda
    Commented Oct 16, 2022 at 10:39
  • this answer was most useful to me as most answers only provided code for adding strings to the filename, not removing substrings.
    – javanoob
    Commented Mar 22 at 11:31
0

Remove endings *.sample:

ls *.sample | cut -d"." -f-2 | xargs -I{} cp {}.sample {}
1

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .