13

I'd like to recursively rename all files and folders (sub-folders) to uppercase.

I found some scripts that will do it to lowercase, but I don't know how to change them so it will do it the other way around (lower to upper).

The script that I found and works for lowercase, but I didn't knew how to modify is:

rename 'y/A-Z/a-z/' *

It's from man rename.

3
  • Without knowing anything about the rename command, I suspect that if you try rename 'y/a-z/A-Z/' * you will get what you want. Careful where you test it through.
    – Warwick
    Jul 7, 2014 at 7:17
  • it's not recursevely (because the initial one wasn't ...) but it works. I can't understand why I didn't tought at that miself :), thenks! Jul 7, 2014 at 7:19
  • I think that given the quality of Gilles' answer, that it woud be good if you accept his answer in preference to mine. It is much more complete than mine and explains your options (and how it all works). Thanks.
    – Warwick
    Jul 9, 2014 at 0:11

6 Answers 6

15

Note that you're using the Perl script called rename distributed by Debian and derivatives (Ubuntu, Mint, …). Other Linux distributions ship a completely different, and considerably less useful, command called rename.

y/A-Z/a-z/ translates each character in the range A through Z into the corresponding character in the range a through z, i.e. ASCII uppercase letters to the corresponding lowercase letter. To perform the opposite translation, use y/a-z/A-Z/. Another way to write the same command is rename '$_ = uc($_)' * — uc is the uppercase function, and the rename command renames files based on the transformation made to the $_ variable.

rename '…' * only renames files in the current directory, because that's what * matches. Dot files (files whose name begins with .) are skipped, too.

If you want to rename files in the current directory and in subdirectories recursively, you can use the find command to traverse the current directory recursively. There is a difficulty here: if you call rename, this renames both the directory and the base name part. If you call rename on a directory before recursing into it (find -exec rename … {} \;), find gets confused because it's found a directory but that directory no longer exists by the time it tries to descend into it. You can work around this by telling find to traverse a directory before acting on it, but then you end up attempting to rename foo/bar to FOO/BAR but the directory FOO doesn't exist.

A simple way to avoid this difficulty is to make the renaming command act only on the base name part of the path. The regular expression ([^/]*\Z) matches the final part of the path that doesn't contain a /.

find . -depth -exec rename 's!([^/]*\Z)!uc($1)!e' {} +

The shell zsh provides more convenient features for renaming — even more cryptic than Perl, but terser and often easier to compose.

The function zmv renames files based on patterns. Run autoload -U zmv once to activate it (put this line in your .zshrc).

In the first argument to zmv (the pattern to replace), you can use zsh's powerful wildcard patterns. In the second argument to zmv (the replacement text), you can use its parameter expansion features, including history modifiers.

zmv -w '**/*' '$1$2:u'

Explanation:

  • -w — automatic assign numeric variables to each wildcard pattern
  • **/* — all files in subdirectories, recursively (**/ matches 0, 1 or more levels of subdirectories)
  • $1 — the first numeric variable, here matching the directory part of each path
  • $2:u — the second numeric variable, here matching the base name part of each path, with the :u modifier to convert the value to uppercase

As an added bonus, this respects the ambient locale settings.

If you aren't sure about a zmv command you wrote, you can pass the -n option to print what the command would do and not change anything. Check the output, and if it does what you want, re-run the command without -n to actually act.

3
  • I don't know if this was changed in zsh in the last 6 years, but I needed to put parenthesis around the patterns for $1$2 to match in zsh: zmv -n -w '(*)/()' '$1$2:u'
    – froh42
    Jul 19, 2020 at 12:18
  • 1
    @froh42 I don't think anything has changed. The point of the -w option is that it adds parentheses automatically. So zmv -w '**/*' '$1$2:u' is equivalent to zmv '(*/)()' '$1$2:u'. It works for me with zsh 5.8. Note that ``zmv '(**)/(*)' '$1$2:u' doesn't work for two reasons: it's missing a / in the replacement ($1/$u:u), and the parentheses around ** make it lose its recursive meaning so it's equivalent to (*)/(*) which only acts on files in immediate subdirectories. Jul 19, 2020 at 17:03
  • Perfect, thanks for the explanation. By the way, I can't fathom how I have missed zmv in the past, it is SO useful.
    – froh42
    Jul 21, 2020 at 10:26
