I want to lowercase every directories' name under a directory. With which commands can I do that?
4 Answers
All the directories at one level, or recursively?
Zsh
At one level:
autoload zmv
zmv -o-i -Q 'root/(*)(/)' 'root/${1:l}'
Recursively:
zmv -o-i -Q 'root/(**/)(*)(/)' 'root/$1${2:l}'
Explanations: zmv
renames files matching a pattern according to the given replacement text. -o-i
passes the -i
option to each mv
command under the hood (see below). In the replacement text, $1
, $2
, etc, are the successive parenthesized groups in the pattern. **
means all (sub)*directories, recursively. The final (/)
is not a parenthesized group but a glob qualifier meaning to match only directories. ${2:l}
converts $2
to lowercase.
Portable
At one level:
for x in root/*/; do mv -i "$x" "$(printf %s "$x" | tr '[:upper:]' '[:lower:]')"; done
The final /
restricts the matching to directories, and mv -i
makes it ask for confirmation in case of a collision. Remove the -i
to overwrite in case of a collision, and use yes n | for …
. to not be prompted and not perform any renaming that would collide.
Recursively:
find root/* -depth -type d -exec sh -c '
t=${0%/*}/$(printf %s "${0##*/}" | tr "[:upper:]" "[:lower:]");
[ "$t" = "$0" ] || mv -i "$0" "$t"
' {} \;
The use of -depth
ensures that deeply nested directories are processed before their ancestors. The name processing relies on there being a /
; if you want to call operate in the current directory, use ./*
(adapting the shell script to cope with .
or *
is left as an exercise for the reader).
Perl rename
Here I use the Perl rename script that Debian and Ubuntu ship as /usr/bin/prename
(typically available as rename
as well). At one level:
rename 's!/([^/]*/?)$!\L/$1!' root/*/
Recursively, with bash ≥4 or zsh:
shopt -s globstar # only in bash
rename 's!/([^/]*/?)$!\L/$1!' root/**/*/
Recursively, portably:
find root -depth -type d -exec rename -n 's!/([^/]*/?)$!\L/$1!' {} +

- 829,060
There isn't a single command that will do that, but you can do something like this:
for fd in */; do
#get lower case version
fd_lower=$(printf %s "$fd" | tr A-Z a-z)
#if it wasn't already lowercase, move it.
[ "$fd" != "$fd_lower" ] && mv "$fd" "$fd_lower"
done
If you need it to be robust, you should account for when there is already two directories that differ only in case.
As a one-liner:
for fd in */; do fd_lower=$(printf %s "$fd" | tr A-Z a-z) && [ "$fd" != "$fd_lower" ] && mv "$fd" "$fd_lower"; done

- 46,081
-
This showed up midway through my writing the (basically the same) suggestion:
for file in * ; do if [ -d "$file" ] ; then dest="$(echo $file | sed y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/)" ; [ "$dest" != "$file" ] && mv "$file" "$dest" ; fi ; done
– frabjous Jan 05 '11 at 04:02 -
I was trying to work it out with
find -type d
, but couldn't quite get it – Michael Mrozek Jan 05 '11 at 04:07 -
1My instinct would have been to do
for fd in */;
, thus avoiding the need for the check if it was a directory, but I have no idea if this instinct was a good one. – Steven D Jan 05 '11 at 04:27 -
-
1@Shawn:
tr
already expects a range, sotr '[A-Z]' '[a-z]'
translates[
to[
and]
to]
in passing. This is useless but harmlesss; however without the quotes the shell would expand the brackets if there was a file with a one-uppercase-letter name in the current directory. – Gilles 'SO- stop being evil' Jan 05 '11 at 08:21
find -execdir rename
This renames files and directories with a regular expression affecting only basenames.
So for a prefix you could do:
PATH=/usr/bin find . -depth -execdir rename 's/(.*)/\L$1/' '{}' \;
or to affect files only:
PATH=/usr/bin find . -type f -execdir rename 's/(.*)/\L$1/' '{}' \;
-execdir
first cd
s into the directory before executing only on the basename.
I have explained it in more detail at: https://stackoverflow.com/questions/16541582/find-multiple-files-and-rename-them-in-linux/54163971#54163971

- 18,092
- 4
- 117
- 102
I took this as a one-liner challenge :) First, establish a test case:
$ for d in foo Bar eVe; do mkdir -p dcthis/$d; touch dcthis/a${d}.txt; done
$ ls dcthis/
Bar aBar.txt aeVe.txt afoo.txt eVe foo
I use find
to spot the directories with uppercase letters and then downcase them via sh -c 'mv {}
. Guess using echo {} | tr [:upper:] [:lower:]
'sh -c
is a bit hack-ish, but my head always explodes when I try escaping things for find
directly.
$ (cd dcthis && find . -maxdepth 1 -type d -path '*[A-Z]*' -exec sh -c 'mv {} `echo {} | tr [:upper:] [:lower:]`' \;)
$ ls dcthis/
aBar.txt aeVe.txt afoo.txt bar eve foo
Be warned: This solution does not check whether downcasing leads to collisions!

- 1,533
-
checking for collisions is easy:
mv -i
. A bigger problem is that you haven't used proper quoting, so your command will fail if there are special characters (whitespace or\[*?
) anywhere in the name. There's no point of usingfind
unless recursing, and then you needfind -depth
, and-path
must be-name
. See my answer for working examples. – Gilles 'SO- stop being evil' Jan 05 '11 at 19:07 -
@Giles. Thanks! I saw your
mv -i
after writing this. Good point with the quoting... – Janus Jan 06 '11 at 05:45
mv -i a a
give "mv: rename a to a/a: Invalid argument". – Janus Jan 05 '11 at 12:09-execdir
which is awesome: https://unix.stackexchange.com/questions/5412/lowercasing-all-directories-under-a-directory/494262#494262 I then found that it has somePATH
madness and was sad :-( – Ciro Santilli OurBigBook.com Jan 13 '19 at 15:09