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:
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' 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 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
- 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 usingfindunless recursing, and then you needfind -depth, and-pathmust 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 -iafter writing this. Good point with the quoting... – Janus Jan 06 '11 at 05:45
mv -i a agive "mv: rename a to a/a: Invalid argument". – Janus Jan 05 '11 at 12:09-execdirwhich is awesome: https://unix.stackexchange.com/questions/5412/lowercasing-all-directories-under-a-directory/494262#494262 I then found that it has somePATHmadness and was sad :-( – Ciro Santilli OurBigBook.com Jan 13 '19 at 15:09