12

I want to lowercase every directories' name under a directory. With which commands can I do that?

erkangur
  • 235
  • 2
  • 7

4 Answers4

10

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!' {} +
Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
  • At least on OS X this will fail if any directories are already lower case: `mv -i a a` give "mv: rename a to a/a: Invalid argument". – Janus Jan 05 '11 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' Jan 05 '11 at 19:05
  • I then found about `-execdir` which is awesome: https://unix.stackexchange.com/questions/5412/lowercasing-all-directories-under-a-directory/494262#494262 I then found that it has some `PATH` madness and was sad :-( – Ciro Santilli OurBigBook.com Jan 13 '19 at 15:09
4

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
Shawn J. Goff
  • 45,338
  • 25
  • 134
  • 145
  • 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
  • 1
    My 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
  • Yep, for...*/ would be better. – Shawn J. Goff Jan 05 '11 at 04:42
  • 1
    @Shawn: `tr` already expects a range, so `tr '[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
0

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

0

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 {} echo {} | tr [:upper:] [:lower:]'. Guess using 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!

Janus
  • 1,523
  • 3
  • 12
  • 15
  • 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 using `find` unless recursing, and then you need `find -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