How to rename all folders and files to lowercase o

2019-01-03 20:36发布

问题:

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.

回答1:

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").



回答2:

smaller still i quite like

rename 'y/A-Z/a-z/' *

On case insensitive filesystems such as OS X's HFS+, you will want to add the -f flag

rename -f 'y/A-Z/a-z/' *


回答3:

for f in `find`; do mv -v "$f" "`echo $f | tr '[A-Z]' '[a-z]'`"; done


回答4:

Just simply try following if you don't need to care about efficiency.

zip -r foo.zip foo/*
unzip -LL foo.zip


回答5:

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 ...



回答6:

This works if you already have or set up rename command (e.g. through brew install in Mac):

rename --lower-case --force somedir/*


回答7:

Man you guys like to over complicate things..

rename 'y/A-Z/a-z/' *



回答8:

This works on CentOS/Redhat or other distributions without the rename Perl script:

for i in $( ls | grep [A-Z] ); do mv -i "$i" "`echo $i | tr 'A-Z' 'a-z'`"; done

Source: https://linuxconfig.org/rename-all-files-from-uppercase-to-lowercase-characters

(in some distros the default rename command comes from util-linux, and that is a different, incompatible tool)



回答9:

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)



回答10:

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



回答11:

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.



回答12:

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.



回答13:

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?



回答14:

With MacOS,

Install the rename package,

brew install rename

Use,

find . -iname "*.py" -type f | xargs -I% rename -c -f  "%"                       

This command find all the files with a *.py extension and converts the filenames to lower case.

`f` - forces a rename

For example,

$ find . -iname "*.py" -type f
./sample/Sample_File.py
./sample_file.py
$ find . -iname "*.py" -type f | xargs -I% rename -c -f  "%"
$ find . -iname "*.py" -type f
./sample/sample_file.py
./sample_file.py


回答15:

Not portable, Zsh only, but pretty concise.

First, make sure zmv is loaded.

autoload -U zmv

Also, make sure extendedglob is on:

setopt extendedglob

Then use:

zmv '(**/)(*)~CVS~**/CVS' '${1}${(L)2}'

To recursively lowercase files and directories where the name is not CVS.



回答16:

In OSX, mv -f shows "same file" error, so I rename twice.

for i in `find . -name "*" -type f |grep -e "[A-Z]"`; do j=`echo $i | tr '[A-Z]' '[a-z]' | sed s/\-1$//`; mv $i $i-1; mv $i-1 $j; done


回答17:

I would reach for python in this situation, to avoid optimistically assuming paths without spaces or slashes. I've also found that python2 tends to be installed in more places than rename.

#!/usr/bin/env python2
import sys, os

def rename_dir(directory):
  print('DEBUG: rename('+directory+')')
  # rename current directory if needed
  os.rename(directory, directory.lower())
  directory = directory.lower()

  # rename children
  for fn in os.listdir(directory):
    path = os.path.join(directory, fn)
    os.rename(path, path.lower())
    path = path.lower()

    # rename children within, if this child is a directory
    if os.path.isdir(path):
        rename_dir(path)

# run program, using the first argument passed to this python script as the name of the folder
rename_dir(sys.argv[1])


回答18:

for f in `find -depth`; do mv ${f} ${f,,} ; done

find -depth prints each file and directory, with a directory's contents printed before the directory itself. ${f,,} lowercases the file name.



回答19:

( 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...



回答20:

Slugify Rename (regex)

Not exactly what the OP asked for, but what I was hoping to find on this page:

A "slugify" version for renaming files so they are similar to URLs
(i.e. only include alphanumeric, dots, and dashes):

rename "s/[^a-zA-Z0-9\.]+/-/g" filename


回答21:

I needed to do this on a cygwin setup on Windows 7 and found that I got syntax errors with the suggestions from above that I tried (though I may have missed a working option) however this solution from straight from ubutu forums worked out of the can :-)

ls | while read upName; do loName=`echo "${upName}" | tr '[:upper:]' '[:lower:]'`; mv "$upName" "$loName"; done

( nb I had previously replace whitespace with underscores using

for f in *\ *; do mv "$f" "${f// /_}"; done


回答22:

If you use Arch Linux, you can install rename package from AUR that provides renamexm command as /usr/bin/renamexm executable and a its manual page along with it.

It is really powerful tool to quickly rename files and directories.

Convert to lowercase

rename -l Developers.mp3 # or --lowcase

Convert to UPPER case

rename -u developers.mp3 # or --upcase, long option

Other options

-R --recursive # directory and its children

-t --test # dry run, output but don't rename

-o --owner # change file owner as well to user specified

-v --verbose # output what file is renamed and its new name

-s/str/str2 # substite string on pattern

--yes # Confirm all actions

You can fetch the sample Developers.mp3 file from here, if needed ;)



回答23:

Nobody suggesting typeset?

typeset -l new        # always lowercase
find $topPoint |      # not using xargs to make this more readable
  while read old
  do mv "$old" "$new" # quotes for those annoying embedded spaces
  done

On windows emulations like git bash this may fail because windows isn't case sensitive under the hood. For those, add a step that mv's the file to another name first, like "$old.tmp", then to $new.



回答24:

Lengthy But "Works With No Surprises & No Installations"

This script handles filenames with spaces, quotes, other unusual characters and Unicode, works on case insensitive filesystems and most Unix-y environments that have bash and awk installed (i.e. almost all). It also reports collisions if any (leaving the filename in uppercase) and of course renames both files & directories and works recursively. Finally it's highly adaptable: you can tweak the find command to target the files/dirs you wish and you can tweak awk to do other name manipulations. Note that by "handles Unicode" I mean that it will indeed convert their case (not ignore them like answers that use tr).

# adapt the following command _IF_ you want to deal with specific files/dirs
find . -depth -mindepth 1 -exec bash -c '
  for file do
    # adapt the awk command if you wish to rename to something other than lowercase
    newname=$(dirname "$file")/$(basename "$file" | awk "{print tolower(\$0)}")
    if [ "$file" != "$newname" ] ; then
        # the extra step with the temp filename is for case-insensitive filesystems
        if [ ! -e "$newname" ] && [ ! -e "$newname.lcrnm.tmp" ] ; then
           mv -T "$file" "$newname.lcrnm.tmp" && mv -T "$newname.lcrnm.tmp" "$newname" 
        else
           echo "ERROR: Name already exists: $newname"
        fi
    fi    
  done
' sh {} +

References

My script is based on these excellent answers:

https://unix.stackexchange.com/questions/9496/looping-through-files-with-spaces-in-the-names

How to convert a string to lower case in Bash?



回答25:

This works nicely on macOS too:

ruby -e "Dir['*'].each { |p| File.rename(p, p.downcase) }"


标签: