Industry guru Dave Taylor offers free tech support on a wide variety of technical and business topics, including HTML, Apple iPhone, online advertising, Cascading Style Sheets, Web design, management, Unix, Linux, search engine optimization, online dating, Mac OS X, shell script programming and Microsoft Windows.

Script to rename thousands of files and directories?

I need to rename thousands of files in MacOS, buried in subdirectories, changing " " to "_". I've found a ton of scripts using Google that purport to do this, but none of them actually work right. Can you please send me a script that does this? I also tried Automator, but it doesn't recurse into subdirectories.

Filenames may have multiple spaces (eg "Holidays/Christmas/Gifts & Stockings (A - G)/stocking.png"). Directory names may have spaces, too.


Dave's Answer:

This is an interesting little programming task and a good demonstration of how double substitution can be your friend even if it looks like a less than elegant solution. To accomplish this task, I've written a shell script on my Mac OS X box that first goes through a directory tree and converts all the directories themselves from "a b" to "a_b", then goes through a second time to fix up all the individual files.

The key idea here is that rather than fight the shell's desire to break up filenames at each space, I'll simply replace each space with a pattern that's extremely unlikely to occur ("___").

Here's the script:

#!/bin/sh

# first, we fix the directories

for name in $(find . -type d -print | sed 's/ /____/g')
do
  if [ $name != "." ] ; then
    oldname="$(echo $name | sed 's/____/ /g')"
    newname="$(echo $name | sed 's/____/_/g')"
    if [ "$oldname" != "$newname" ] ; then
      echo "renaming \"$oldname\" to $newname"
      mv "$oldname" "$newname"
    fi
  fi
done

echo ""
echo "done with directories, fixing individual files..."

# now let's fix the files therein using almost identical code

for name in $(find . -type f -print | sed 's/ /____/g')
do
  oldname="$(echo $name | sed 's/____/ /g')"
  newname="$(echo $name | sed 's/____/_/g')"
  if [ "$oldname" != "$newname" ] ; then
    echo "renaming \"$oldname\" to $newname"
    mv "$oldname" "$newname"
  fi
done

exit 0

Notice in the first loop, where we fix the directory names, that there's a special test for the "." match: even though there are no spaces in the directory name, it still makes the script get a bit confused about the substitution, so it's easily sidestepped.

Otherwise that's all there is to it: you should be able to use this script to easily "webify" your file and directory names, and it would be a straightforward extension to change the "newname" test to also either strip out or convert unwanted punctuation characters too, including "&" and "%", if they were possible names.



Help others find this article at Del.icio.us, Digg, Netscape, Reddit, and Stumble Upon    

Subscribe!

Never miss another useful Q&A article again! Subscribe to AskDaveTaylor with Google Reader.

Comments

oh boy i don't even know where to begin with this one :(

OK here goes:
1)DO NOT use: for baz in $(command) !!
The reason is that the for loop while have to wait until the whole find command is done and store the whole freaking list in memory while it's waiting.
A more effective loop is :

command | while read baz; do stuff;done

2)DO NOT try to fight the shell ! Word splitting is there for a very good reason and bash has all the tools to work with it.

3)Study the possible input. What happens when a filename contains a newline? or a hyphen ? that's right, your script fails ! The right delimiter to use is $'\0' since filenames can't contain it. This is another reason not to use for loops, but while read -d $'\0' along with find's -print0 .

4)limit the input. why should your script operate on files that don't even contain spaces ? huh ?
find -name '* *' solves that

5)avoid using sed for such a trivial task. Use parameter expansion instead, like foo=${foo// /_}.man bash, section parameter expansion

6)help echo

good luck !

Posted by: monsieur at May 17, 2006 8:12 AM

Great ideas for tweaks and modifications. Remember, though, that one of the key concepts for these sort of throwaway (e.g. "use once") scripts is that quick and dirty trumps elegant and optimized. If this were a script I'd use from a cron job every 15 minutes for the next six years I'd write it differently, but for a lot of people who work within the Unix or Linux environment, learning how to get it done and get on with their job is far, far more valuable than learning The One Best Way to do things. :-)

