Perl Rename Script – Troubleshooting Issues

findrename

I tried running the following command:

find . -type f -exec rename -v 's/ /_/g' {} \;

, to rename all files recursively with spaces in their names with underscores. The problem is that it works sometimes and sometimes it doesn't. When it doesn't work I get the following error:

Can't rename ./dir/subdir/file name with spaces ./dir/subdir/file_name_with_spaces: No such file or directory

Anything I could be doing wrong ?

Best Answer

What is happening is that you are trying to run rename on a file in or below a directory with spaces in its name. The rename utility is a bit simplistic by default and applies the given bit of Perl code to the complete argument, i.e. to the full pathname, not just to the filename portion of the pathname.

Fortunately, it's easy to restrict the action to the filename portion of the pathname:

find . -type f -execdir rename -v 'tr/ /_/' -- {} \;

The -execdir predicate is non-standard, but commonly implemented. With -execdir in place of -exec, this executes the command with the directory where the file was found as its current directory. This way the rename utility never sees the names of the file's parent directories.

I've used -- here to protect against the event that we're trying to rename a file with a name starting with a dash (see What does "--" (double-dash) mean?). This would not be an issue with GNU find as the filenames will be prefixed with ./, but this does not happen on at least some implementations of find on BSD systems.

Another option is to use the rename utility's -d, --filename, --nopath, or --nofullpath option (these are all the same):

find . -type f -exec rename -d -v 'tr/ /_/' {} \;

This stops the utility from considering anything other than the filename portion of any given pathname argument.

I've taken the liberty to change the Perl expression from a s/// command to a tr/// command, since the transformation that we're performing is a simple transliteration and not a generic substitution.

You may also want to only run rename only on filenames that contain spaces. Pick these out with a -name '* *' test before the -exec or -execdir.