3

I have 1,000,000 files in some folders and subfolders. I want to rename them from lowercase to uppercase using shell commands. I don't want to modify the extension. only filename part.

I have found this one:

rename 's/^([^.]*)\.(.*)$/\U$1\E.$2/' *

but it is not recursive and only works on files in current folder.

Then I tried this one:

find . -depth  -execdir rename 's/^([^.]*)\.(.*)$/\U$1\E.$2/' {} \;

But no files changed.

How can I use it recursively?

Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
hd.
  • 155
  • 1
  • 5
  • And your shell is…? See your shell's manual to find out whether it supports `**` syntax in globbing. `bash` (with `globstar` turned on) and `zsh` does. – manatwork Jul 24 '13 at 10:37
  • If possible, you should backup your files before running anything. –  Jul 24 '13 at 14:17
  • 1
    See also [Renaming files to have lower case extensions with 'rename'](http://unix.stackexchange.com/questions/19807/renaming-files-to-have-lower-case-extensions-with-rename) and [Lowercasing all directories under a directory](http://unix.stackexchange.com/questions/5412/lowercasing-all-directories-under-a-directory) for other methods of changing the case of file names. – Gilles 'SO- stop being evil' Jul 25 '13 at 02:00
  • Obligatory 'Some people, when confronted with a problem, think “I know, I'll use regular expressions.” Now they have two problems.' but with a smattering of history that I'd not seen http://regex.info/blog/2006-09-15/247 – msw Jul 25 '13 at 05:19

4 Answers4

3

Assuming that you have the Perl rename provided by Debian and derived distributions such as Debian and Ubuntu, you're almost there. The problem is that -execdir passes a file name prefixed with ./ to the command. (The reason for that is that some commands treat arguments starting with some characters specially; this way, if you have a file called -foo, it's passed as ./-foo and therefore treated as a file and not as an option.) With your regex, this results in $1 being always empty, and hence the new name is identical to the old name.

Accommodate for this ./ in your regular expression.

find . -depth  -execdir rename 's/^(\.\/[^.]*)\.(.*)$/\U$1\E.$2/' {} \;
Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
  • The above command fails on directory name with a dot(.) separator. ex : dir.xyz/abc.txt is being renamed to DIR.xyz/ABC.txt – Priya Jul 25 '13 at 05:00
  • It is ambiguous in the OP whether files or {files and directories} were to be changed. I read `-type f` and Giles infers the opposite. We're both wrong **and** both right. – msw Jul 25 '13 at 05:21
  • @Ameer `rename` will never be called with an argument of the form `dir.xyz/abc.txt`, because `-execdir` was used and not `-exec`. All the arguments to `rename` will be of the form `./SOMETHING` where `SOMETHING` doesn't contain any slash. So `dir.xyz` will be renamed to `DIR.xyz`, which is consistent with the expressed requirements. Thanks to `-depth`, this happens after `dir.xyz/abc.txt` has been renamed to `dir.xyz/ABC.txt`, so there won't be a problem with `find` attempting to move into a directory that's been renamed without it knowing. – Gilles 'SO- stop being evil' Jul 25 '13 at 11:18
0

find . -type f -exec rename -v 's/(\w+).(\w+)$/\U$1\E.$2/' {} \;

Priya
  • 113
  • 4
  • This is close to correct but it will fail on filenames with spaces or punctuation in them. It would also be a better answer if you explained why you are using `-type`. – msw Jul 24 '13 at 10:04
  • @msw What problem do you see with file names with whitespace? This will, however, fail on files in subdirectories that contain lowercase letters, as it will attempt to rename e.g. `foo/bar.ext` to `FOO/BAR.ext` (hd. used `-execdir` to work around this problem). – Gilles 'SO- stop being evil' Jul 24 '13 at 21:56
  • @Gilles , there is no fail with directory as -type f only gives the file path. and renames $1 & $2 only touches the files inside the dir. – Priya Jul 25 '13 at 05:01
  • @Giles I think you know the answer as you used the more inclusive pattern in your answer, but `\w+` will not match `test file` or `test-file` and many other such likely names. It also uses `.` when it should have `\.`. Also since @Ameer commented while I was writing this, without `-execdir` you need to ensure that only the last path component is matched yielding (untested) `s!/([^/]*)\.([^/]*)$!—!` – msw Jul 25 '13 at 05:08
0

You can use this little bash script.

#!/bin/bash

find . -type f -exec sh -c  '
    dn=`dirname "$1"`
    bn=`basename "$1"`
    fn=`echo "${bn%.*}"`
    ext=`echo "${bn##*.}"`
    FN=`echo "${fn^^}"`
    newfn="${FN}"."${ext}"
    #echo ${dn}/$newfn
    if [ "${fn}" = "${FN}" ] ;then : ;else echo "${bn} renamed to ${newfn}";mv "$1" "${dn}/${newfn}";fi
' _ {} \;
Luis
  • 2,438
  • 2
  • 20
  • 19
  • And how will that traverse the directory structure recursively, as requested in the question? – manatwork Jul 24 '13 at 12:22
  • @manatwork: This new script takes into consideration the subdir part of the question that I overlooked before. – Luis Jul 24 '13 at 12:36
  • 1
    Ok, it works fine now. But why you use `fn=\`echo "${bn%.*}"\`` instead of `fn="${bn%.*}"`? – manatwork Jul 24 '13 at 12:46
-1
ls -r | rename 's/^([^.]*)\.(.*)$/\U$1\E.$2/'

Is this OK?

  • never use `ls` for anything – frostschutz Jul 24 '13 at 10:29
  • 3
    Except listing the contents of filesystems? I find `find -maxdepth 1 -ls` to be a bit of a nuisance for that. ;) But joking temporarily aside, *parsing* `ls` is a baaaad idea unless you're doing something *very* constrained and well-defined. And probably not even then. – Alexios Jul 24 '13 at 11:14
  • Wondering who upvoted a `ls` call with `--reverse` instead of `--recursive`? – manatwork Jul 24 '13 at 12:43