Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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.

share|improve this question
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. –  Patrick Aug 26 '12 at 3:40
@don_crissti: The solution offered in the link you provided worked for me, so please close this as a duplicate of that. The command that worked for me was find /tmp/ -depth -name "* *" -execdir rename 's/ /_/g' "{}" \; –  Dave M G Aug 26 '12 at 5:17
@jasonwryan No, there's an added wrinkle here: when renaming directories, you need to go depth first. –  Gilles Aug 26 '12 at 21:47
@Gilles thanks: nice answer. –  jasonwryan Aug 26 '12 at 23:22
add comment

marked as duplicate by jasonwryan, Renan, rush, Kevin, warl0ck Sep 8 '12 at 1:20

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

2 Answers

up vote 3 down vote accepted

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|improve this answer
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? –  Dave M G Aug 27 '12 at 3:54
@DaveMG -depth and -execdir together make it work. -name "* *" avoids running rename` needlessly, so it's a useful optimization. –  Gilles Aug 27 '12 at 12:14
add comment

You should use the following code to avoid space in filename :

IFS=$(echo -en "\n\b")
find $mymusicdir -name "* *" -execdir rename 's/ /_/g' "{}" \;

At first change $mymusicdir to your path, then run it.

share|improve this answer
This only fixes half the problem. –  Gilles Aug 26 '12 at 21:46
Do i understand truely your problem? You want to rename every file that has space and convert space to "_" ? if true above code is true. –  Mohsen Pahlevanzadeh Aug 26 '12 at 21:55
add comment

Not the answer you're looking for? Browse other questions tagged or ask your own question.