Rename files to be capitalised but not impact on file extensions

Solution 1:

One way to achieve this might be to use Negative Lookbehind to only match the word-boundary - word character sequence when it is not preceded by a literal period e.g. given

$ ls
alan's file.txt  bar.txt  foo

then

$ rename -n 's/(?<!\.)\b\w*/\u$&/g' *
rename(alan's file.txt, Alan'S File.txt)
rename(bar.txt, Bar.txt)
rename(foo, Foo)

Assuming you also want to avoid capitalizing the pluralizing s as well, we might modify that to

$ rename -n 's/(?<![.'\''])\b\w*/\u$&/g' *
rename(alan's file.txt, Alan's File.txt)
rename(bar.txt, Bar.txt)
rename(foo, Foo)

or even

$ rename -n 's/(?<![[:punct:]])\b\w*/\u$&/g' *
rename(alan's file.txt, Alan's File.txt)
rename(bar.txt, Bar.txt)
rename(foo, Foo)

Solution 2:

This will capitalize the first letter of every word except the final extension:

rename -n 's/\b(.+?)\b/\u$1/g; s/(.+)\.(.)/$1\.\l$2/' *

I tested with these files:

$ ls -1
'a file with a '$'\n''new line.foo'
'a file with spaces.txt'
justonelongfilename.ext
no-extensions-here
this.has.many.extensions.pdf

And it will rename them as:

$ rename -n 's/\b(.+?)\b/\u$1/g; s/(.+)\.(.)/$1\.\l$2/' *
a file with a 
new line.foo -> A File With A 
New Line.foo
a file with spaces.txt -> A File With Spaces.txt
justonelongfilename.ext -> Justonelongfilename.ext
no-extensions-here -> No-Extensions-Here
this.has.many.extensions.pdf -> This.Has.Many.Extensions.pdf

The trick is to first capitalize every first letter, ignoring the extensions, and then go back and make the extension lower case:

  • s/\b(.+?)\b/\u$1/g; : The .+? is a non-greedy pattern, which means it will find the shortest possible match. Since it is anchored by word boundaries (\b), this will find all words (all because of the final g). These are then replaced with the capitalized (first letter capitalized) version of themselves (\l$1).

  • s/(.+)\.(.)/$1\.\l$2/ : the .+ is greedy, so it will find the longest possible match. This means the longest string until a final ., after which will be the extension (if any). We replace the match with everything before the extension ($1), a . and the extension with the first letter lower cased again (\l$2).


Recursion is easy enough as well. If your shell is bash (if you don't know, it probably is), you can use the globstar option which makes ** match 0 or more subdirectories:

shopt -s globstar

Now, run the rename command like this (this will also work on any files in your current directory):

rename -n 's/\b(.+?)\b/\u$1/g; s/(.*)\.(.)/$1\.\l$2/' **

To limit it to only files or directories with extensions, use **/*.* instead of **.

Alternatively, use find:

find /path/to/dir -exec rename -n 's/\b(.+?)\b/\u$1/g; s/(.*)\.(.)/$1\.\l$2/' {} +

And, to limit to those files and directories with an extension:

find /path/to/dir -name '*.*' -exec rename -n 's/\b(.+?)\b/\u$1/g; s/(.*)\.(.)/$1\.\l$2/' {} +

Note that all of these solutions will cheerfully rename directories as well as files. If you don't want that, be careful where you tell it to recurse, or give it a more specific pattern like *.txt.


In all examples, remove the -n to make them actually do something. The -n causes rename to simply print what it would do and not actually do anything. Useful for testing.

Solution 3:

Most sensible way would be to tackle all "words" ( with use of word boundary \b and referring via $1 to any first character) including the extension , but then lowercase the extension itself:

$ prename -nv 's/\b(.)/\u$1/g;s/^(.*)\.(.*)/$1.\l$2/' *                                                                                                                
another filename trispaced.txt renamed as Another Filename Trispaced.txt
filename_with_underschore.txt renamed as Filename_with_underschore.txt
one filename.txt renamed as One Filename.txt

Note that this doesn't work for filename with underscores, i.e. "word" boundaries are considered at blanks ( tabs, spaces, newlines - which hopefully shouldn't be in filename anyway).

Note use of prename for portability to ksh, where rename is actually a built-in command, not the standalone perl executable.

Solution 4:

Just use rename 's/\b(\w)/\u$1/' * WITHOUT the g flag.
The g flag means "global = do it multiple times". You make 1st time in filename, and you do not want to make uppercase in 2nd time right?