How do I get this find and rename command to work with subdirectories?
Here's what happens:
-
find
finds the matching directory./Test 02
. -
find
executes therename
command on that directory. -
rename
renamesTest 02
toTest_02
. -
find
tries to descend into the directoryTest 02
. But it no longer exists.
The easiest way of solving this problem is to tell find
to work the other way round: first look for matches inside directory, then check if the directory itself matches. This is what the -depth
option does.
If you only add -depth
, you'll run into another problem, which is that when find
reaches Test 01/Test A
, it invokes rename 'y/\ /\_/' 'Test 01/Test A'
, which tries to rename that directory to Test_01/Test_A
. This will fail since there is no directory called Test_01
. An easy fix is to use the -execdir
option, which invokes rename
inside the Test 01
directory and passes Test A
as the argument. You can speed things up by passing multiple arguments to rename
in one batch, by using +
instead of ;
to terminate the -execdir
command.
find -depth -name '* *' -type d -execdir rename 'y/ /_/' {} +
Alternatively, use this short but cryptic zsh command:
autoload zmv
zmv -Qw '**/*(/D)' '$1${2// /_}'
The zmv
function renames files according to patterns. The source pattern is **/*
, which matches all files (*
) in all subdirectories recursively (**/
). The glob qualifiers (activated by the -Q
option) indicate that only directories are matched (/
) and that dot files are included (D
). The -w
option creates a backreference for each wildcard in the source pattern. The replacement starts with $1
which designates the match for the first wildcard (for **
, this includes a final /
), and is followed by ${2// /_}
, which is $2
(what the *
matched) modified to replace all space characters by _
. Add the -v
option to see what the command does, you'll notice that it traverses depth first like find -depth
.
Related videos on Youtube
ADAM
Updated on September 18, 2022Comments
-
ADAM almost 2 years
Possible Duplicate:
Recursive rename files and directoriesI have a large directory of music files that is often changing as files and directories come and go. My preference is to make sure that file and directory names don't have spaces in them, so I replace them all with underscores.
I go into the main directory and run this command:
$ find -type d -exec rename 'y/\ /\_/' {} \;
The problem is that when there are subdirectories, this command seems to get lost and it will return errors. So if I have the following directory structure:
... and if I run the command, I get the following errors:
$ find -type d -exec rename 'y/\ /\_/' {} \; find: `./Test 02': No such file or directory find: `./Test 01': No such file or directory find: `./Test 03': No such file or directory
And then the result is that my directory structure looks like this. Note the subdirectories still have spaces in them:
If I run the command again, I get these errors, even though it seems like maybe it renamed the directories in question:
$ find -type d -exec rename 'y/\ /\_/' {} \; find: `./Test_01/Test A': No such file or directory find: `./Test_01/Test C': No such file or directory find: `./Test_01/Test B': No such file or directory
Finally, I run the command yet one more time, and I get no errors, and I have all directories and subdirectories named the way I want:
Obviously this requires running the command even more times when I have multiple subdirectories, which can get tedious.
How can I make it so that this command only has to be run once and it will rename all directories and subdirectories in one go?
My ultimate aim is to include this in a Bash script so that I can run it along with other similar housekeeping commands, so I need it to not return errors or need more input from me. Also, I'm running Ubuntu 12.04 if that makes a difference.
-
Admin almost 12 yearsThe problem you're running into is that you're renaming the directory and
find
isn't aware that you've done so. So when it goes to dive into the directory and look for children, the directory isn't there any more. Unfortunately I cannot think of a simple way of fixing it (that wont break on weird filenames). A short script could do it, but not what I'd call simple. -
Admin almost 12 years@jasonwryan No, there's an added wrinkle here: when renaming directories, you need to go depth first.
-
-
ADAM almost 12 yearsThank you for this answer. This command seems to find files and directories and rename them all in one go:
find /tmp/ -depth -name "* *" -execdir rename 's/ /_/g' "{}" \;
Is there a reason I shouldn't use this command and yours instead? -
Gilles 'SO- stop being evil' almost 12 years@DaveMG
-depth
and-execdir
together make it work.-name "* *" avoids running
rename` needlessly, so it's a useful optimization.