I know this is an old question, but here is another solution that can handle several points in the name, and also when there is no extension at all (or the extension is only "."):
/^(.*?)(\.[^.]*)?$/
Taking this apiece at the same time:
^
Anchor to the beginning of the line (to avoid partial matches)
(.*?)
Match any character . , 0 or more times * , lazily ? (don't just grab them all if the subsequent optional extension can match) and put them in the first capture group ( ) ,
(\.
Run the second capture group to expand with ( . This group starts with a literal character . (With which we run away with \ , so . Is not interpreted as "match any character").
[^.]*
Define the character set [] . Match non-character characters by specifying this is an inverted character set ^ . Match 0 or more characters not . to get the rest of the file extension * . We specify it in such a way that it does not match file names like foo.bar.baz , incorrectly providing an extension with more than one dot in it .bar.baz instead of just .baz . . no escaping inside [] is necessary since everything (except ^ ) is a literal in the character set.
)?
Complete the second capture group ) and indicate that the entire group is optional ? since it may not have an extension.
$
Anchor to the end of the line (again, to avoid partial matches)
If you use ES6, you can use destruction to get the results in 1 line:
[,filename, extension] = /^(.*?)(\.[^.]*)?$/.exec('foo.bar.baz'); which gives the file name as 'foo.bar' and the extension as '.baz' .
'foo' gives 'foo' and ''
'foo.' gives 'foo' and '.'
'.js' gives '' and '.js'
source share