# What's a robust way to insert another extension into a filename?

I'd like to add an extension to a filename before the file extension, otherwise leaving the given filename the same. In particular, absolute filenames should stay absolute, and relative filenames should stay relative. Examples: suppose the piece I want to insert is ".123", then

• /home/me/dir/foo.txt becomes /home/me/dir/foo.123.txt.
• xyz.csv becomes xyz.123.csv
• foobar becomes foobar.123 (just append in case of no extension)

I don't expect to be dealing with filenames that already have multiple extensions, but just in case, the desired behavior is

• nomnom.tar.gz becomes nomnom.123.tar.gz

I can ensure that the filename does not end with a slash.

The obvious way to do this is by concatenating the directory name, the file base name, the new piece, and the extension:

insertPiece[fn_, piece_] := FileNameJoin[{
DirectoryName[fn],
StringJoin[{FileBaseName[fn], piece, ".", FileExtension[fn]}]
}]


but is there some corner case I missed in which this wouldn't work? Is there a more efficient or more elegant way to do it?

-
insertPiece[] won't work nicely on tarballs like stuff.tar.gz. –  Guess who it is. Jan 23 '12 at 4:49
Ah, good point. Multi-extension files won't (or shouldn't) come up in the particular application I'm using this for, but I think I'll modify it to take that into account just in case. –  David Z Jan 23 '12 at 5:10
The whole filename extension thing is a bit ill-defined. In the example nomnom.123.tar.gz above, I would consider nomnom.123 to be the base filename and tar.gz to be the extension (I know I have files on my computer that I would want the code to view like this). Without having a list of "valid" file extensions, this would be hard to program in general. –  Simon Jan 23 '12 at 5:51
Here's the definition I'm using: split the last component of the filename on the character ".". The first element of the resulting list is the base filename, and all the rest of the elements in the list are extensions. –  David Z Jan 23 '12 at 6:05
@David: That's what my code does. I was just pointing out that it's not what might always be wanted. –  Simon Jan 23 '12 at 6:14

Here's my non-regex solution (but it does use a "String Pattern", which is equivalent to regex). I think it is robust.

insertExtension[fn_String, piece_String] :=
Module[{split = FileNameSplit[fn], temp},
temp = Insert[StringSplit[Last[split], "."], piece, 2];
temp = StringJoin[Riffle[temp, "."]];
FileNameJoin[Append[Most[split], temp]]]


Test:

In[]:= insertExtension["/home/me.em/dir.ab/nomnom.tar.gz", "123"]

Out[]= "/home/me.em/dir.ab/nomnom.123.tar.gz"

-
I think this one looks a little cleaner, so I'm accepting your answer. Though is there any reason to use Module instead of Block? –  David Z Jan 23 '12 at 21:40
@David: My rule of thumb is to use Block if I need to temporarily redefining (or undefine) and existing symbol inside the block. And Module for standard lexical scoping. Block can be slightly faster and since the above code is quite self contained (no calls to functions that might depend on Globalsplit or Globaltemp) it should be safe to use Block. –  Simon Jan 23 '12 at 22:28
–  Simon Jan 23 '12 at 22:29
–  Simon Jan 23 '12 at 22:49

String manipulation like this is probably done best using regular expressions. If you're not familiar with these, you may want to have a look at the Wikipedia article or the Mathematica tutorial on them first.

The code given below defines a pattern regex that matches an extension of the type .abcDeF... at the end of a string (that's accomplished by the $ sign). If that pattern does not match, then the extension is simply added; if the pattern matches, the extension is put in between the rest of the string and the match. Here's the code for that: appendExtension[filename_, extension_] := Block[{regex}, regex = RegularExpression["\\.[a-z]+$"];
If[Length@StringCases[filename, regex] == 0,
filename <> "." <> extension,
StringReplace[filename, regex -> ("." <> extension <> "$0")] ] ]; appendExtension["/home/me/dir/foo.txt", "123"] appendExtension["xyz.csv", "123"] appendExtension["foobar", "123"]  Output: "/home/me/dir/foo.123.txt" "xyz.123.csv" "foobar.123"  A few remarks: • I'm not sure how you want filenames like foo.bar.baz to be handled. The code above replaces this by foo.bar.123.baz, i.e. takes only the last extension into account. If you want the new one inserted as the leftmost one, change the regular expression to (\\.[a-z]+)+$, and the replacement will yield foo.123.bar.baz.
• The regex doesn't include upper case extensions or digits. To add these, change [a-z] inside the regex to [a-zA-Z0-9].
-
This should work nicely, especially because I'm very comfortable with regular expressions but I always forget that Mathematica can use them too :-) FWIW in a case with multiple extensions, the new one should be the first extension of the resulting filename, though as I mentioned in a comment on the question I don't expect that case to come up. But I'll edit my question accordingly. –  David Z Jan 23 '12 at 5:13