Recursively rename files (change extension) in Linux

How do I rename all files in a directory, recursively, changing one file extension to another, for thousands of files in thousands of subfolders? I see a lot of commands that do almost what I want, but not quite.

find . -name "*.andnav" -exec rename .andnav .tile {} \;
syntax error at (eval 1) line 1, near "."

...

rename -nv 's/\.andnav$/\.tile/i' *.andnav
0.png.andnav renamed as 0.png.tile

Figured it out

find . -name "*.andnav" -exec rename -v 's/\.andnav$/\.tile/i' {} \;
./0/0.png.andnav renamed as ./0/0.png.tile
./0/1.png.andnav renamed as ./0/1.png.tile
./1/0.png.andnav renamed as ./1/0.png.tile
./1/1.png.andnav renamed as ./1/1.png.tile

of course remove the -v when actually doing it, or it will waste time displaying all the files


With zsh:

autoload zmv
zmv -n '(**/)(*).andnav' '$1$2.tile'

Remove the -n to actually perform the renaming.


Something like:

find . -name '*.andnav' -exec sh -c 'mv "$0" "${0%.andnav}.tile"' {} \;

Explanation

The above starts walking the directory tree starting at the current working directory (.). Every time a file name matches the pattern *.andnav (e.g., foo.andnav) the following command is executed:

sh -c 'mv "$0" "${0%.andnav}.tile"' foo.andnav

Where $0 is foo.andnav and ${0%.andnav}.tile replaces the .andnav suffix with .tile so basically:

mv foo.andnav foo.tile

I found this method is easier and easier to read:

find . -name "*.andnav" | rename "s/\.andnav$/.tile/"

At least on Ubuntu derivations rename takes a list of files from STDIN if none are on the command line. And this can be tested easily with:

find . -name "*.andnav" | rename -vn "s/\.andnav$/.tile/"

until you get it right.