Posted by: Dave Taylor at May 17, 2006 4:42 PM

oh, quick and dirty ey ?

find . -type d -iname '* *' -exec sh -c 'mv "$1" "${1// /_}"' -- {} \;
find . -type f -iname '* *' -exec sh -c 'mv "$1" "${1// /_}"' -- {} \;

No need for a script then :P

Seriously, being correct is ALWAYS more important than being fast in the real world. If you learn the right way to write shell scripts, it WILL save time in the long run (whether it's a production machine or a desktop!). Trust me on this one.

Anyway, at least learn how word splitting works and why it's there, it'll pay back quickly, i promise ;)

Posted by: monsieur at May 19, 2006 7:01 AM

How about handling filenames like:

Hey! Santa's Movie.mpg


Rather filenames with "'" in them.

eg:
Linux's_kernel.doc

thanks in advance..!!

Posted by: evuraan at May 31, 2006 10:21 PM

meh, none of this appears to work right for:
/myfolder/First Spacer/somefolder/Second Spacer/

Quick and dirty should at least work fully the first time it's run not requiring multlple runs to make sure it gets everything.

Posted by: Someguy at March 22, 2007 1:57 PM

Hello Dave !

In this example (and similar in others) You use
the loop :

for name in $(find . -type f -print | sed 's/ /____/g')
do
...
done

You get here after the substitution of the
find-command a very long line. In older shells
I made the experience, that 1000 words (in Your
example files) and in newer Shells 10000 is the
limit of then number of words for substitute.
To prevent such a performance-dependent loop, You
better write it in the following manner:

find . -type f -print | sed 's/ /____/g' | while read name
do
...
done

keep on doing and nice greetings from germany !

Posted by: harald eck at December 31, 2007 8:53 AM

hi dave, i have been trying to write a script for mac OSX. i need to rename files eg :
116786_mcc_V1.jpeg to change to 116786.jpeg

can a script be written for this?

thanks
akbar

Posted by: akbar at October 9, 2009 2:37 AM

hi dave
i have been trying write a script for mac OSX
i need to rename a file eg
116786_mcc_v1.jpeg to 116786.jpeg

Can this script be written?

thanks
akbar

Posted by: akbar at October 9, 2009 2:39 AM

That's quite easy, Akbar. Look at the "for" loop, then figure out how to transform a current filename into a new filename. Something like this:

for name in *jpeg
do
newfilename = $(echo $name | transformation)
mv $name $newfilename
done

I'll let you figure out the transformation in the above. :-)

Posted by: Dave Taylor at October 9, 2009 9:56 AM

I have a lot to say, but ...
Starbucks coffee cup I have a lot to say, and questions of my own for that matter, but most of all I'd like to say thank you for all your efforts on this Web site by buying you a chai!

I do have a comment, now that you mention it!











Remember personal info?


Please note that I will never send you any unsolicited commercial email. Ever.

While I'm at it, please note that by submitting a question or comment you're agreeing to my terms of service, which are: you relinquish any subsequent rights of ownership to your material by submitting it on this site.









Uniblue: Free Virus Scan

Follow me on Twitter @DaveTaylor

Search
Find just the answers you seek from among our 2300+ free tech support articles by using our Lijit search engine.


Help!





Subscribe to
Ask Dave Taylor!

Add to Google Reader
Add to My Yahoo!
Subscribe in NewsGator Online

RDF   XML

Free Updates!
Sign up and get free weekly updates and special offers on books, seminars, workshops and more.


Recent Entries
Book Links
© 2002 - 2009 by Dave Taylor. All Rights Reserved.

Note: This web site is for the purpose of disseminating information for educational purposes, free of charge, for the benefit of all visitors. We take great care to provide quality information. However, we do not guarantee, and accept no legal liability whatsoever arising from or connected to, the accuracy, reliability, currency or completeness of any material contained on this web site or on any linked site.

[whiteboard marker tray]
"Ask Dave Taylor®" is a registered trademark of Intuitive Systems, LLC.