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?
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
then
Assuming you also want to avoid capitalizing the pluralizing
s
as well, we might modify that toor even
This will capitalize the first letter of every word except the final extension:
I tested with these files:
And it will rename them as:
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 finalg
). 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:Now, run the rename command like this (this will also work on any files in your current directory):
To limit it to only files or directories with extensions, use
**/*.*
instead of**
.Alternatively, use
find
:And, to limit to those files and directories with an extension:
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
causesrename
to simply print what it would do and not actually do anything. Useful for testing.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: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 toksh
, whererename
is actually a built-in command, not the standaloneperl
executable.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?