242

I have to rename a complete folder tree recursively so that no uppercase letter appears anywhere (it's C++ source code, but that shouldn't matter).

Bonus points for ignoring CVS and Subversion version control files/folders. The preferred way would be a shell script, since a shell should be available on any Linux box.

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

  1. I think files with the same lowercase names should be overwritten; it's the user's problem. When checked out on a case-ignoring file system, it 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 Subversion version control files should be omitted. After all, it's just a scratch checkout. Maybe an "export" is more appropriate.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
vividos
  • 6,468
  • 9
  • 43
  • 53
  • 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 – Mihai Limbășan Sep 30 '08 at 10:57

31 Answers31

328

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/' *
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
tristanbailey
  • 4,427
  • 1
  • 26
  • 30
  • 5
    http://linux.icydog.net/rename.php: `The renaming utility that comes by default with Ubuntu is a Perl program sometimes called prename` – sleepsort May 08 '13 at 15:19
  • 2
    Thanks, I used it in this way $ find | xargs rename 'y/A-Z/a-z/' * – Rashi Oct 26 '13 at 06:36
  • Hmm... I get "'./ABC.txt' not renamed. './abc.txt' already exists" even though it doesn't. – user456584 Mar 14 '14 at 19:54
  • 1
    If in that folder there's too much stuff (rename isn't able to handle more than a given number of elements (I have got about 99k files) giving you the message "Argument list too long"), you can go with `find . -exec rename 'y/A-Z/a-z/' {} \;` – reallynice Sep 11 '14 at 15:07
  • 7
    This assumes you have perls rename, which is not the case always. e.g. on my debian rename is completely different – Krzysztof Krasoń Mar 17 '15 at 08:37
  • 15
    On Arch, the program is called `perl-rename` (and is provided by a package with the same name) – jaymmer - Reinstate Monica Nov 13 '15 at 04:20
  • @user456584 you're using a case-insensitive filesystem, so `abc.txt` and `ABC.txt` there are a same file (a.k.a NTFS or VFAT). Use the `-f` flag – Antti Haapala -- Слава Україні May 08 '16 at 17:19
  • 8
    This doesn't answer the given problem since "rename" is not recursive. – Cybolic Sep 19 '16 at 15:07
  • y is transliterate? I couldn't find anything about 'y' in the man page of rename (on Ubuntu 16.10 or OSX with bash version 3.2.57) but found this under sed? http://manpages.ubuntu.com/manpages/precise/en/man1/sed.1.html – notapatch Apr 14 '17 at 10:43
  • 10
    on OSX make sure you have rename. `brew install rename` – Nathan Hinchey May 19 '17 at 16:04
  • Most importantly: Unlike the other answers, one can actually remember this! – xeruf May 10 '18 at 06:47
  • 1
    `rename -c *` or `rename -cf *` is shorter. – Victor Yarema Sep 05 '18 at 12:06
  • @reallynice the idea is interesting but I had to run it several times as there was many depths of folders, but is working little by little thx :) – Aquarius Power Jun 11 '19 at 22:29
  • Given `/tmp/AAA/BBB` `/tmp/AAA/CCC`, `rename -f 'y/A-Z/a-z/' /tmp/AAA/*` still return `/tmp/aaa/bbb: No such file or directory` on a case-sensitive filesystem, `-f` flag doesn't solve the problem – Pablo Bianchi Jan 11 '20 at 01:43
  • Add `-v` to see what is changed. – Martin T. Feb 01 '22 at 08:42
  • On Fedora the package is `prename`: "prename.noarch : Perl script to rename multiple files" – user598527 Apr 20 '22 at 21:06
  • you can make rename `recursive` just by changing the address: `rename 'y/A-Z/a-z/' ./*/*` – sorrow poetry Aug 11 '22 at 17:50
  • 1
    FYI for future readers. This doesn't work on WSL (windows files from Linux). You get a "file already exists". More a function of how the OS combination works. I ended up doing something like ```find . -depth | rename -v -n -d 'y/A-Z\./a-z+/'``` followed by ```find . -depth | rename -n -v -d "s/\+/\./g"```. Of course for such a hack to work, you need to be sure no file names contain +. Which you can check by doing ```find . -iname "*+*"``` – Dr Phil Jan 09 '23 at 00:31
200

