views:

7607

answers:

10

I have to rename a complete folder tree recursively so that no uppercase letter appears anywhere (it's C++ sourcecode, but that shouldn't matter). Bonus points for ignoring CVS and SVN control files/folders. Preferred way would be a shell script, since shell should be available at any Linux box.

There were some valid arguments about details of the file renaming.

  1. I think files with same lowercase names should be overwritten, it's the user's problem. When checked out on a case-ignoring file system would overwrite the first one with the latter, too.

  2. I would consider A-Z characters and transform them to a-z, everything else is just calling for problems (at least with source code).

  3. The script would be needed to run a build on a Linux system, so I think changes to CVS or SVN control files should be omitted. After all, it's just a scratch checkout. Maybe an "export" is more appropriate.

+3  A: 

Here's my suboptimal solution, using a bash Shell script:

#!/bin/bash
# first, rename all folders
for f in `find . -depth ! -name CVS -type d`; do
   g=`dirname "$f"`/`basename "$f" | tr '[A-Z]' '[a-z]'`
   if [ "xxx$f" != "xxx$g" ]; then
      echo "Renaming folder $f"
      mv -f "$f" "$g"
   fi
done

# now, rename all files
for f in `find . ! -type d`; do
   g=`dirname "$f"`/`basename "$f" | tr '[A-Z]' '[a-z]'`
   if [ "xxx$f" != "xxx$g" ]; then
      echo "Renaming file $f"
      mv -f "$f" "$g"
   fi
done

Edit: I made some modifications based on the suggestions so far. Now folders are all renamed correctly, mv isn't asking questions when permissions don't match, and CVS folders are not renamed (CVS control files inside that folder are still renamed, unfortunately).

Edit: Since "find -depth" and "find | sort -r" both return the folder list in a usable order for renaming, I prefered using "-depth" for searching folders.

vividos
Your first find does not work. Try "mkdir -p A/B/C" and then running your script.
ΤΖΩΤΖΙΟΥ
+3  A: 
for f in `find`; do mv -v $f `echo $f | tr '[A-Z]' '[a-z]'`; done
Swaroop C H
Please do a "mkdir -p A/B/C" before running your script.
ΤΖΩΤΖΙΟΥ
+3  A: 

Using Larry Wall's filename fixer

$op = shift or die $help;
chomp(@ARGV = <STDIN>) unless @ARGV;
for (@ARGV) {
    $was = $_;
    eval $op;
    die $@ if $@;
    rename($was,$_) unless $was eq $_;
}

it's as simple as

find | fix 'tr/A-Z/a-z/'

(where fix is of course the script above)

agnul
+2  A: 

This is a small shell script that does what you requested:

root_directory="${1?-please specify parent directory}"
do_it () {
    awk '{ lc= tolower($0); if (lc != $0) print "mv \""  $0 "\" \"" lc "\"" }' | sh
}
# first the folders
find "$root_directory" -depth -type d | do_it
find "$root_directory" ! -type d | do_it

Note the -depth action in the first find.

ΤΖΩΤΖΙΟΥ
+2  A: 

The previously posted will work perfectly out of the box or with a few adjustments for simple cases, but there are some situations you might want to take into account before running the batch rename:

  1. What should happen if you have two or more names at the same level in the path hierarchy which differ only by case, such as ABCdef, abcDEF and aBcDeF? Should the rename script abort or just warn and continue?

  2. How do you define lower case for non US-ASCII names? If such names might be present, should one check and exclude pass be performed first?

  3. If you are running a rename operation on CVS or SVN working copies, you might corrupt the working copy if you change the case on file or directory names. Should the script also find and adjust internal administrative files such as .svn/entries or CVS/Entries?

Mihai Limbășan
+2  A: 

The original question asked for ignoring SVN and CVS directories, which can be done by adding -prune to the find command. E.g to ignore CVS:

find . -name CVS -prune -o -exec mv '{}' `echo {} | tr '[A-Z]' '[a-z]'` \; -print

[edit] I tried this out, and embedding the lower-case translation inside the find didn't work for reasons I don't actually understand. So, amend this to:

$> cat > tolower
#!/bin/bash
mv $1 `echo $1 | tr '[:upper:]' '[:lower:]'`
^D
$> chmod u+x tolower 
$> find . -name CVS -prune -o -exec tolower '{}'  \;

Ian

Ian Dickinson
A: 
( find YOURDIR -type d | sort -r;
  find yourdir -type f ) |
grep -v /CVS | grep -v /SVN |
while read f; do mv -v $f `echo $f | tr '[A-Z]' '[a-z]'`; done

First rename the directories bottom up sort -r (where -depth is not available), then the files. Then grep -v /CVS instead of find ...-prune because it's simpler. For large directories, for f in ... can overflow some shell buffers. Use find ... | while read to avoid that.

And yes, this will clobber files which differ only in case...

pklausner
First: `find YOURDIR -type d | sort -r` is too much trouble. You want `find YOURDIR -depth -type d`. Second, the `find -type f` MUST run after the directories have been renamed.
ΤΖΩΤΖΙΟΥ
+12  A: 

A concise version using "rename" command.

find my_root_dir -depth -exec rename 's/(.*)\/([^\/]*)/$1\/\L$2/' {} \;

This avoids problems with directories being renamed before files and trying to move files into non-existing directories (e.g. "A/A" into "a/a").

Or, a more verbose version without using "rename".

for SRC in `find my_root_dir -depth`
do
    DST=`dirname "${SRC}"`/`basename "${SRC}" | tr '[A-Z]' '[a-z]'`
    if [ "${SRC}" != "${DST}" ]
    then
        [ ! -e "${DST}" ] && mv -T "${SRC}" "${DST}" || echo "${SRC} was not renamed"
    fi
done

P. S.

The latter allows more flexibility with move command (e. g. "svn mv").

Alex B
using rename can be done this way as wellrename 'y/A-Z/a-z/' *
Tzury Bar Yochay
+1  A: 

Most of the answers above are dangerous because they do not deal with names containing odd characters. Your safest bet for this kind of thing is to use find's -print0 option, which will terminate filenames with ascii NUL instead of \n. Here I submit this script, which only alter files and not directory names so as not to confuse find.

find .  -type f -print0 | xargs -0n 1 bash -c \
's=$(dirname "$0")/$(basename "$0"); 
d=$(dirname "$0")/$(basename "$0"|tr "[A-Z]" "[a-z]"); mv -f "$s" "$d"'

I tested it and it works with filenames containing spaces, all kinds of quotes, etc. This is important because if you run, as root, one of those other script on a tree that includes the file created by:

touch \;\ echo\ hacker::0:0:hacker:\$\'\057\'root:\$\'\057\'bin\$\'\057\'bash

... well guess what ...

niXar
A: 

I have a huge directory tree from somebodies webserver that has multiple directories and files with names that only differ by case (Images and images, banner.jpg and banner.JPG).

In most cases the duplicated files and directories are unique in their content, so I need a script that will not overwrite but rename if the conversion to lowercase results in a duplicate.. Any ideas?