12
$\begingroup$

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?

$\endgroup$
5
  • 2
    $\begingroup$ insertPiece[] won't work nicely on tarballs like stuff.tar.gz. $\endgroup$ – J. M.'s ennui Jan 23 '12 at 4:49
  • $\begingroup$ 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. $\endgroup$ – David Z Jan 23 '12 at 5:10
  • 2
    $\begingroup$ 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. $\endgroup$ – Simon Jan 23 '12 at 5:51
  • $\begingroup$ 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. $\endgroup$ – David Z Jan 23 '12 at 6:05
  • $\begingroup$ @David: That's what my code does. I was just pointing out that it's not what might always be wanted. $\endgroup$ – Simon Jan 23 '12 at 6:14
9
$\begingroup$

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"
$\endgroup$
4
9
$\begingroup$

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].
$\endgroup$
1
  • $\begingroup$ 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. $\endgroup$ – David Z Jan 23 '12 at 5:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.