I want to lowercase every directories’ name under a directory. With which commands can I do that?
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
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!' {} +
Method 2
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
Method 3
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!
Method 4
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
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0