7

I am using this on a customer's directory to rename files with first letter of each word being capitalised as per their request:

rename 's/\b(\w)/\u$1/g' *

That works fine, but it also capitalises the first letter of the extension, so I use this to fix that:

rename 's/\.Txt$/.txt/' *.Txt

which works okay if most of the files in a folder are the same extension (generally true),but is a pain if they are very mixed.

To get around that issue, I created a small script that looks like this (don't laugh!):

#!/bin/bash
rename 's/\.Txt$/.txt/' *.Txt
rename 's/\.Doc$/.doc/' *.Doc
rename 's/\.Docx$/.docx/' *.Docx
...
rename 's/\.Xlsx$/.xlsx/' *.Xlsx

I ignore the 'Can't rename *.Txt *.txt: No such file or directory' errors, and if I find an extension that is missing, I just add that line to my script.

I don't think this will matter, but the files are on a Windows server fileshare, but I am accessing it using Ubuntu 16.04LTS. If it does matter, then I could copy them to my local drive first, run a command in Ubuntu, then move the files back if required.

Is there any way to amend the first rename command to ignore the extensions and leave them as lowercase? Can I run the new command in a higher level directory, and have it recurse through all the sub-directories?

2
  • So in other words, you want filename.txt to become Filename.txt , correct ? Can you give an example ? Oct 30, 2017 at 23:10
  • Hi Sergiy - Yes that would be a correct example. This would be another: 'alan's file.txt' to become 'Alan's File.txt'
    – Alan
    Oct 30, 2017 at 23:11

4 Answers 4

7

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)
2
  • Perfect! I will use this one: rename -n 's/(?<![.'\''])\b\w*/\u$&/g' * Thank you for your help!
    – Alan
    Oct 31, 2017 at 2:00
  • @Alan make sure to run the command to make sure it outputs the right renamings, and then remove the -n to actually rename. Oct 31, 2017 at 4:08
5

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.

2
  • Smart move, capitalizing all first letters, then lowercasing the extension. +1'ed Oct 30, 2017 at 23:57
  • Hi Terdon: I just tested rename -n 's/\b(.+?)\b/\u$1/g; s/(.+)\.(.)/$1\.\l$2/' * and it almost works, but it also capitalises the possessive 's of Alan's. Looks like SteelDriver has an nswer below with rename -n 's/(?<![.'\''])\b\w*/\u$&/g' * Thank you for your help!
    – Alan
    Oct 31, 2017 at 1:59
4

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.

8
  • Hi Sergiy - If I do that, will I need to list every possible extension in the array at the end of the command? Also, it sems to rename alan's file.txt as Alan's file.txt (does not capitalise the second word).
    – Alan
    Oct 30, 2017 at 23:17
  • @Alan ah, so you wanted all words of filename to be capitalized ? Can you please edit your original post to include that information ? As for extensions - yes, that would be correct, an "array" of all possible combinations. Oct 30, 2017 at 23:22
  • Hi Sergiy - It now reads rename files with first letter of each word being capitalised is that okay? In terms of the array, I could do *.*?
    – Alan
    Oct 30, 2017 at 23:26
  • @Alan yeah, *.* would be alright, if there's going to be just one . in file name, i.e. no foo.bar.txt. Thanks for the edit, btw, I'll work on editing my answer now Oct 30, 2017 at 23:37
  • @Sergiy *.* should match all non-hidden files with an extension, including foo.bar.txt, so *.* should work fine. No need to give a list of extensions.
    – terdon
    Oct 30, 2017 at 23:49
1

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?

7
  • Hi V-Mark - I tried that, but it renames 'zz alan.txt' to 'Alan's file.txt' (it only capitalises the first word, but not the other words. They are wanting each word capitalised).
    – Alan
    Oct 30, 2017 at 23:15
  • @Alan this would be so much easier to understand if you just included some examples in your question and what you would like them to be renamed to. Please make sure to be specific, your comment here states that zz alan.txt was renamed to Alan's file.txt which is not very likely :)
    – terdon
    Oct 30, 2017 at 23:35
  • @Alan What if you leave your original rename, but make the extension lowercase with rename 's/(.*)\.(\w)(.*)/$1.\l$2$3/' * This makes the word before last period lowercase. So from Qwe.Asd.Zxc to Qwe.Asd.zxc `
    – V-Mark
    Oct 30, 2017 at 23:48
  • ...and you can put these renames together...
    – V-Mark
    Oct 30, 2017 at 23:56
  • Hi V-Mark - Not sure how that is different than what I am currently doing (albeit probably more efficiently). I would still be running two commands?
    – Alan
    Oct 31, 2017 at 1:51

You must log in to answer this question.

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