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!' {} +
-
At least on OS X this will fail if any directories are already lower case:
mv -i a agive "mv: rename a to a/a: Invalid argument".Janus– Janus2011-01-05 12:09:14 +00:00Commented Jan 5, 2011 at 12:09 -
@Janus: Right, you get an error message, which is ugly (though harmless at the command line). But anyway I should have used zmv, which takes care of this case.Gilles 'SO- stop being evil'– Gilles 'SO- stop being evil'2011-01-05 19:05:08 +00:00Commented Jan 5, 2011 at 19:05
-
I then found about
-execdirwhich is awesome: unix.stackexchange.com/questions/5412/… I then found that it has somePATHmadness and was sad :-(Ciro Santilli OurBigBook.com– Ciro Santilli OurBigBook.com2019-01-13 15:09:31 +00:00Commented Jan 13, 2019 at 15:09
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
-
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 ; donefrabjous– frabjous2011-01-05 04:02:35 +00:00Commented Jan 5, 2011 at 4:02 -
I was trying to work it out with
find -type d, but couldn't quite get itMichael Mrozek– Michael Mrozek2011-01-05 04:07:44 +00:00Commented Jan 5, 2011 at 4: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– Steven D2011-01-05 04:27:50 +00:00Commented Jan 5, 2011 at 4:27 -
Yep, for...*/ would be better.Shawn J. Goff– Shawn J. Goff2011-01-05 04:42:11 +00:00Commented Jan 5, 2011 at 4:42
-
1@Shawn:
tralready 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'– Gilles 'SO- stop being evil'2011-01-05 08:21:32 +00:00Commented Jan 5, 2011 at 8:21
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!
-
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 usingfindunless recursing, and then you needfind -depth, and-pathmust be-name. See my answer for working examples.Gilles 'SO- stop being evil'– Gilles 'SO- stop being evil'2011-01-05 19:07:46 +00:00Commented Jan 5, 2011 at 19:07 -
@Giles. Thanks! I saw your
mv -iafter writing this. Good point with the quoting...Janus– Janus2011-01-06 05:45:02 +00:00Commented Jan 6, 2011 at 5:45
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 cds 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