How do I get this find and rename command to work with subdirectories?

5,286

Here's what happens:

  • find finds the matching directory ./Test 02.
  • find executes the rename command on that directory.
  • rename renames Test 02 to Test_02.
  • find tries to descend into the directory Test 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.

Share:
5,286

Related videos on Youtube

ADAM
Author by

ADAM

Updated on September 18, 2022

Comments

  • ADAM
    ADAM almost 2 years

    Possible Duplicate:
    Recursive rename files and directories

    I 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:

    enter image description here

    ... 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:

    enter image description here

    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:

    enter image description here

    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
      Admin almost 12 years
      The 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
      Admin almost 12 years
      @jasonwryan No, there's an added wrinkle here: when renaming directories, you need to go depth first.
  • ADAM
    ADAM almost 12 years
    Thank 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'
    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.