A concise version using the "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 the move command (for example, "svn mv").

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alex B
  • 82,554
  • 44
  • 203
  • 280
  • 39
    using rename can be done this way as well rename 'y/A-Z/a-z/' * – Tzury Bar Yochay Oct 11 '09 at 07:07
  • 5
    Beware, both versions won't work on a case insensitive filesystem. In my case I replaced the `then`-enclosed line by `(mv "${SRC}" "${DST}.renametmp" && mv "${DST}.renametmp" "${DST}") || echo "${SRC} was not renamed"`. – Lloeki Apr 20 '11 at 16:21
  • 6
    The second approach didn't work correctly for me with files containing blank spaces (context: linux, bash). – dim Jun 29 '11 at 16:34
  • 6
    Using the last version of rename, no regex is needed. The full command becomes `find my_root_dir -depth -exec rename -c {} \;`. Add -f to rename if you're on a case-insensitive filesystem (eg Mac) – Javache Aug 14 '11 at 14:24
  • The second one worked for me on Solaris by removing the `-T` from the `mv` command. – Ham Feb 21 '12 at 12:35
  • Using `find ... | while read -r SRC` in place of the `for` loop fixed problems with whitespace for me. (Mac OS) – Matt Oct 11 '12 at 19:32
  • +1 for the second option. The first one didn't seem to work for me. – carlspring May 06 '13 at 17:55
  • If you're using this in a git repo, make sure not to rename anything in your `.git` folder. – schmunk Sep 26 '13 at 12:31
  • the first one worked for me on ubuntu 12.04 LTS, thanks for both options! – Henry van Megen Jul 03 '14 at 10:04
  • I had to add a `-f` to get the rename to work. I kept getting a warning saying the file already existed. – loeschg Aug 08 '14 at 18:46
  • make sure you actually have `rename`... for OSX, `brew install rename` – Nathan Hinchey May 19 '17 at 16:03
  • find my_root_dir -depth -exec rename 's/(.*)\/([^\/]*)/$1\/\L$2/' {} \; This code works but I want it in reverse lowercase to uppercase how do I do it? – Jerico Pulvera Jan 20 '18 at 18:20
  • whats the change make in first script if we want to rename only folders and sub folders,not files? – HaFiz Umer Nov 12 '20 at 23:24
  • @tzury Bar Yochay's regexp does not work when the directory is multiple levels deep. – hbogert Mar 05 '21 at 13:36
  • using which version of `rename`? – CervEd Jul 20 '21 at 16:31
112
for f in `find`; do mv -v "$f" "`echo $f | tr '[A-Z]' '[a-z]'`"; done
rubo77
  • 19,527
  • 31
  • 134
  • 226
