Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I'm IT at my small firm; and, despite my dire warnings, everyone puts files on the server with awful names, including leading & trailing spaces, bad characters (including \ ; / + . < > - etc!)

They do this by accessing the (FreeBSD/FreeNAS) server via AFP on Macs, so no part of the system complains.

Is there a script I can use to go through an entire directory tree and fix bad filenames?

Basically replace all spaces & bad ASCII with _ ... and if a file already exists, just slap a _2 or something on the end.

I don't suppose there's a way to get the system to enforce good filenaming conventions, is there?


share|improve this question
You can't tell netatalk or whatnot to do this? – Ignacio Vazquez-Abrams Aug 20 '12 at 1:52
up vote 3 down vote accepted

I'd use bash and find. I'm sure there's a simpler option but here's what I came up with:

  1. This can deal with file names containing "/" (find will give a warning, ignore it), but it will only work on files in the current directory (no subdirectories). I couldn't figure out how to tell bash or find to differentiate between a "/" in a file name and a "/" that is part of the path.

    for i in $(find . -maxdepth 1 -type f  -name "*[\:\;><\@\$\#\&\(\)\?\\\/\%]*" | sed 's/\.\///'); do mv "$i" ${i//[\;><\@\$\#\&\(\)\?\\\/\%]/_}; done
  2. This one cannot deal with file names containing "/" but it will work on all files in the current directory and its subdirectories:

    for i in $(find . -type f  -name "*[\:\;\>\<\@\$\#\&\(\)\?\\\%]*"); do mv "$i" ${i//[\;><\@\$\#\&\(\)\?\\\%]/_}; done

Make sure to test these before running. They worked fine in the few tests I ran, but I was not exhaustive. Also bear in mind that I am on a linux system. The particular implementation of find, and perhaps bash, may differ on yours.

EDIT: Changing the mv $i command to `mv -i $i‘ will cause mv to prompt you before overwriting an existing file.

EDIT2: To deal with filenames with spaces, you can change the bash IFS (Input Field Separator) variable like so (adapted from here):

SAVEIFS=$IFS; IFS=$(echo -en "\n\b"); for i in $(find . -type f  -name "*[\:\;\>\<\@\$\#\&\(\)\?\\\%\ ]*"); do mv "$i" ${i//[\;><\@\$\#\&\(\)\?\\\%\ ]/_}; done; IFS=$SAVEIFS

I also modified the regular expression to match/replace spaces with underscores. The SAVEIFS bit just returns the IFS variable to its original configuration.


for i in $(command); do something $i; done

This is a generic bash loop. It will go through a command's output, sequentially setting variable $i to each of the values returned by command, and will do something to it.

find . -maxdepth 1 -type f  -name "*[\:\;><\@\$\#\&\(\)\?\\\/\%]*" '

Find all files in the current directory whose name contains one of the following characters: :;><@$#&()\/%. To add more, just escape them with "\" (eg "\¿") and add them to the list within the brackets ([ ]). Probably, not all these characters need to be escaped, but I can never remember which are special variables in which environment so I escape everything, just in case.

sed 's/\.\///

Remove the current directory from find's output, print "foo" instead of "./foo".

mv "$i" ${i//[\;><\@\$\#\&\(\)\?\\\/\%]/_}

Every time this little scipt loops, $i will be the name of a badly named file. This command will move (rename) that file changing all unwanted characters to "_". Look up bash substitution for more information.

share|improve this answer
This looks good, but (a) I had to change the "..." in the find expression to '...'; and (b) when I try to run the loop, it says do: command not found. Maybe a FreeBSD limitation? Or csh? – Ze'ev Aug 20 '12 at 19:23
It was csh ... works better after I exec bash! – Ze'ev Aug 20 '12 at 19:54
@Ze'ev Yup, you definitely need bash for this. The substitution command is also bash specific. You can try and automate the file renaming process by introducing a variable that will be incremented every time you have 2 files with the same name. – terdon Aug 20 '12 at 20:06
Using bash is fine; almost working. But there is a problem with filenames with a space in them. If I have a file called ./a/b/c/xxx$ 111, then the find command returns ./a/b/c/xxx$ and 111 as two separate items in $i, and then the mv fails. – Ze'ev Aug 20 '12 at 20:09
@Ze'ev I played around with that for this answer and couldn't get it to work. The problem was making find differentiate between slashes in a filename and actual paths. I also had some other complication I can't remember. If you can make it work, more power to you :) In any case, the updated answer should work. – terdon Aug 20 '12 at 20:27

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

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