Recursively rename files (change extension) in Linux

24

15

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

endolith

Posted 2010-11-20T16:38:30.323

Reputation: 6 626

1Do you have Debian rename or Red Hat rename? – Ignacio Vazquez-Abrams – 2010-11-20T16:42:51.953

@Ignacio: Ubuntu = Debian? – endolith – 2010-11-20T16:44:19.740

Yes, Ubuntu is a Debian derivative. – Ignacio Vazquez-Abrams – 2010-11-20T20:39:36.257

Answers

27

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

endolith

Posted 2010-11-20T16:38:30.323

Reputation: 6 626

17

With zsh:

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

Remove the -n to actually perform the renaming.

Gilles 'SO- stop being evil'

Posted 2010-11-20T16:38:30.323

Reputation: 58 319

2Fantastic answer! – Chris Knadler – 2013-04-17T17:33:18.393

15

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

cYrus

Posted 2010-11-20T16:38:30.323

Reputation: 18 102

what is this doing? – user1028270 – 2020-01-21T08:30:00.837

@user1028270 supposedly what the OP asked. – cYrus – 2020-01-21T14:06:33.900

I just meant an explanation of the syntax and behaviour- people (like me!) might not be familiar with bash parameter expansion – user1028270 – 2020-01-21T14:12:38.323

@user1028270 Got it!, See the update. – cYrus – 2020-01-21T14:47:20.847

this one worked best for me – cwd – 2011-07-17T21:04:12.073

4

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.

Bernd Wechner

Posted 2010-11-20T16:38:30.323

Reputation: 141

1

Rename files and directories with find -execdir | rename

If you are going to rename both files and directories not simply with a suffix, then this is a good pattern:

PATH="$(echo "$PATH" | sed -E 's/(^|:)[^\/][^:]*//g')" \
  find . -depth -execdir rename 's/findme/replaceme/' '{}' \;

The awesome -execdir option does a cd into the directory before executing the rename command, unlike -exec.

-depth ensure that the renaming happens first on children, and then on parents, to prevent potential problems with missing parent directories.

-execdir is required because rename does not play well with non-basename input paths, e.g. the following fails:

rename 's/findme/replaceme/g' acc/acc

The PATH hacking is required because -execdir has one very annoying drawback: find is extremely opinionated and refuses to do anything with -execdir if you have any relative paths in your PATH environment variable, e.g. ./node_modules/.bin, failing with:

find: The relative path ‘./node_modules/.bin’ is included in the PATH environment variable, which is insecure in combination with the -execdir action of find. Please remove that entry from $PATH

See also: https://askubuntu.com/questions/621132/why-using-the-execdir-action-is-insecure-for-directory-which-is-in-the-path/1109378#1109378

-execdir is a GNU find extension to POSIX. rename is Perl based and comes from the rename package. Tested in Ubuntu 18.10.

Rename lookahead workaround

If your input paths don't come from find, or if you've had enough of the relative path annoyance, we can use some Perl lookahead to safely rename directories as in:

git ls-files | sort -r | xargs rename 's/findme(?!.*\/)\/?$/replaceme/g' '{}'

I haven't found a convenient analogue for -execdir with xargs: Xargs: change working directory to file path before executing?

The sort -r is required to ensure that files come after their respective directories, since longer paths come after shorter ones with the same prefix.

Ciro Santilli 新疆改造中心法轮功六四事件

Posted 2010-11-20T16:38:30.323

Reputation: 5 621

0

Using find recursively:

  • In the following examples, “foo” is replaced with “bar”.
  • For greater control over the search parameter, add -name foo to rename only certain instances e.g. find . -depth -name foo -type d -execdir rename "s/foo/bar/" \{} \+. Using this also performs a faster search.
  • The global g flag replaces all occurrences within a string. Without it, only the first occurrence will be replaced.
  • The case-insensitive i flag performs case-insensitive substitution. Use with -iname instead of -name for case-insensitive searches.
  • Wildcards * are utilized as necessary per your specific operation.
  • Verbose -v trigger included to enable progress to be viewed. If not included, command is complete when shell returns to the prompt.
  • No action -n trigger simulates the process; remove when satisfied with printed results.

Example 1:

find /target/path/ -depth -type d -name "foo*" -execdir rename -v -n "s/foo/bar/g" \{} \+

Example 2:

find -execdir rename "s/foo/bar/" \{} \;

Rename all files/directories:

find ./ -depth -execdir rename -v -n "s/foo/bar/gi" \{} \+

Rename all files only:

find ./ -type f -execdir rename -v -n "s/foo/bar/gi" \{} \;

Rename all files only with a particular extension:

find ./ -type f -iname "*foo*.txt" -execdir rename -v -n "s/foo/bar/gi" \{} \;

Rename all directories only:

find ./ -depth -type d -execdir rename -v -n "s/foo/bar/gi" \{} \+

Rename whole words within a string:

find ./ -depth -name "* foo *" -execdir rename -v -n "s/foo/bar/g" \{} \+

or;

find ./ -depth -execdir rename -v -n "s/* foo */ bar /g" \{} \+

As per OP:

find ./ -type f -name "*.andnav" -execdir rename -v -n "s/andnav/tile/" \{} \;


Useful article on the find command: https://www.tecmint.com/35-practical-examples-of-linux-find-command/comment-page-4/#comments

Reactive

Posted 2010-11-20T16:38:30.323

Reputation: 1