Swaroop C H
  • 16,902
  • 10
  • 43
  • 50
  • Please do a "mkdir -p A/B/C" before running your script. – tzot Sep 30 '08 at 10:56
  • 3
    "find" should be replaced by whatever command you use to get a list of the files you wish to rename; for example, "ls *.{C,CPP,H}". – JPaget Feb 06 '14 at 19:38
  • `for f in *.txt; do mv -v $f `echo $f | tr [:upper:] [:lower:]`; done` Works with spaces on debian. – soyuka May 13 '15 at 08:43
  • 2
    Doesn't work on OS X, just prints the usage info for `find`. `find .` seems to fix this. – Emil Laine Feb 25 '16 at 19:38
  • 4
    The "consolidated" version, from all comments (answer can't be edited anymore): ```for f in `find .`; do mv -v "$f" "`echo $f | tr '[A-Z]' '[a-z]'`"; done``` – romaricdrigon Jun 20 '17 at 07:13
  • @romaricdrigon - I went with your version (on macOS), and it works perfectly. – Carl Smith Nov 03 '17 at 23:43
  • my output is `'[A-Z] [error opening dir]'$'\n''[a-z] [error opening dir]'$'\n\n''0 directories, 0 files'` on `lubuntu` 16.4 – Timo Nov 24 '17 at 15:12
  • 2
    Can someone explain to my why this should work in the first place? If it finds ./FOO and ./FOO/BAR, it first renames ./FOO to ./foo, after which it can no longer find ./FOO/BAR. Entering my own answer below that should fix this. – oisyn Feb 14 '19 at 15:54
  • how can rename only folders name? – HaFiz Umer Jul 23 '20 at 07:13
  • This isn't working for file paths with spaces in it. – Aaron Franke Sep 02 '23 at 00:58
112

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

zip -r foo.zip foo/*
unzip -LL foo.zip
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ginhing
  • 1,369
  • 1
  • 9
  • 13
  • 21
    Wow, that's a pretty inefficient way. – Artjom B. Aug 31 '14 at 09:24
  • 42
    Assuming that computational efficiency is typically NOT a concern when you find yourself needing to rename files in a directory... I think this is probably the most creatively simple solution on this thread. Really something this trivial should not be as complicated as the accepted answers demonstrate and this workaround is far more proportionate to the amount of thought I want to invest in an operation like this. +1000 – Peter M. Elias Apr 18 '15 at 20:20
  • 1
    @peter what's more, it works! I tried using the other more complex answers, and for whatever reason that was beyond my willingness to debug, they simply were not working. this simple one works like a charm! – JHixson Oct 08 '15 at 08:44
  • 1
    This is hilarious-- I just tried it and it worked wonders. Definitely simpler than writing a script. – David Betz Jan 31 '16 at 05:52
  • 43
    You can speed it up with `-0` (that's 'zero', no compression) switch, `zip -0 -r foo.zip foo/`. – Johnny Baloney Feb 16 '16 at 19:29
  • #ingenious!! I thought on making a gradual folder name changing (changes top folders prior to inner ones, and re-scan it all) and so on.. but this works great! thx! btw `unrar x -cl "file.rar" "somefolder/"` – Aquarius Power Feb 24 '16 at 22:29
  • 1
    This will not remove any files with the uppercase characters, which actually worked better in my case. But, you can delete the directory in between the two operations to ensure that the result contains only the files with lowercase names. – palswim May 11 '16 at 07:16
  • 9
    I had to change over 50'000 filenames in different directory structures. The original accepted answer did barely 10% the work in 2 minutes where this, with the `-0` compression, was almost instant! – Peon Feb 01 '17 at 13:14
  • 3
    This is not just inefficient. This one is simply **impossible** if there is not enough free space for a temporary archive. – Victor Yarema Sep 05 '18 at 12:09
  • 2
    I know it's a dumb and inefficient method but it was the fastest way for me on WSL because Windows was being pissy about there already being a file with the upper case name. – Matthew Bahr Jul 18 '19 at 16:24
  • how can rename only folders and sub folders name. not files – HaFiz Umer Jul 23 '20 at 07:14
  • This is a good solution for files that are small enough in size. If you have large files (for example I needed to change names of high resolution pictures and videos), this solution would be painfully slow. Plus of course you need a lot of free space. – Dr Phil Jan 08 '23 at 18:56
26

One can simply use the following which is less complicated:

rename 'y/A-Z/a-z/' *
NelsonGon
  • 13,015
  • 7
  • 27
  • 57
Stephen
  • 819
  • 8
  • 8
  • 3
    This does not work, it says that file with lowercased name already exist. – kirhgoff Feb 20 '17 at 00:47
  • @kirhgoff I've seen that happen with NFS mounts -- follow the instructions [here](https://askubuntu.com/a/546429). – joebeeson Jun 01 '17 at 13:43
  • 1
    This doesn't add anything to [this identical answer posted 6 years earlier](https://stackoverflow.com/a/8167105/6243352). – ggorlen Nov 13 '20 at 20:29
21

This works on CentOS/Red Hat Linux 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: Rename all file names from uppercase to lowercase characters

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

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Pere
  • 1,647
  • 3
  • 27
  • 52
20

The simplest approach I found on Mac OS X was to use the rename package from http://plasmasturm.org/code/rename/:

brew install rename
rename --force --lower-case --nows *

--force Rename even when a file with the destination name already exists.

--lower-case Convert file names to all lower case.

--nows Replace all sequences of whitespace in the filename with single underscore characters.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Kim T
  • 5,770
  • 1
  • 52
  • 79
18

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

rename --lower-case --force somedir/*
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
alemol
  • 8,058
  • 2
  • 24
  • 29
  • 11
    ... if you set up rename first, e.g. through `brew install rename` – pdu Nov 20 '15 at 09:15
  • 3
    rename 'y/A-Z/a-z/' * – Stephen Feb 18 '17 at 10:53
  • 6
    `--lower-case`? That's not the case in Arch Linux at least, sadly. –  Jun 22 '17 at 21:14
  • 3
    `rename` is not a bash builtin. It is an external utility and syntax will differ depending on the version you have installed. This answer is not sufficient as a portable solution "for all unix based OS" as claimed. – Corey Goldberg Nov 25 '17 at 13:18
  • 3
    Just checked, the `rename` command included in the latest Fedora (28), through `util-linux-2.31` package, does not have the `--lower-case` option. – niXar Sep 09 '18 at 07:04
16

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 is a 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 scripts on a tree that includes the file created by

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

... well guess what ...

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
niXar
  • 670
  • 5
  • 13
6

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

Folders are all renamed correctly, and 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).

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

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
vividos
  • 6,468
  • 9
  • 43
  • 53
6

One-liner:

for F in K*; do NEWNAME=$(echo "$F" | tr '[:upper:]' '[:lower:]'); mv "$F" "$NEWNAME"; done

Or even:

for F in K*; do mv "$F" "${F,,}"; done

Note that this will convert only files/directories starting with letter K, so adjust accordingly.

Eduardo
  • 7,631
  • 2
  • 30
  • 31
5

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)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
agnul
  • 12,608
  • 14
  • 63
  • 85
5

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
  • 12,875
  • 11
  • 40
  • 67
5

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.

benjwadams
  • 1,520
  • 13
  • 16
5

This works nicely on macOS too:

ruby -e "Dir['*'].each { |p| File.rename(p, p.downcase) }"
Chris
  • 39,719
  • 45
  • 189
  • 235
4
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.

jpaugh
  • 6,634
  • 4
  • 38
  • 90
  • This doesn't work: it renames a directory before it operates on the contents, such that the later attempt on the contents fails. – Prune Feb 22 '16 at 19:07
  • Adding `-depth` fixes that! This is a really quick solution, but of course without using the `-print0` option to find, it is not the most reliable – jpaugh Feb 25 '16 at 21:08
  • @jpaugh No it does not. It will try to rename ./FOO/BAR to ./foo/bar, but ./foo does not exist yet at that time. – oisyn Feb 14 '19 at 16:00
3

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.

tzot
  • 92,761
  • 29
  • 141
  • 204
3

Use typeset:

typeset -l new        # Always lowercase
find $topPoint |      # Not using xargs to make this more readable
  while read old
  do new="$old"       # $new is a lowercase version of $old
     mv "$old" "$new" # Quotes for those annoying embedded spaces
  done

On Windows, emulations, like Git Bash, 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", and then to $new.

Paul Hodges
  • 13,382
  • 1
  • 17
  • 36
2

In OS X, 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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jonghee Park
  • 1,257
  • 15
  • 14
2

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
akilesh raj
  • 656
  • 1
  • 8
  • 19
  • Why did you use `-I` option for `xargs` here? – Victor Yarema Sep 05 '18 at 12:03
  • The `- I` flag is not mandatory. It is used when we execute multiple commands with `xargs`. Here is an example of using multiple commands with `xargs` `cat foo.txt | xargs -I % sh -c 'echo %; mkdir %'` You can run without `-I` like `find . -iname "*.py" -type f | xargs -I% rename -c -f "%"` – akilesh raj Sep 13 '18 at 09:49
2

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?

ndemou
  • 4,691
  • 2
  • 30
  • 33
1

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 straight from Ubuntu 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 replaced whitespace with underscores using:

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

)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
jacanterbury
  • 1,435
  • 1
  • 26
  • 36
1

Slugify Rename (regex)

It is 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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
cwd
  • 53,018
  • 53
  • 161
  • 198
1

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])
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
John Foley
  • 957
  • 9
  • 19
  • Talk about making it complicated... just do .. rename 'y/A-Z/a-z/' * – Stephen Feb 18 '17 at 10:53
  • 1
    @Stephen, that assumes you have admin access to install rename. I frequently need to munge data on systems on which I do not have the ability to install other software. In recent Ubuntus, this is only installed if perl is present. – John Foley Feb 20 '17 at 15:00
1

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

It is a 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 # Substitute string on pattern

--yes # Confirm all actions

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

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 1
    rename from `brew` on macOS has `-l` bound to creating a symlink, and `-c` to converting to lowercase, so watch out. – rien333 Aug 12 '17 at 18:15
1

None of the solutions here worked for me because I was on a system that didn't have access to the perl rename script, plus some of the files included spaces. However, I found a variant that works:

find . -depth -exec sh -c '
    t=${0%/*}/$(printf %s "${0##*/}" | tr "[:upper:]" "[:lower:]");
    [ "$t" = "$0" ] || mv -i "$0" "$t"
' {} \;

Credit goes to "Gilles 'SO- stop being evil'", see this answer on the similar question "change entire directory tree to lower-case names" on the Unix & Linux StackExchange.

1

I believe the one-liners can be simplified:

for f in **/*; do mv "$f" "${f:l}"; done

TransferOrbit
  • 201
  • 2
  • 7
0
( 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
  • 129
  • 3
  • 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. – tzot Sep 30 '08 at 11:33
0
find . -depth -name '*[A-Z]*'|sed -n 's/\(.*\/\)\(.*\)/mv -n -v -T \1\2 \1\L\2/p'|sh

I haven't tried the more elaborate scripts mentioned here, but none of the single commandline versions worked for me on my Synology NAS. rename is not available, and many of the variations of find fail because it seems to stick to the older name of the already renamed path (eg, if it finds ./FOO followed by ./FOO/BAR, renaming ./FOO to ./foo will still continue to list ./FOO/BAR even though that path is no longer valid). Above command worked for me without any issues.

What follows is an explanation of each part of the command:


find . -depth -name '*[A-Z]*'

This will find any file from the current directory (change . to whatever directory you want to process), using a depth-first search (eg., it will list ./foo/bar before ./foo), but only for files that contain an uppercase character. The -name filter only applies to the base file name, not the full path. So this will list ./FOO/BAR but not ./FOO/bar. This is ok, as we don't want to rename ./FOO/bar. We want to rename ./FOO though, but that one is listed later on (this is why -depth is important).

This comand in itself is particularly useful to finding the files that you want to rename in the first place. Use this after the complete rename command to search for files that still haven't been replaced because of file name collisions or errors.


sed -n 's/\(.*\/\)\(.*\)/mv -n -v -T \1\2 \1\L\2/p'

This part reads the files outputted by find and formats them in a mv command using a regular expression. The -n option stops sed from printing the input, and the p command in the search-and-replace regex outputs the replaced text.

The regex itself consists of two captures: the part up until the last / (which is the directory of the file), and the filename itself. The directory is left intact, but the filename is transformed to lowercase. So, if find outputs ./FOO/BAR, it will become mv -n -v -T ./FOO/BAR ./FOO/bar. The -n option of mv makes sure existing lowercase files are not overwritten. The -v option makes mv output every change that it makes (or doesn't make - if ./FOO/bar already exists, it outputs something like ./FOO/BAR -> ./FOO/BAR, noting that no change has been made). The -T is very important here - it treats the target file as a directory. This will make sure that ./FOO/BAR isn't moved into ./FOO/bar if that directory happens to exist.

Use this together with find to generate a list of commands that will be executed (handy to verify what will be done without actually doing it)


sh

This pretty self-explanatory. It routes all the generated mv commands to the shell interpreter. You can replace it with bash or any shell of your liking.

oisyn
  • 1,306
  • 5
  • 14
0

Using bash, without rename:

find . -exec bash -c 'mv $0 ${0,,}' {} \;
Felix
  • 170
  • 9
0

First Step

on MacOS:

brew install rename

on linux:

sudo install rename

Second Step

find . -depth -execdir rename -f 'y/A-Z/a-z/' {} \;

Explain

find . -depth: Ensures that the directories are processed in a depth-first manner. It's important to avoid issues when renaming parent directories before their children.

$ tree -L 3                  
.
└── ACB
    ├── HUS
    └── TEXT.AA

$ find .
.
./ACB
./ACB/TEXT.AA
./ACB/HUS

$ find . -depth                                      
./ACB/TEXT.AA
./ACB/HUS
./ACB
.

-execdir rename 'y/A-Z/a-z/' {} \;: Executes the rename command with the Perl expression 'y/A-Z/a-z/'. The {} is a placeholder for the directory name, and \; marks the end of the -execdir option.

$ find . -depth -execdir rename -f 'y/A-Z/a-z/' {} \;
# success
$ find .
.
./acb
./acb/text.aa
./acb/hus
Shaowen Zhu
  • 51
  • 1
  • 6