vividos
vividos

Reputation: 6748

How do I rename all folders and files to lowercase on Linux?

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.

Upvotes: 257

Views: 300432

Answers (30)

Shaowen Zhu
Shaowen Zhu

Reputation: 105

First Step

on MacOS:

brew install rename

on linux:

sudo apt-get 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

Upvotes: 2

Dark Angel
Dark Angel

Reputation: 105

using Bash only (with ls and mv):

for fname in $(ls); do mv ${fname} ${fname,,}; done```

Upvotes: 0

agnul
agnul

Reputation: 13038

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)

Upvotes: 5

Sebastian Schmied
Sebastian Schmied

Reputation: 152

The following script works on RedHat-based systems such as RHEL, CentOS, Rocky & Alma.

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 filenames I wanted to rename included spaces (which have issues on some answers). 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 Unix & Linux StackExchange.

Upvotes: 3

Swaroop C H
Swaroop C H

Reputation: 17024

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

Note: The dot is required for macOS compatibility, but it still works on Linux.

Upvotes: 116

Felix
Felix

Reputation: 196

Using bash, without rename:

find . -exec bash -c 'mv $0 ${0,,}' {} \;

Upvotes: 1

Eduardo
Eduardo

Reputation: 7841

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.

Upvotes: 6

TransferOrbit
TransferOrbit

Reputation: 227

I believe the one-liners can be simplified:

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

Upvotes: 0

Stephen
Stephen

Reputation: 814

One can simply use the following which is less complicated:

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

Upvotes: 24

Paul Hodges
Paul Hodges

Reputation: 15246

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.

Upvotes: 3

Kim T
Kim T

Reputation: 6416

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.

Upvotes: 23

user4104817
user4104817

Reputation:

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 ;)

Upvotes: 1

Pere
Pere

Reputation: 1973

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

Upvotes: 21

John Foley
John Foley

Reputation: 987

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])

Upvotes: 1

alemol
alemol

Reputation: 8632

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

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

Upvotes: 18

Jonghee Park
Jonghee Park

Reputation: 1275

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

Upvotes: 1

cwd
cwd

Reputation: 54756

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

Upvotes: 1

jacanterbury
jacanterbury

Reputation: 1505

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

)

Upvotes: 0

Ginhing
Ginhing

Reputation: 1439

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

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

Upvotes: 119

tristanbailey
tristanbailey

Reputation: 4605

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/' *

Upvotes: 342

niXar
niXar

Reputation: 680

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

Upvotes: 17

vividos
vividos

Reputation: 6748

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.

Upvotes: 6

Alex B
Alex B

Reputation: 84792

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

Upvotes: 207

oisyn
oisyn

Reputation: 1366

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.

Upvotes: 1

Chris
Chris

Reputation: 40613

This works nicely on macOS too:

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

Upvotes: 5

ndemou
ndemou

Reputation: 5506

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?

Upvotes: 2

akilesh raj
akilesh raj

Reputation: 674

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

Upvotes: 2

Chris Schierkolk
Chris Schierkolk

Reputation: 61

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.

Upvotes: 5

benjwadams
benjwadams

Reputation: 1580

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.

Upvotes: 6

Ian Dickinson
Ian Dickinson

Reputation: 13285

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

Upvotes: 5

Related Questions