6

I'd like to direct anyone who's still being linked to this answer to the excellent answer Guiles Quernot gave to this question which doesn't require find.

The resulting command would be:

shopt -s globstar
rename -n 'y/a-z/A-Z/' **

But before running please read the answer linked for caveats regarding old bash versions.

Finally in case someone is wondering what does the y/// command does in perl regex. Here's a link to the relevant documentation.

1
  • Thank you! Incredible... and easy. I didn't know. Dec 11, 2020 at 11:09
5

Stolen (with a minor edit) from Gilles post here

find <DIR> -depth -type d -exec rename -n 's!/([^/]*/?)$!\U/$1!' {} +

7
  • This doesn't act recursively. Jul 7, 2014 at 21:42
  • True. I should have read the question properly. I don't have access to the rename command, but I believe that this will work recursively - find <dir> -exec rename 'y/a-z/A-Z/' {} \;
    – Warwick
    Jul 7, 2014 at 23:03
  • It's a bit more complicated than that, because you're renaming directories while find is recursing into them. Jul 7, 2014 at 23:09
  • @Gilles - OK. After doing a bit of research, it appears that I need to add the -depth option to find so that directory names are handled last. So find <dir> -depth -exec rename 'y/a-z/A-Z/' {} \; should handle the renaming directories issue. Correct?
    – Warwick
    Jul 7, 2014 at 23:41
  • No, that still doesn't work, because rename changes e.g. foo/bar to FOO/BAR and FOO doesn't exist at that point. Jul 7, 2014 at 23:45
2

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/(.*)/\U$1/' '{}' \;

or to affect files only:

PATH=/usr/bin find . -type f -execdir rename 's/(.*)/\U$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

Try this after moving to directory where you would like to rename the files:

for word in `ls -ltr |tail -n +2 |awk '{print $9}'`
do
  a=$(echo $word | tr '[a-z]' '[A-Z]')
  mv $word $a
  echo "Done Successfully"
done
1
0

A solution without using rename (which is not available in some distro's):

find <dir> -type d -printf "echo mv %h/%f %h/\$(echo %f | sed 's/(.*)/\U$1/')\n" | sort -r -k3,3 | sh

for testing and to do actual work :

find <dir> -type d -printf "mv %h/%f %h/\$(echo %f | sed 's/(.*)/\U$1/')" | sort -r -k2,2 | sh

Explanation: Look in current folder (find .) and find all directories (-type d) which and print a command which will be passed to sh for execution. printf replaces %h and %f with path-to and name of found folders. The part $(echo %f | sed 's/(.*)/\U$1/g') asks the sed command to replace (s/) anything and take them as group 1 ((.*)) and replace it with uppercaseed group 1 (\U$1) in folder name. The important part is to reverse sort (| sort -r) these commands by the source path only (-k2,2) before running them (| sh) otherwise you may have renamed a parent folder before renaming it's child and the command fails and the -k2,2 ensures extra parts does not mingle with sorting. (try these commands to understand the reason printf "./a/b \n./a \n./c/a \n./a/a \n./c/a/a \n" |sort and printf "./a/b t\n./a t\n./c/a t\n./a/a t\n./c/a/a t\n" |sort) Note: it may fail if the folders names have white spaces

1
  • Welcome to the site, and thank you for your contribution. Please note that since the OP is specifically asking for a transformation to uppercase, you may want to edit your post to reflect that. Also, your solution will ill-behave if the filenames contain spaces or other special characters.
    – AdminBee
    Sep 14, 2021 at 15:44

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .