How can I recursively change the case of files and folders under bash

I have some files and folders that are all in uppercase that I want to rename to their lowercase equivalent. What would be the best way to go about doing this in bash on a Linux system?

As an example I might have:

.
|-- FOLDER0
|   |-- SUBFOLDERA
|   `-- SUBFOLDERB
`-- FOLDER1
    `-- AFILE.TXT

And I want to convert it to:

.
|-- folder0
|   |-- subfoldera
|   `-- subfolderb
`-- folder1
    `-- afile.txt

I can probably write a depth first recursive script to do this (depth first to ensure that files and subfolders are renamed before their parent folder), but I was wondering if there is a better way. rename might be useful, but it doesn't seem to support recursion.


Solution 1:

find . -depth -print0 | xargs -0 rename -n '$_ = lc $_'

Take out the -n flag once you're sure that it's doing what you want.

Solution 2:

This is more of a comment and a meta observation on the way this forum works, but I feel pretty strongly that it should be stated more loudly than if I just left a comment in rudedog's answer. This is also a comment directed more at people who may have a problem similar to David Dean's question, but not identical.

It is easy to do a huge amount of damage with recursive operations that rename files. Let me say that again because it is very important. You can destroy a huge amount of data very very very quickly with recursive operations that rename files. You can quickly render a system totally completely unusable. You can delete 99% of your data very quickly. Such actions should be done very carefully and with a lot of thought, backups, and test runs.

I am very wary of an answer to this specific question that relies on several specific implied assumptions:

  1. The version of "rename" that I'm using is the same version you're using and that they both have the safety features that I'm accustomed to. "rename" is not some standard, normal utility in unix, as far as I know. It seems to be some clever script that exists in some distribution.
  2. "rename" as described has some safety feature that prevents overwriting a file.
  3. either the files being munged have unambiguous squashed-case results or it isn't a big deal if the resulting subdirectory has some upper-case files because the squash-case results had a collision and the name-changing mechanism does nothing if the target name already exists.

I'm perfectly comfortable doing things that are shortcuts. I try to understand, however, that something is a shortcut and what the limitations of such a shortcut are, so that I know when I need to run the always-correct program or the 10,000 times faster but fails in some edge cases shortcut.

In short -- the above script using rename is 100% reasonable in 90% of cases. The 100% correct in 100% of cases solution is actually a little bit complex and requires some smarts in dealing with namespace collisions.

But, as I said before, be very careful with recursive file operations, especially if they only munge metadata such as permissions or filenames. They're fast and difficult to reverse without going to backups...

Here's an example of how to write the "rename" command in bash or ksh or some other modernish shell that has "typeset". Let's call this script /tmp/squash

#!/usr/local/bin/pdksh
# use some shell that supports typeset such as ksh, pdksh, or bash
# call this script from "xargs -0" and feed it with "find -print0"

typeset -l targetname || { echo "This shell doesn't support typeset!" >&2 ; exit 1 ; }
for file
do
  count=""
  target="$file"
  targetpath="${target%/*}/"
  targetname="${target##*/}"
  if
    [[ "$file" == "$targetpath$targetname" ]]  || [[ "$file" == . ]]
  then
    :
  else
    while
      test -e "$targetpath/$targetname""$count"
    do
      count=$((${count:-0}+1))
    done
    echo "renaming $file to $targetpath$targetname$count" >&2
    mv -n  "$file" "$targetpath$targetname$count"
  fi
done

Now, you can run the following two commands and have your files all squashed and not have any file overwritten.

find /path/to/files -type f -print0 | xargs -0 /tmp/squash
find /path/to/files -type d -depth -print0 | xargs -0 /tmp/squash

And I'm making the assumptions that the directories in question won't have stuff put into them or stuff renamed while I'm running the program.