Extract file extension: Difference between revisions

m
(Added Algol 68 and Algol W)
 
(161 intermediate revisions by 74 users not shown)
Line 1:
{{draft task}}
[[wp:Filename extension|Filename extensions]] are a rudimentary but commonly used way of identifying files types.
 
Write a program that takes one string argument representing the path to a file and returns the file's extension, or the null string if the file path has no extension. An extension appears after the last period in the file name and consists of one or more letters or numbers.
 
;Task:
Show here the action of your routine on the following examples:
Write a function or program that
* takes one string argument representing the path/URL to a file
* returns the filename extension according to the below specification, or an empty string if the filename has no extension.
 
# picture.jpg returns .jpg
# <nowiki>http://mywebsite.com/picture/image.png</nowiki> returns .png
# myuniquefile.longextension returns .longextension
# IAmAFileWithoutExtension returns an empty string ""
# /path/to.my/file returns an empty string as the period is in the directory name rather than the file
# file.odd_one returns an empty string as an extension (by this definition), cannot contain an underscore.
 
If your programming language &nbsp;(or standard library)&nbsp; has built-in functionality for extracting a filename extension,
<br>show how it would be used and how exactly its behavior differs from this specification.
 
 
;Specification:
For the purposes of this task, a filename extension:
* occurs at the very end of the filename
* consists of a period, followed solely by one or more ASCII letters or digits (A-Z, a-z, 0-9)
 
 
;Test cases:
::::{| class="wikitable"
|-
! Input
! Output
! Comment
|-
| <code><nowiki>http://example.com/download.tar.gz</nowiki></code>
| <code>.gz</code>
|
|-
| <code>CharacterModel.3DS</code>
| <code>.3DS</code>
|
|-
| <code>.desktop</code>
| <code>.desktop</code>
|
|-
| <code>document</code>
| <code></code>
| ''empty string''
|-
| <code>document.txt_backup</code>
| <code></code>
| ''empty string, because <code>_</code> is not a letter or number''
|-
| <code>/etc/pam.d/login</code>
| <code></code>
| ''empty string, as the period is in the parent directory name rather than the filename''
|}
 
 
{{Template:Strings}}
<br><br>
 
=={{header|11l}}==
 
{{trans|Python}}
<syntaxhighlight lang="11l">F extract_ext(path)
V m = re:‘\.[A-Za-z0-9]+$’.search(path)
R I m {m.group(0)} E ‘’
 
V paths = [‘http://example.com/download.tar.gz’,
‘CharacterModel.3DS’,
‘.desktop’,
‘document’,
‘document.txt_backup’,
‘/etc/pam.d/login’]
 
L(path) paths
print(path.rjust(max(paths.map(p -> p.len)))‘ -> ’extract_ext(path))</syntaxhighlight>
{{out}}
<pre>
http://example.com/download.tar.gz -> .gz
CharacterModel.3DS -> .3DS
.desktop -> .desktop
document ->
document.txt_backup ->
/etc/pam.d/login ->
</pre>
 
=={{header|Action!}}==
{{libheader|Action! Tool Kit}}
<syntaxhighlight lang="action!">INCLUDE "D2:CHARTEST.ACT" ;from the Action! Tool Kit
 
PROC FileExt(CHAR ARRAY path,ext)
BYTE pos,c
 
pos=path(0)
ext(0)=0
WHILE pos>0
DO
c=path(pos)
IF c='. THEN
EXIT
ELSEIF IsDigit(c)=0 AND IsAlpha(c)=0 THEN
RETURN
FI
pos==-1
OD
IF pos=0 THEN
RETURN
FI
SCopyS(ext,path,pos,path(0))
RETURN
 
PROC Test(CHAR ARRAY path)
CHAR ARRAY ext(10)
 
FileExt(path,ext)
PrintF("""%S"":%E""%S""%E%E",path,ext)
RETURN
 
PROC Main()
Put(125) PutE() ;clear the screen
 
Test("http://example.com/download.tar.gz")
Test("CharacterModel.3DS")
Test(".desktop")
Test("document")
Test("document.txt_backup")
Test("/etc/pam.d/login")
RETURN</syntaxhighlight>
{{out}}
[https://gitlab.com/amarok8bit/action-rosetta-code/-/raw/master/images/Extract_file_extension.png Screenshot from Atari 8-bit computer]
<pre>
"http://example.com/download.tar.gz":
".gz"
 
"CharacterModel.3DS":
".3DS"
 
".desktop":
".desktop"
 
"document":
""
 
"document.txt_backup":
""
 
"/etc/pam.d/login":
""
</pre>
 
=={{header|Ada}}==
===As originally specified===
<syntaxhighlight lang="ada">with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Fixed; use Ada.Strings.Fixed;
use Ada.Strings;
with Ada.Characters.Handling; use Ada.Characters.Handling;
 
procedure Main is
function extension (S : in String) return String is
P_Index : Natural;
begin
P_Index :=
Index (Source => S, Pattern => ".", From => S'Last, Going => Backward);
if P_Index = 0 then
return "";
else
for C of S (P_Index + 1 .. S'Last) loop
if not Is_Alphanumeric (C) then
return "";
end if;
end loop;
return S (P_Index .. S'Last);
end if;
end extension;
F1 : String := "http://example.com/download.tar.gz";
F2 : String := "CharacterModel.3DS";
F3 : String := ".desktop";
F4 : String := "document";
F5 : String := "document.txt_backup";
F6 : String := "/etc/pam.d/login:";
begin
Put_Line (F1 & " -> " & extension (F1));
Put_Line (F2 & " -> " & extension (F2));
Put_Line (F3 & " -> " & extension (F3));
Put_Line (F4 & " -> " & extension (F4));
Put_Line (F5 & " -> " & extension (F5));
Put_Line (F6 & " -> " & extension (F6));
end Main;
</syntaxhighlight>
{{output}}
<pre>
http://example.com/download.tar.gz -> .gz
CharacterModel.3DS -> .3DS
.desktop -> .desktop
document ->
document.txt_backup ->
/etc/pam.d/login: ->
</pre>
 
===In response to problem discussions===
<syntaxhighlight lang="ada">with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Fixed; use Ada.Strings.Fixed;
use Ada.Strings;
with Ada.Characters.Handling; use Ada.Characters.Handling;
 
procedure Main is
function extension (S : in String) return String is
P_Index : Natural;
begin
P_Index :=
Index (Source => S, Pattern => ".", From => S'Last, Going => Backward);
if P_Index < 2 or else P_Index = S'Last then
return "";
else
for C of S (P_Index + 1 .. S'Last) loop
if not Is_Alphanumeric (C) then
return "";
end if;
end loop;
return S (P_Index .. S'Last);
end if;
end extension;
F1 : String := "http://example.com/download.tar.gz";
F2 : String := "CharacterModel.3DS";
F3 : String := ".desktop";
F4 : String := "document";
F5 : String := "document.txt_backup";
F6 : String := "/etc/pam.d/login:";
F7 : String := "filename.";
F8 : String := ".";
begin
Put_Line (F1 & " -> " & extension (F1));
Put_Line (F2 & " -> " & extension (F2));
Put_Line (F3 & " -> " & extension (F3));
Put_Line (F4 & " -> " & extension (F4));
Put_Line (F5 & " -> " & extension (F5));
Put_Line (F6 & " -> " & extension (F6));
Put_Line (F7 & " -> " & extension (F7));
Put_Line (F8 & " -> " & extension (F8));
end Main;
</syntaxhighlight>
{{output}}
<pre>
http://example.com/download.tar.gz -> .gz
CharacterModel.3DS -> .3DS
.desktop ->
document ->
document.txt_backup ->
/etc/pam.d/login: ->
filename. ->
. ->
</pre>
 
=={{header|ALGOL 68}}==
 
{{works with|ALGOL 68G|Any - tested with release 2.8.win32}}
<langsyntaxhighlight lang="algol68"># extracts a file-extension from the end of a pathname. The file extension is #
# defined as a dot followed by one or more letters or digits #
OP EXTENSION = ( STRING pathname )STRING:
Line 57 ⟶ 293:
main:
( test extension( "picturehttp://example.jpgcom/download.tar.gz", ".jpggz" )
; test extension( "http://mywebsiteCharacterModel.com/picture/image.png3DS", ".png" ".3DS" )
; test extension( "myuniquefile.longextensiondesktop", ".longextensiondesktop" )
; test extension( "IAmAFileWithoutExtensiondocument", "" "" )
; test extension( "/path/todocument.my/filetxt_backup", "" )
; test extension( "file/etc/pam.odd_oned/login", "" )
)</langsyntaxhighlight>
{{out}}
<pre>
picturehttp://example.com/download.tar.jpggz got extension: (.jpggz) as expected
http://mywebsite.com/picture/imageCharacterModel.png3DS got extension: (.png3DS) as expected
myuniquefile.longextensiondesktop got extension: (.longextensiondesktop) as expected
IAmAFileWithoutExtensiondocument got extension: () as expected
/path/todocument.my/filetxt_backup got extension: () as expected
file/etc/pam.odd_oned/login got extension: () as expected</pre>
</pre>
 
=={{header|ALGOL W}}==
 
<lang algolw>begin
<syntaxhighlight lang="algolw">begin
% extracts a file-extension from the end of a pathname. %
% The file extension is defined as a dot followed by one or more letters %
Line 144 ⟶ 380:
end ; % text extension %
testExtension( "picturehttp://example.jpgcom/download.tar.gz", ".jpggz" );
testExtension( "http://mywebsiteCharacterModel.com/picture/image.png3DS", ".png" ".3DS" );
testExtension( "myuniquefile.longextensiondesktop", ".longextensiondesktop" );
testExtension( "IAmAFileWithoutExtensiondocument", "" "" );
testExtension( "/path/todocument.my/filetxt_backup", "" );
testExtension( "file/etc/pam.odd_oned/login", "" );
 
end.</langsyntaxhighlight>
{{out}}
<pre>
picturehttp://example.jpg com/download.tar.gz -> (.jpggz ) as expected
http://mywebsiteCharacterModel.com/picture/image.png3DS -> (.png3DS ) as expected
myuniquefile.longextensiondesktop -> (.longextensiondesktop ) as expected
IAmAFileWithoutExtensiondocument -> ( ) as expected
/path/todocument.my/file txt_backup -> ( ) as expected
file/etc/pam.odd_one d/login -> ( ) as expected
</pre>
 
=={{header|AppleScript}}==
AppleScript paths can have either of two formats, depending on the system used to access the items on a disk or network.
The current task specification implies that the slash-separated "POSIX" format is intended in all cases.
Some macOS "files" are actually directories called "bundles" or "packages". Their paths may or may not end with separators.
Underscores are valid extension characters in macOS and extensions are returned without leading dots.
When extracting extensions in AppleScript, one would normally follow the rules for macOS, but variations are possible.
The task specification is taken at its word that the input strings do represent paths to files.
 
===Vanilla===
<syntaxhighlight lang="applescript">on getFileNameExtension from txt given underscores:keepingUnderscores : true, dot:includingDot : false
set astid to AppleScript's text item delimiters
-- Extract the file or bundle name from the path.
set AppleScript's text item delimiters to "/"
if (txt ends with "/") then
set itemName to text item -2 of txt
else
set itemName to text item -1 of txt
end if
-- Extract the extension.
if (itemName contains ".") then
set AppleScript's text item delimiters to "."
set extn to text item -1 of itemName
if ((not keepingUnderscores) and (extn contains "_")) then set extn to ""
if ((includingDot) and (extn > "")) then set extn to "." & extn
else
set extn to ""
end if
set AppleScript's text item delimiters to astid
return extn
end getFileNameExtension
 
set output to {}
repeat with thisString in {"http://example.com/download.tar.gz", "CharacterModel.3DS", ".desktop", "document", "document.txt_backup", "/etc/pam.d/login"}
set end of output to {thisString's contents, getFileNameExtension from thisString with dot without underscores}
end repeat
 
return output</syntaxhighlight>
 
{{output}}
<pre>{{"http://example.com/download.tar.gz", ".gz"}, {"CharacterModel.3DS", ".3DS"}, {".desktop", ".desktop"}, {"document", ""}, {"document.txt_backup", ""}, {"/etc/pam.d/login", ""}}</pre>
 
===ASObjC===
 
AppleScriptObjectiveC makes the task a little easier, but not necessarily more efficient.
 
<syntaxhighlight lang="applescript">use AppleScript version "2.4" -- Mac OS X 10.10 (Yosemite) or later.
use framework "Foundation"
 
on getFileNameExtension from txt given underscores:keepingUnderscores : true, dot:includingDot : false
-- Get an NSString version of the text and extract the 'pathExtension' from that as AppleScript text.
set txt to current application's class "NSString"'s stringWithString:(txt)
set extn to txt's pathExtension() as text
if ((not keepingUnderscores) and (extn contains "_")) then set extn to ""
if ((includingDot) and (extn > "")) then set extn to "." & extn
return extn
end getFileNameExtension
 
set output to {}
repeat with thisString in {"http://example.com/download.tar.gz", "CharacterModel.3DS", ".desktop", "document", "document.txt_backup", "/etc/pam.d/login"}
set end of output to {thisString's contents, getFileNameExtension from thisString with dot without underscores}
end repeat
 
return output</syntaxhighlight>
 
{{output}}
<pre>{{"http://example.com/download.tar.gz", ".gz"}, {"CharacterModel.3DS", ".3DS"}, {".desktop", ".desktop"}, {"document", ""}, {"document.txt_backup", ""}, {"/etc/pam.d/login", ""}}</pre>
 
=={{header|AutoHotkey}}==
<syntaxhighlight lang="autohotkey">data := ["http://example.com/download.tar.gz"
,"CharacterModel.3DS"
,".desktop"
,"document"
,"document.txt_backup"
,"/etc/pam.d/login"]
 
for i, file in data{
RegExMatch(file, "`am)\.\K[a-zA-Z0-9]+$", ext)
result .= file " --> " ext "`n"
}
MsgBox % result</syntaxhighlight>
{{out}}
<pre>http://example.com/download.tar.gz --> gz
CharacterModel.3DS --> 3DS
.desktop --> desktop
document -->
document.txt_backup -->
/etc/pam.d/login --> </pre>
 
=={{header|AWK}}==
 
<lang AWK>
The following code shows two methods.
# syntax: GAWK -f EXTRACT_FILE_EXTENSION.AWK
 
The first one was provided by an earlier contributor and shows a little more awk syntax and builtins (albeit with a bug fixed: it was testing for underscores in the extension but not other characters such as hyphens). It can be adjusted to allow any character in the extension other than /, \, : or . by replacing <code>[^a-zA-Z0-9]</code> with <code>[\\/\\\\:\\.]</code>.
 
<syntaxhighlight lang="awk">
BEGIN {
arr[++i] = "picture.jpg"
Line 172 ⟶ 502:
arr[++i] = "/path/to.my/file"
arr[++i] = "file.odd_one"
 
for (j=1; j<=i; j++) {
printf("%-40s '%s'\n",arr[j],extract_ext(arr[j]))
Line 185 ⟶ 516:
tmp = 1
}
if (fn ~ /_[^a-zA-Z0-9]/ || tmp == 0) {
return("")
}
return(fn)
}
</syntaxhighlight>
</lang>
 
<p>Output:</p>
The second method is shorter and dispenses with the need to search for and remove the path components first. It too can be modified to allow all valid extensions (not just those described in the specification), by replacing <code>\\.[A-Za-z0-9]+$</code> with <code>\\.[^\\/\\\\:\\.]+$</code>.
 
<syntaxhighlight lang="awk">
BEGIN {
arr[++i] = "picture.jpg"
arr[++i] = "http://mywebsite.com/picture/image.png"
arr[++i] = "myuniquefile.longextension"
arr[++i] = "IAmAFileWithoutExtension"
arr[++i] = "/path/to.my/file"
arr[++i] = "file.odd_one"
 
for (j=1; j<=i; j++) {
printf("%-40s '%s'\n",arr[j],extract_ext(arr[j]))
}
exit(0)
}
function extract_ext(fn, pos) {
pos = match(fn, "\\.[^\\/\\\\:\\.]+$")
if (pos == 0) {
return ("")
} else {
return (substr(fn,pos+1))
}
}
</syntaxhighlight>
<p>Both examples give the output:</p>
<pre>
picture.jpg 'jpg'
Line 200 ⟶ 557:
file.odd_one ''
</pre>
 
=={{header|Batch File}}==
<syntaxhighlight lang="dos">@echo off
 
:loop
if "%~1"=="" exit /b
echo File Path: "%~1" ^| File Extension "%~x1"
shift
goto loop</syntaxhighlight>
{{out}}
<pre>File Path: "http://example.com/download.tar.gz" | File Extension ".gz"
File Path: "CharacterModel.3DS" | File Extension ".3DS"
File Path: ".desktop" | File Extension ".desktop"
File Path: "document" | File Extension ""
File Path: "document.txt_backup" | File Extension ".txt_backup"
File Path: "/etc/pam.d/login" | File Extension ""</pre>
 
=={{header|BCPL}}==
<syntaxhighlight lang="bcpl">get "libhdr"
 
// Find filename extension, store at `v'
let extension(s, v) = valof
$( let loc = valof
$( for i=s%0 to 1 by -1
$( let c = s%i
if c = '.'
resultis i
unless 'A'<=c<='Z' | 'a'<=c<='z' | '0'<=c<='9'
resultis 0
$)
resultis 0
$)
test loc=0 do
v%0 := 0
or
$( v%0 := s%0-loc+1
for i=1 to v%0 do
v%i := s%(i+loc-1)
$)
resultis v
$)
 
let show(s) be
$( let v = vec 32
writef("*"%S*": *"%S*"*N", s, extension(s, v))
$)
 
let start() be
$( show("http://example.com/download.tar.gz")
show("CharacterModel.3DS")
show(".desktop")
show("document")
show("document.txt_backup")
show("/etc/pam.d/login")
$)</syntaxhighlight>
{{out}}
<pre>"http://example.com/download.tar.gz": ".gz"
"CharacterModel.3DS": ".3DS"
".desktop": ".desktop"
"document": ""
"document.txt_backup": ""
"/etc/pam.d/login": ""</pre>
 
=={{header|C}}==
 
<lang C>
<syntaxhighlight lang="c">#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
 
/* Returns a pointer to the extension of 'string'. If no extension is found,
* thenIf no extension is found, returns a pointer to the null-terminatorend of 'string'. */
char* file_ext(const char *string)
{
Line 218 ⟶ 638:
 
for (char *iter = ext + 1; *iter != '\0'; iter++) {
if (!isalnum((unsigned char)*iter))
return (char*) string + strlen(string);
}
 
return ext + 1;
}
 
int main(void)
{
const char *stringstestcases[][2] = {
{"picturehttp://example.jpgcom/download.tar.gz", ".gz"},
{"http://mywebsiteCharacterModel.con/picture/image3DS", ".png3DS"},
{"myuniquefile.longextensiondesktop", ".desktop"},
{"IAmAFileWithoutExtensiondocument", ""},
{"/path/todocument.my/filetxt_backup", ""},
{"file/etc/pam.odd_oned/login", ""}
};
 
int exitcode = 0;
for (int i = 0; i < sizeof(strings) / sizeof(strings[0]); ++i) {
for (size_t i = 0; i < sizeof(testcases) / sizeof(testcases[0]); i++) {
printf("'%s' - '%s'\n", strings[i], file_ext(strings[i]));
const char *ext = file_ext(testcases[i][0]);
if (strcmp(ext, testcases[i][1]) != 0) {
fprintf(stderr, "expected '%s' for '%s', got '%s'\n",
testcases[i][1], testcases[i][0], ext);
exitcode = 1;
}
}
return exitcode;
}
}</syntaxhighlight>
</lang>
 
=={{header|C sharp|C#}}==
 
<syntaxhighlight lang="[[c sharp|c#]]">public static string FindExtension(string filename) {
int indexOfDot = filename.Length;
for (int i = filename.Length - 1; i >= 0; i--) {
char c = filename[i];
if (c == '.') {
indexOfDot = i;
break;
}
if (c >= '0' && c <= '9') continue;
if (c >= 'A' && c <= 'Z') continue;
if (c >= 'a' && c <= 'z') continue;
break;
}
//The dot must be followed by at least one other character,
//so if the last character is a dot, return the empty string
return indexOfDot + 1 == filename.Length ? "" : filename.Substring(indexOfDot);
}</syntaxhighlight>
 
'''Using regular expressions (C# 6)'''
<syntaxhighlight lang="[[c sharp|c#]]">public static string FindExtension(string filename) => Regex.Match(filename, @"\.[A-Za-z0-9]+$").Value;</syntaxhighlight>
 
=={{header|C++}}==
 
<syntaxhighlight lang="cpp">#include <iostream>
#include <filesystem>
 
int main() {
for (std::filesystem::path file : { "picture.jpg",
"http://mywebsite.com/picture/image.png",
"myuniquefile.longextension",
"IAmAFileWithoutExtension",
"/path/to.my/file",
"file.odd_one",
"thisismine." }) {
std::cout << file << " has extension : " << file.extension() << '\n' ;
}
}</syntaxhighlight>
{{out}}
<samp><pre>"picture.jpg" has extension : ".jpg"
"http://mywebsite.com/picture/image.png" has extension : ".png"
"myuniquefile.longextension" has extension : ".longextension"
"IAmAFileWithoutExtension" has extension : ""
"/path/to.my/file" has extension : ""
"file.odd_one" has extension : ".odd_one"
"thisismine." has extension : "."</pre></samp>
 
=={{header|Clojure}}==
<syntaxhighlight lang="clojure">
(defn file-extension [s]
(second (re-find #"(\.[a-zA-Z0-9]+)$" s)))
</syntaxhighlight>
 
{{out}}
<pre>
(map file-extension ["http://example.com/download.tar.gz"
'picture.jpg' - 'jpg'
"CharacterModel.3DS"
'http://mywebsite.con/picture/image.png' - 'png'
".desktop"
'myuniquefile.longextension' - 'longextension'
"document"
'IAmAFileWithoutExtension' - ''
"document.txt_backup"
'/path/to.my/file' - ''
"/etc/pam.d/login"])
'file.odd_one' - ''
 
(".gz" ".3DS" ".desktop" nil nil nil)
</pre>
 
=={{header|C sharp|C#CLU}}==
CLU contains a built-in filename parser, which behaves slightly differently than
<lang [[C sharp|C#]]>
the task specification. It returns the <em>first</em>, rather than last dotted part,
public static string ExtractExtension(string str)
and also accepts non-alphanumeric characters in the extension. Furthermore, it does
not include the dot itself in its output.
 
<syntaxhighlight lang="clu">% Find the extension of a filename, according to the task specification
extension = proc (s: string) returns (string)
for i: int in int$from_to_by(string$size(s), 1, -1) do
c: char := s[i]
if c>='A' & c<='Z'
| c>='a' & c<='z'
| c>='0' & c<='9' then continue end
if c='.' then return(string$rest(s,i)) end
break
end
return("")
end extension
% For each test case, show both the extension according to the task,
% and the extension that the built-in function returns.
start_up = proc ()
po: stream := stream$primary_output()
tests: sequence[string] := sequence[string]$[
"http://example.com/download.tar.gz",
"CharacterModel.3DS",
".desktop",
"document",
"document.txt_backup",
"/etc/pam.d/login"
]
stream$putleft(po, "Input", 36)
stream$putleft(po, "Output", 10)
stream$putl(po, "Built-in")
stream$putl(po, "---------------------------------------------------------")
for test: string in sequence[string]$elements(tests) do
stream$putleft(po, test, 36)
stream$putleft(po, extension(test), 10)
% Using the built-in filename parser
stream$putl(po, file_name$parse(test).suffix)
except when bad_format:
stream$putl(po, "[bad_format signaled]")
end
end
end start_up</syntaxhighlight>
{{out}}
<pre>Input Output Built-in
---------------------------------------------------------
http://example.com/download.tar.gz .gz tar
CharacterModel.3DS .3DS 3DS
.desktop .desktop desktop
document
document.txt_backup txt_backup
/etc/pam.d/login</pre>
 
=={{header|Common Lisp}}==
 
<syntaxhighlight lang="lisp">(pathname-type "foo.txt")
=>
"txt"</syntaxhighlight>
 
=={{header|D}}==
=== Variant 1===
<syntaxhighlight lang="d">
import std.stdio;
import std.path;
 
void main()
{
auto filenames = ["http://example.com/download.tar.gz",
string s = str;
string temp = "CharacterModel.3DS";,
string result = ".desktop";,
bool isDotFound = false; "document",
"document.txt_backup",
"/etc/pam.d/login"]
 
foreach(filename; filenames)
for (int i = s.Length -1; i >= 0; i--)
writeln(filename, " -> ", filename.extension);
{
if(s[i].Equals('.'))
}
{
</syntaxhighlight>
temp += s[i];
isDotFound = true;
break;
}
else
{
temp += s[i];
}
}
 
{{out}}
if(!isDotFound)
<pre>
{
http://example.com/download.tar.gz -> .gz
result = "";
CharacterModel.3DS -> .3DS
}
.desktop ->
else
document ->
{
document.txt_backup -> .txt_backup
for (int j = temp.Length - 1; j >= 0; j--)
/etc/pam.d/login ->
{
</pre>
result += temp[j];
}
}
 
=== Variant 2===
return result;
<syntaxhighlight lang="d">
import std.stdio;
import std.string;
import std.range;
import std.algorithm;
 
void main()
{
auto filenames = ["http://example.com/download.tar.gz",
"CharacterModel.3DS",
".desktop",
"document",
"document.txt_backup",
"/etc/pam.d/login"]
 
foreach(filename; filenames)
{
string ext;
auto idx = filename.lastIndexOf(".");
if(idx >= 0)
{
auto tmp = filename.drop(idx);
if(!tmp.canFind("/", "\\", "_", "*");
ext = tmp;
}
writeln(filename, " -> ", ext);
}
}
 
</lang>
</syntaxhighlight>
{{out}}
<pre>
http://example.com/download.tar.gz -> .gz
CharacterModel.3DS -> .3DS
.desktop -> .desktop
document ->
document.txt_backup ->
/etc/pam.d/login ->
</pre>
=={{header|Delphi}}==
{{libheader| System.SysUtils}}
{{libheader| System.Character}}
<syntaxhighlight lang="delphi">
program Extract_file_extension;
 
{$APPTYPE CONSOLE}
 
uses
System.SysUtils,
System.Character;
 
const
TEST_CASES: array[0..5] of string = ('http://example.com/download.tar.gz',
'CharacterModel.3DS', '.desktop', 'document', 'document.txt_backup',
'/etc/pam.d/login');
 
function GetExt(path: string): string;
var
c: char;
begin
// Built-in functionality, just extract substring after dot char
Result := ExtractFileExt(path);
 
// Fix ext for dot in subdir
while (Result.IndexOf('/') > -1) do
begin
Result := Result.Substring(Result.IndexOf('/'), MaxInt);
Result := ExtractFileExt(Result);
end;
 
// Ignore empty or "." ext
if length(result) < 2 then
exit('');
 
// Ignore ext with not alphanumeric char (except the first dot)
for var i := 2 to length(result) do
begin
c := result[i];
if not c.IsLetterOrDigit then
exit('');
end;
end;
 
begin
for var path in TEST_CASES do
Writeln(path.PadRight(40), GetExt(path));
 
{$IFNDEF UNIX} readln; {$ENDIF}
end.</syntaxhighlight>
{{out}}
<pre>http://example.com/download.tar.gz .gz
CharacterModel.3DS .3DS
.desktop .desktop
document
document.txt_backup
/etc/pam.d/login</pre>
 
=={{header|EasyLang}}==
<syntaxhighlight>
func isalphanum c$ .
c = strcode c$
return if c >= 65 and c <= 90 or c >= 97 and c <= 122 or c >= 48 and c <= 57
.
func$ exext path$ .
for i = len path$ downto 1
c$ = substr path$ i 1
if isalphanum c$ = 1
ex$ = c$ & ex$
elif c$ = "."
return ex$
else
break 1
.
.
.
for s$ in [ "http://example.com/download.tar.gz" "CharacterModel.3DS" ".desktop" "document" "document.txt_backup" "/etc/pam.d/login" ]
print s$ & " -> " & exext s$
.
</syntaxhighlight>
 
=={{header|Emacs Lisp}}==
 
<lang Lisp>(file-name-extension "foo.txt")
<syntaxhighlight lang="lisp">(file-name-extension "foo.txt")
=>
"txt"</langsyntaxhighlight>
 
No extension is distinguished from empty extension but an <code>(or ... "")</code> can give <code>""</code> for both if desired
 
<langsyntaxhighlight Lisplang="lisp">(file-name-extension "foo.") => ""
(file-name-extension "foo") => nil</langsyntaxhighlight>
 
An Emacs backup <code>~</code> or <code>.~NUM~</code> are not part of the extension, but otherwise any characters are allowed.
 
<langsyntaxhighlight Lisplang="lisp">(file-name-extension "foo.txt~") => "txt"
(file-name-extension "foo.txt.~1.234~") => "txt"</langsyntaxhighlight>
 
=={{header|GoFactor}}==
Factor's <tt>file-extension</tt> word allows symbols to be in the extension and omits the dot from its output.
<lang go>package main
<syntaxhighlight lang="factor">USING: assocs formatting kernel io io.pathnames math qw
sequences ;
IN: rosetta-code.file-extension
 
qw{
import (
http://example.com/download.tar.gz
"fmt"
CharacterModel.3DS
"path"
.desktop
)
document
document.txt_backup
/etc/pam.d/login
}
 
dup [ file-extension ] map zip
// An exact copy of `path.Ext` from Go 1.4.2 for reference:
"Path" "| Extension" "%-35s%s\n" printf
func Ext(path string) string {
47 [ "-" write ] times nl
for i := len(path) - 1; i >= 0 && path[i] != '/'; i-- {
[ "%-35s| %s\n" vprintf ] each</syntaxhighlight>
if path[i] == '.' {
{{out}}
return path[i:]
<pre>
}
Path | Extension
}
-----------------------------------------------
return ""
http://example.com/download.tar.gz | gz
CharacterModel.3DS | 3DS
.desktop | desktop
document |
document.txt_backup | txt_backup
/etc/pam.d/login |
</pre>
 
=={{header|Forth}}==
 
<syntaxhighlight lang="forth">: invalid? ( c -- f )
toupper dup [char] A [char] Z 1+ within
swap [char] 0 [char] 9 1+ within or 0= ;
: extension ( addr1 u1 -- addr2 u2 )
dup 0= if exit then
2dup over +
begin 1- 2dup <= while dup c@ invalid? until then
\ no '.' found
2dup - 0> if 2drop dup /string exit then
\ invalid char
dup c@ [char] . <> if 2drop dup /string exit then
swap -
\ '.' is last char
2dup 1+ = if drop dup then
/string ;
 
: type.quoted ( addr u -- )
[char] ' emit type [char] ' emit ;
: test ( addr u -- )
2dup type.quoted ." => " extension type.quoted cr ;
: tests
s" http://example.com/download.tar.gz" test
s" CharacterModel.3DS" test
s" .desktop" test
s" document" test
s" document.txt_backup" test
s" /etc/pam.d/login" test ;</syntaxhighlight>
{{out}}
<pre>cr tests
'http://example.com/download.tar.gz' => '.gz'
'CharacterModel.3DS' => '.3DS'
'.desktop' => '.desktop'
'document' => ''
'document.txt_backup' => ''
'/etc/pam.d/login' => ''
ok</pre>
 
=={{header|Fortran}}==
The plan is to scan backwards from the end of the text until a non-extensionish character is encountered. If it is a period, then a valid file extension has been spanned. Otherwise, no extension. Yet again the "no specification" on the possibility of shortcut evaluation of compound logical expressions prevents the structured use of a DO WHILE(L1 > 0 & TEXT(L1:L1)''etc'') loop because the possible evaluation of both parts of the expression means that the second part may attempt to access character zero of a text. So, the compound expression has to be broken into two separate parts.
 
The source incorporates a collection of character characterisations via suitable spans of a single sequence of characters. Unfortunately, the PARAMETER statement does not allow its constants to appear in EQUIVALENCE statements, so the text is initialised by DATA statements, and thus loses the protection of read-only given to constants defined via PARAMETER statements. The statements are from a rather more complex text scanning scheme, as all that are needed here are the symbols of GOODEXT.
 
The text scan could instead check for a valid character via something like <code> ("a" <= C & C <= "z") | ("A" <= C & C <= "Z") | (0 <= C & C <= "9")</code> but this is not just messy but unreliable - in EBCDIC for example there are gaps in the sequence of letters that are occupied by other symbols. So instead, a test via INDEX into a sequence of all the valid symbols. If one was in a hurry, for eight-bit character codes, an array GOODEXT of 256 logical values could be indexed by the numerical value of the character. <syntaxhighlight lang="fortran"> MODULE TEXTGNASH !Some text inspection.
CHARACTER*10 DIGITS !Integer only.
CHARACTER*11 DDIGITS !With a full stop masquerading as a decimal point.
CHARACTER*13 SDDIGITS !Signed decimal digits.
CHARACTER*4 EXPONENTISH !With exponent parts.
CHARACTER*17 NUMBERISH !The complete mix.
CHARACTER*16 HEXLETTERS !Extended for base sixteen.
CHARACTER*62 DIGILETTERS !File nameish but no .
CHARACTER*26 LITTLELETTERS,BIGLETTERS !These are well-known.
CHARACTER*52 LETTERS !The union thereof.
CHARACTER*66 NAMEISH !Allowing digits and . and _ as well.
CHARACTER*3 ODDITIES !And allow these in names also.
CHARACTER*1 CHARACTER(72) !Prepare a work area.
EQUIVALENCE !Whose components can be fingered.
1 (CHARACTER( 1),EXPONENTISH,NUMBERISH), !Start with numberish symbols that are not nameish.
2 (CHARACTER( 5),SDDIGITS), !Since the sign symbols are not nameish.
3 (CHARACTER( 7),DDIGITS,NAMEISH), !Computerish names might incorporate digits and a .
4 (CHARACTER( 8),DIGITS,HEXLETTERS,DIGILETTERS), !A proper name doesn't start with a digit.
5 (CHARACTER(18),BIGLETTERS,LETTERS), !Just with a letter.
6 (CHARACTER(44),LITTLELETTERS), !The second set.
7 (CHARACTER(70),ODDITIES) !Tack this on the end.
DATA EXPONENTISH /"eEdD"/ !These on the front.
DATA SDDIGITS /"+-.0123456789"/ !Any of these can appear in a floating point number.
DATA BIGLETTERS /"ABCDEFGHIJKLMNOPQRSTUVWXYZ"/ !Simple.
DATA LITTLELETTERS /"abcdefghijklmnopqrstuvwxyz"/ !Subtly different.
DATA ODDITIES /"_:#"/ !Allow these in names also. This strains := usage!
 
CHARACTER*62 GOODEXT !These are all the characters allowed
EQUIVALENCE (CHARACTER(8),GOODEXT)
c PARAMETER (GOODEXT = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" !for an approved
c 1 //"abcdefghijklmnopqrstuvwxyz" !file "extension" part
c 2 //"0123456789") !Of a file name.
INTEGER MEXT !A fixed bound.
PARAMETER (MEXT = 28) !This should do.
CONTAINS
CHARACTER*(MEXT) FUNCTION FEXT(FNAME) !Return the file extension part.
CHARACTER*(*) FNAME !May start with the file's path name blather.
INTEGER L1,L2 !Fingers to the text.
L2 = LEN(FNAME) !The last character of the file name.
L1 = L2 !Starting at the end...
10 IF (L1.GT.0) THEN !Damnit, can't rely on DO WHILE(safe & test)
IF (INDEX(GOODEXT,FNAME(L1:L1)).GT.0) THEN !So do the two parts explicitly.
L1 = L1 - 1 !Well, that was a valid character for an extension.
GO TO 10 !So, move back one and try again.
END IF !Until the end of valid stuff.
IF (FNAME(L1:L1).EQ.".") THEN !Stopped here. A proper introduction?
L1 = L1 - 1 !Yes. Include the period.
GO TO 20 !And escape.
END IF !Otherwise, not valid stuff.
END IF !Keep on moving back.
L1 = L2 !If we're here, no period was found.
20 FEXT = FNAME(L1 + 1:L2) !The text of the extension.
END FUNCTION FEXT !Possibly, blank.
END MODULE TEXTGNASH !Enough for this.
 
PROGRAM POKE
USE TEXTGNASH
 
WRITE (6,*) FEXT("Picture.jpg")
WRITE (6,*) FEXT("http://mywebsite.com/picture/image.png")
WRITE (6,*) FEXT("myuniquefile.longextension")
WRITE (6,*) FEXT("IAmAFileWithoutExtension")
WRITE (6,*) FEXT("/path/to.my/file")
WRITE (6,*) FEXT("file.odd_one")
WRITE (6,*)
WRITE (6,*) "Now for the new test collection..."
WRITE (6,*) FEXT("http://example.com/download.tar.gz")
WRITE (6,*) FEXT("CharacterModel.3DS")
WRITE (6,*) FEXT(".desktop")
WRITE (6,*) FEXT("document")
WRITE (6,*) FEXT("document.txt_backup")
WRITE (6,*) FEXT("/etc/pam.d/login")
WRITE (6,*) "Approved characters: ",GOODEXT
END</syntaxhighlight>
The output cheats a little, in that trailing spaces appear just as blankly as no spaces. The result of FEXT could be presented to TRIM (if that function is available), or the last non-blank could be found. With F2003, a scheme to enable character variables to be redefined to take on a current length is available, and so trailing spaces could no longer appear. This facility would also solve the endlessly annoying question of "how long is long enough", manifested in parameter MEXT being what might be a perfect solution. Once, three was the maximum extension length (not counting the period), then perhaps six, but now, what?
<pre>
.jpg
.png
.longextension
 
 
 
 
Now for the new test collection...
.gz
.3DS
.desktop
 
 
 
Approved characters:
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
</pre>
Note that if FEXT were presented with a file name containing trailing spaces, it would declare no extension to be present.
 
=={{header|FreeBASIC}}==
<syntaxhighlight lang="freebasic">' FB 1.05.0 Win64
 
Function isAlphaNum(s As String) As Boolean
Return ("a" <= s AndAlso s <= "z") OrElse ("A" <= s AndAlso s <= "Z") OrElse("0" <= s AndAlso s <= "9")
End Function
 
Function extractFileExt(filePath As String) As String
If filePath = "" Then Return ""
Dim index As Integer = InstrRev(filePath, ".")
If index = 0 Then Return ""
Dim ext As String = Mid(filePath, index + 1)
If ext = "" Then Return ""
For i As Integer = 1 To Len(ext)
If Not isAlphaNum(Mid(ext, i, 1)) Then Return ""
Next
Return ext
End Function
 
Dim filePaths(1 To 6) As String = _
{ _
"http://example.com/download.tar.gz", _
"CharacterModel.3DS", _
".desktop", _
"document", _
"document.txt_backup", _
"/etc/pam.d/login" _
}
 
Print "File path"; Tab(40); "Extension"
// A variation that handles the extra non-standard requirement
Print "========="; Tab(40); "========="
// that extensions shall only "consists of one or more letters or numbers".
Print
//
For i As Integer = 1 To 6
// Note, instead of direct comparison with '0-9a-zA-Z' we could instead use:
Print filePaths(i); Tab(40);
// case !unicode.IsLetter(rune(b)) && !unicode.IsNumber(rune(b)):
Dim ext As String = extractFileExt(filePaths(i))
// return ""
If ext = "" Then
// even though this operates on bytes instead of Unicode code points (runes),
Print "(empty string)"
// it is still correct given the details of UTF-8 encoding.
Else
func ext(path string) string {
Print ext
End If
Next
Print
Print "Press any key to quit"
Sleep</syntaxhighlight>
 
{{out}}
<pre>
File path Extension
========= =========
 
http://example.com/download.tar.gz gz
CharacterModel.3DS 3DS
.desktop desktop
document (empty string)
document.txt_backup (empty string)
/etc/pam.d/login (empty string)
</pre>
 
 
=={{header|Frink}}==
<syntaxhighlight lang="frink">fileExtension[str] :=
{
if [ext] = str =~ %r/(\.[A-Za-z0-9]+)$/
return ext
else
return ""
}
 
files = ["http://example.com/download.tar.gz",
"CharacterModel.3DS",
".desktop",
"document",
"document.txt_backup",
"/etc/pam.d/login"]
 
r = new array
for f = files
r.push[[f, "->", fileExtension[f]]]
 
println[formatTable[r, "right"]]</syntaxhighlight>
{{out}}
<pre>
http://example.com/download.tar.gz -> .gz
CharacterModel.3DS -> .3DS
.desktop -> .desktop
document ->
document.txt_backup ->
/etc/pam.d/login ->
</pre>
 
=={{header|FutureBasic}}==
The underscores is a valid extension character in macOS and extensions are returned without leading dots.
<syntaxhighlight lang="futurebasic">include "NSLog.incl"
 
void local fn DoIt
CFArrayRef paths = @[@"http://example.com/download.tar.gz",@"CharacterModel.3DS",@".desktop",@"document",@"document.txt_backup",@"/etc/pam.d/login"]
CFStringRef path
for path in paths
NSLog(@"%@",fn StringPathExtension( path ))
next
end fn
 
fn DoIt
 
HandleEvents</syntaxhighlight>
 
{{Out}}
<pre>
gz
3DS
desktop
null
txt_backup
null
</pre>
 
=={{header|Gambas}}==
As Gambas has its own tools for file extension extraction I have used those rather than complicate the code to match the requested criteria.
'''[https://gambas-playground.proko.eu/?gist=d52464fe8c05c857311d49184299814a Click this link to run this code]'''
<syntaxhighlight lang="gambas">Public Sub Main()
Dim sDir As String = "/sbin"
Dim sFileList As String[] = Dir(sDir)
Dim sTemp As String
Dim sFile As String
 
For Each sTemp In sFileList
sFile = sDir &/ sTemp
Print File.Name(sFile) & Space(25 - Len(File.Name(sFile)));
Print File.Ext(sFile)
Next
 
End</syntaxhighlight>
Output:
<pre>
....
mount.ntfs ntfs
iptables-save
mkfs.minix minix
exfatlabel
modprobe
vgrename
mkfs.ext2 ext2
lsmod
umount.ecryptfs_private ecryptfs_private
fstab-decode
mount.ecryptfs ecryptfs
....
</pre>
 
=={{header|Go}}==
 
<syntaxhighlight lang="go">package main
 
import "fmt"
 
func Ext(path string) string {
for i := len(path) - 1; i >= 0; i-- {
switch bc := path[i]; {
switch {
case b == '.':
case c == '.':
return path[i:]
case '0' <= bc && bc <= '9':
case 'aA' <= bc && bc <= 'zZ':
case 'Aa' <= bc && bc <= 'Zz':
default:
return ""
Line 347 ⟶ 1,312:
 
func main() {
type testcase struct {
tests := []string{
input string
"picture.jpg",
output string
"http://mywebsite.com/picture/image.png",
"myuniquefile.longextension",
"IAmAFileWithoutExtension",
"/path/to.my/file",
"file.odd_one",
// Extra, with unicode
"café.png",
"file.resumé",
// with unicode combining characters
"cafe\u0301.png",
"file.resume\u0301",
}
 
for _, str := range tests {
tests := []testcase{
std := path.Ext(str)
{"http://example.com/download.tar.gz", ".gz"},
custom := ext(str)
{"CharacterModel.3DS", ".3DS"},
fmt.Printf("%38s\t→ %-8q", str, custom)
{".desktop", ".desktop"},
if custom != std {
{"document", ""},
fmt.Printf("(Standard: %q)", std)
{"document.txt_backup", ""},
{"/etc/pam.d/login", ""},
}
 
for _, testcase := range tests {
ext := Ext(testcase.input)
if ext != testcase.output {
panic(fmt.Sprintf("expected %q for %q, got %q",
testcase.output, testcase.input, ext))
}
fmt.Println()
}
}</langsyntaxhighlight>
 
=={{header|Haskell}}==
 
<syntaxhighlight lang="haskell">module FileExtension
where
 
myextension :: String -> String
myextension s
|not $ elem '.' s = ""
|elem '/' extension || elem '_' extension = ""
|otherwise = '.' : extension
where
extension = reverse ( takeWhile ( /= '.' ) $ reverse s )
</syntaxhighlight>
{{out}}
<pre>map myextension ["http://example.com/download.tar.gz", "CharacterModel.3DS", ".desktop", "document", "document.txt_backup", "/etc/pam.d/login"]
<pre>
[".gz",".3DS",".desktop","","",""]
picture.jpg → ".jpg"
http://mywebsite.com/picture/image.png → ".png"
myuniquefile.longextension → ".longextension"
IAmAFileWithoutExtension → ""
/path/to.my/file → ""
file.odd_one → "" (Standard: ".odd_one")
café.png → ".png"
file.resumé → "" (Standard: ".resumé")
café.png → ".png"
file.resumé → "" (Standard: ".resumé")
</pre>
 
===Posix compliant===
On Unix systems, the penultimate file extension would be recognised, so using the Haskell library function '''takeExtension''':
 
<syntaxhighlight lang="haskell">import System.FilePath.Posix (FilePath, takeExtension)
 
fps :: [FilePath]
fps =
[ "http://example.com/download.tar.gz",
"CharacterModel.3DS",
".desktop",
"document",
"document.txt_backup",
"/etc/pam.d/login"
]
 
main :: IO ()
main = mapM_ (print . takeExtension) fps</syntaxhighlight>
{{Out}}
<pre>".gz"
".3DS"
".desktop"
""
".txt_backup"
""</pre>
 
=={{header|J}}==
Line 389 ⟶ 1,382:
'''Implementation:'''
 
<langsyntaxhighlight Jlang="j">require'regex'
ext=: '[.][a-zA-Z0-9]+$'&rxmatch ;@rxfrom ]</langsyntaxhighlight>
 
Obviously most of the work here is done by the regex implementation ([[wp:Perl Compatible Regular Expressions|pcre]], if that matters - and this particular kind of expression tends to be a bit more concise expressed in [[Perl|perl]] than in J...).
Line 400 ⟶ 1,393:
 
'''Alternative non-regex Implementation'''
<langsyntaxhighlight Jlang="j">ext=: #(}.~ [i: +./\ e.&'.' *.)@(#~ [: -. [: +./\. -.@e.&('.',AlphaNum_j_)</langsyntaxhighlight>
 
 
'''Task examples:'''
<syntaxhighlight lang="j"> ext 'http://example.com/download/tar.gz'
.gz
ext 'CharacterModel.3DS'
.3DS
 
Examples=: 'http://example.com/download.tar.gz';'CharacterModel.3DS';'.desktop';'document';'document.txt_backup';'/etc/pam.d/login'
<lang J> ext 'picture.jpg'
.jpg
ext 'http://mywebsite.com/picture/image.png'
.png
Examples=: 'picture.jpg';'http://mywebsite.com/picture/image.png';'myuniquefile.longextension';'IAmAFileWithoutExtension';'/path/to.my/file';'file.odd_one'
ext each Examples
┌───┬────┬────────┬┬┬┐
┌────┬────┬──────────────┬┬┬┐
│.gz│.3DS│.desktop││││
│.jpg│.png│.longextension││││
└───┴────┴────────┴┴┴┘</syntaxhighlight>
└────┴────┴──────────────┴┴┴┘</lang>
 
=={{header|Java}}==
<syntaxhighlight lang="java">
import java.io.File;
</syntaxhighlight>
<syntaxhighlight lang="java">
public static void main(String[] args) {
String[] strings = {
"http://example.com/download.tar.gz",
"CharacterModel.3DS",
".desktop",
"document",
"document.txt_backup",
"/etc/pam.d/login",
};
for (String string : strings)
System.out.println(extractExtension(string));
}
 
static String extractExtension(String string) {
/* we can use the 'File' class to extract the file-name */
File file = new File(string);
String filename = file.getName();
int indexOf = filename.lastIndexOf('.');
if (indexOf != -1) {
String extension = filename.substring(indexOf);
/* and use a regex to match only valid extensions */
if (extension.matches("\\.[A-Za-z\\d]+"))
return extension;
}
return "";
}
</syntaxhighlight>
{{Out}}
<pre>
.gz
.3DS
.desktop
 
 
</pre>
 
=={{header|javascript}}==
<syntaxhighlight lang="javascript">let filenames = ["http://example.com/download.tar.gz", "CharacterModel.3DS", ".desktop", "document", "document.txt_backup", "/etc/pam.d/login"];
let r = /\.[a-zA-Z0-9]+$/;
filenames.forEach((e) => console.log(e + " -> " + (r.test(e) ? r.exec(e)[0] : "")));</syntaxhighlight>
{{Out}}
<pre>http://example.com/download.tar.gz -> .gz
CharacterModel.3DS -> .3DS
.desktop -> .desktop
document ->
document.txt_backup ->
/etc/pam.d/login -> </pre>
 
 
With JS embedded in browsers and other applications across most or all operating systems, we need some flexibility in any reusable '''takeExtension''' function.
 
One approach is to define a more general curried function, from which we can obtain various simpler and OS-specific functions by specialisation:
 
<syntaxhighlight lang="javascript">(() => {
'use strict';
 
// OS-INDEPENDENT CURRIED FUNCTION --------------------
 
// takeExtension :: Regex String -> FilePath -> String
const takeExtension = charSet => fp => {
const
rgx = new RegExp('^[' + charSet + ']+$'),
xs = fp.split('/').slice(-1)[0].split('.'),
ext = 1 < xs.length ? (
xs.slice(-1)[0]
) : '';
return rgx.test(ext) ? (
'.' + ext
) : '';
};
 
// OS-SPECIFIC SPECIALIZED FUNCTIONS ------------------
 
// takePosixExtension :: FilePath -> String
const takePosixExtension = takeExtension('A-Za-z0-9\_\-');
 
// takeWindowsExtension :: FilePath -> String
const takeWindowsExtension = takeExtension('A-Za-z0-9');
 
 
// TEST -------------------------------------------
// main :: IO()
const main = () => {
[
['Posix', takePosixExtension],
['Windows', takeWindowsExtension]
].forEach(
([osName, f]) => console.log(
tabulated(
'\n\ntake' + osName +
'Extension :: FilePath -> String:\n',
x => x.toString(),
x => "'" + x.toString() + "'",
f,
[
"http://example.com/download.tar.gz",
"CharacterModel.3DS",
".desktop",
"document",
"document.txt_backup",
"/etc/pam.d/login"
]
),
'\n'
)
)
 
};
 
// GENERIC FUNCTIONS FOR TESTING AND DISPLAY OF RESULTS
 
// comparing :: (a -> b) -> (a -> a -> Ordering)
const comparing = f =>
(x, y) => {
const
a = f(x),
b = f(y);
return a < b ? -1 : (a > b ? 1 : 0);
i
};
 
// compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
const compose = (f, g) => x => f(g(x));
 
// justifyRight :: Int -> Char -> String -> String
const justifyRight = (n, cFiller, s) =>
n > s.length ? (
s.padStart(n, cFiller)
) : s;
 
// Returns Infinity over objects without finite length.
// This enables zip and zipWith to choose the shorter
// argument when one is non-finite, like cycle, repeat etc
 
// length :: [a] -> Int
const length = xs =>
(Array.isArray(xs) || 'string' === typeof xs) ? (
xs.length
) : Infinity;
 
// Map over lists or strings
 
// map :: (a -> b) -> [a] -> [b]
const map = (f, xs) =>
(Array.isArray(xs) ? (
xs
) : xs.split('')).map(f);
 
 
// maximumBy :: (a -> a -> Ordering) -> [a] -> a
const maximumBy = (f, xs) =>
0 < xs.length ? (
xs.slice(1)
.reduce((a, x) => 0 < f(x, a) ? x : a, xs[0])
) : undefined;
 
// tabulated :: String -> (a -> String) ->
// (b -> String) ->
// (a -> b) -> [a] -> String
const tabulated = (s, xShow, fxShow, f, xs) => {
// Heading -> x display function ->
// fx display function ->
// f -> values -> tabular string
const
ys = map(xShow, xs),
w = maximumBy(comparing(x => x.length), ys).length,
rows = zipWith(
(a, b) => justifyRight(w, ' ', a) + ' -> ' + b,
ys,
map(compose(fxShow, f), xs)
);
return s + '\n' + unlines(rows);
};
 
// take :: Int -> [a] -> [a]
// take :: Int -> String -> String
const take = (n, xs) =>
'GeneratorFunction' !== xs.constructor.constructor.name ? (
xs.slice(0, n)
) : [].concat.apply([], Array.from({
length: n
}, () => {
const x = xs.next();
return x.done ? [] : [x.value];
}));
 
 
// unlines :: [String] -> String
const unlines = xs => xs.join('\n');
 
// Use of `take` and `length` here allows zipping with non-finite lists
// i.e. generators like cycle, repeat, iterate.
 
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = (f, xs, ys) => {
const
lng = Math.min(length(xs), length(ys)),
as = take(lng, xs),
bs = take(lng, ys);
return Array.from({
length: lng
}, (_, i) => f(as[i], bs[i], i));
};
 
// MAIN ---
return main();
})();</syntaxhighlight>
{{Out}}
<pre>
takePosixExtension :: FilePath -> String:
 
http://example.com/download.tar.gz -> '.gz'
CharacterModel.3DS -> '.3DS'
.desktop -> '.desktop'
document -> ''
document.txt_backup -> '.txt_backup'
/etc/pam.d/login -> ''
 
 
takeWindowsExtension :: FilePath -> String:
 
http://example.com/download.tar.gz -> '.gz'
CharacterModel.3DS -> '.3DS'
.desktop -> '.desktop'
document -> ''
document.txt_backup -> ''
/etc/pam.d/login -> '' </pre>
 
=={{header|jq}}==
 
The following definitions include the delimiting period.
Pending resolution of the inconsistency in the task description as of this writing, the following
definitions exclude the delimiting period.
 
In the first section, a version intended for jq version 1.4 is presented.
Line 423 ⟶ 1,649:
 
{{works with|jq| 1.4}}
<langsyntaxhighlight lang="jq">def file_extension:
def alphanumeric: explode | unique
| reduce .[] as $i
Line 432 ⟶ 1,658:
rindex(".") as $ix
| if $ix then .[1+$ix:] as $ext
| if $ext|alphanumeric then $ext # or ".\($ext)" if# include the period is wanted
else ""
end
else ""
end;</langsyntaxhighlight>
 
{{works with|jq|1.5}}
<langsyntaxhighlight lang="jq">def file_extension:
(match( "(\\.([a-zA-Z0-9]*$)" ) //| .captures[0].string) false
// "" ;</syntaxhighlight>
| if . then .captures[0].string else "" end ;</lang>
 
'''Examples''':
 
Using either version above gives the same results.
<syntaxhighlight lang="jq">"http://example.com/download.tar.gz",
<lang jq>"picture.jpg",
"CharacterModel.3DS",
"myuniquefile.longextension",
".desktop",
"http://mywebsite.com/picture/image.png",
"document",
"myuniquefile.longextension",
"document.txt_backup",
"IAmAFileWithoutExtension",
"/pathetc/topam.myd/filelogin",
| "\(.) has extension: \(file_extension)"</syntaxhighlight>
"file.odd_one"
 
| "\(.) has extension: \"\(file_extension)\""</lang>
<syntaxhighlight lang="sh">$ jq -r -n -f Extract_file_extension.jq</syntaxhighlight>
{{out}}
<pre>http://example.com/download.tar.gz has extension: .gz
<lang sh>$ jq -r -n -f Extract_file_extension.jq
pictureCharacterModel.jpg3DS has extension: "jpg".3DS
myuniquefile.longextensiondesktop has extension: "longextension".desktop
http://mywebsite.com/picture/image.pngdocument has extension: "png"
myuniquefiledocument.longextensiontxt_backup has extension: "longextension"
IAmAFileWithoutExtension/etc/pam.d/login has extension: ""
</pre>
/path/to.my/file has extension: ""
 
file.odd_one has extension: ""</lang>
=={{header|Jsish}}==
<syntaxhighlight lang="javascript">#!/usr/bin/env jsish
/* Extract filename extension (for a limited subset of possible extensions) in Jsish */
function extractExtension(filename) {
var extPat = /\.[a-z0-9]+$/i;
var ext = filename.match(extPat);
return ext ? ext[0] : '';
}
 
if (Interp.conf('unitTest')) {
var files = ["http://example.com/download.tar.gz", "CharacterModel.3DS",
".desktop", "document", "document.txt_backup", "/etc/pam.d/login"];
for (var fn of files) puts(fn, quote(extractExtension(fn)));
}
 
/*
=!EXPECTSTART!=
http://example.com/download.tar.gz ".gz"
CharacterModel.3DS ".3DS"
.desktop ".desktop"
document ""
document.txt_backup ""
/etc/pam.d/login ""
=!EXPECTEND!=
*/</syntaxhighlight>
 
{{out}}
<pre>prompt$ jsish --U extractExtension.jsi
http://example.com/download.tar.gz ".gz"
CharacterModel.3DS ".3DS"
.desktop ".desktop"
document ""
document.txt_backup ""
/etc/pam.d/login ""
 
prompt$ jsish -u extractExtension.jsi
[PASS] extractExtension.jsi</pre>
 
=={{header|Julia}}==
<syntaxhighlight lang="julia">extension(url::String) = try match(r"\.[A-Za-z0-9]+$", url).match catch "" end
 
@show extension("http://example.com/download.tar.gz")
@show extension("CharacterModel.3DS")
@show extension(".desktop")
@show extension("document")
@show extension("document.txt_backup")
@show extension("/etc/pam.d/login")</syntaxhighlight>
 
{{out}}
<pre>extension("http://example.com/download.tar.gz") = ".gz"
extension("CharacterModel.3DS") = ".3DS"
extension(".desktop") = ".desktop"
extension("document") = ""
extension("document.txt_backup") = ""
extension("/etc/pam.d/login") = ""</pre>
 
=={{header|Kotlin}}==
<syntaxhighlight lang="scala">// version 1.0.6
 
val r = Regex("[^a-zA-Z0-9]") // matches any non-alphanumeric character
 
fun extractFileExtension(path: String): String {
if (path.isEmpty()) return ""
var fileName = path.substringAfterLast('/')
if (path == fileName) fileName = path.substringAfterLast('\\')
val splits = fileName.split('.')
if (splits.size == 1) return ""
val ext = splits.last()
return if (r.containsMatchIn(ext)) "" else "." + ext
}
 
fun main(args: Array<String>) {
val paths = arrayOf(
"http://example.com/download.tar.gz",
"CharacterModel.3DS",
".desktop",
"document",
"document.txt_backup",
"/etc/pam.d/login",
"c:\\programs\\myprogs\\myprog.exe", // using back-slash as delimiter
"c:\\programs\\myprogs\\myprog.exe_backup" // ditto
)
for (path in paths) {
val ext = extractFileExtension(path)
println("${path.padEnd(37)} -> ${if (ext.isEmpty()) "(empty string)" else ext}")
}
}</syntaxhighlight>
 
{{out}}
<pre>
http://example.com/download.tar.gz -> .gz
CharacterModel.3DS -> .3DS
.desktop -> .desktop
document -> (empty string)
document.txt_backup -> (empty string)
/etc/pam.d/login -> (empty string)
c:\programs\myprogs\myprog.exe -> .exe
c:\programs\myprogs\myprog.exe_backup -> (empty string)
</pre>
 
=={{header|Lua}}==
<syntaxhighlight lang="lua">-- Lua pattern docs at http://www.lua.org/manual/5.1/manual.html#5.4.1
function fileExt (filename) return filename:match("(%.%w+)$") or "" end
local testCases = {
"http://example.com/download.tar.gz",
"CharacterModel.3DS",
".desktop",
"document",
"document.txt_backup",
"/etc/pam.d/login"
}
for _, example in pairs(testCases) do
print(example .. ' -> "' .. fileExt(example) .. '"')
end</syntaxhighlight>
{{out}}
<pre>http://example.com/download.tar.gz -> ".gz"
CharacterModel.3DS -> ".3DS"
.desktop -> ".desktop"
document -> ""
document.txt_backup -> ""
/etc/pam.d/login -> ""</pre>
 
=={{header|Mathematica}}/{{header|Wolfram Language}}==
FileExtension is a built-in function:
<syntaxhighlight lang="mathematica">FileExtension /@ {"http://example.com/download.tar.gz", "CharacterModel.3DS", ".desktop", "document","document.txt_backup","/etc/pam.d/login"}</syntaxhighlight>
{{out}}
{"gz", "3DS", "", "", "txt_backup", ""}
 
=={{header|Nanoquery}}==
The File object type in Nanoquery has a built-in method to extract the file extension from a filename, but it treats all characters as potentially valid in an extension and URLs as not being. As a result, the .txt_backup extension is included in the output.
<syntaxhighlight lang="nanoquery">import Nanoquery.IO
 
filenames = {"http://example.com/download.tar.gz", "CharacterModel.3DS"}
filenames += {".desktop", "document", "document.txt_backup", "/etc/pam.d/login"}
 
for fname in filenames
println new(File, fname).getExtension()
end</syntaxhighlight>
{{out}}
<pre>.gz
.3DS
.desktop
 
.txt_backup
</pre>
 
=={{header|Nim}}==
As can be seen in the examples, Nim standard library function <code>splitFile</code> detects that a file such as <code>.desktop</code> is a special file. But, on the other hand, it considers that an underscore is a valid character in an extension.
<syntaxhighlight lang="nim">import os, strutils
 
func extractFileExt(path: string): string =
var s: seq[char]
for i in countdown(path.high, 0):
case path[i]
of Letters, Digits:
s.add path[i]
of '.':
s.add '.'
while s.len > 0: result.add s.pop()
return
else:
break
result = ""
 
for input in ["http://example.com/download.tar.gz", "CharacterModel.3DS",
".desktop", "document", "document.txt_backup", "/etc/pam.d/login"]:
echo "Input: ", input
echo "Extracted extension: ", input.extractFileExt()
echo "Using standard library: ", input.splitFile()[2]
echo()</syntaxhighlight>
 
{{out}}
<pre>Input: http://example.com/download.tar.gz
Extracted extension: .gz
Using standard library: .gz
 
Input: CharacterModel.3DS
Extracted extension: .3DS
Using standard library: .3DS
 
Input: .desktop
Extracted extension: .desktop
Using standard library:
 
Input: document
Extracted extension:
Using standard library:
 
Input: document.txt_backup
Extracted extension:
Using standard library: .txt_backup
 
Input: /etc/pam.d/login
Extracted extension:
Using standard library: </pre>
 
=={{header|Objeck}}==
<syntaxhighlight lang="objeck">use Query.RegEx;
 
class FindExtension {
function : Main(args : String[]) ~ Nil {
file_names := [
"http://example.com/download.tar.gz", "CharacterModel.3DS",
".desktop", "document", "document.txt_backup", "/etc/pam.d/login"];
 
each(i : file_names) {
file_name := file_names[i];
System.IO.Console->Print(file_name)->Print(" has extension: ")->PrintLine(GetExtension(file_name));
};
}
 
function : GetExtension(file_name : String) ~ String {
index := file_name->FindLast('.');
if(index < 0) {
return "";
};
 
ext := file_name->SubString(index, file_name->Size() - index);
if(ext->Size() < 1) {
return "";
};
if(<>RegEx->New("\\.([a-z]|[A-Z]|[0-9])+")->MatchExact(ext)) {
return "";
};
 
return ext;
}
}</syntaxhighlight>
 
{{output}}
<pre>
http://example.com/download.tar.gz has extension: .gz
CharacterModel.3DS has extension: .3DS
.desktop has extension: .desktop
document has extension:
document.txt_backup has extension:
/etc/pam.d/login has extension:
</pre>
 
=={{header|OCaml}}==
Since OCaml 4.04 there is a function '''[http://caml.inria.fr/pub/docs/manual-ocaml/libref/Filename.html#VALextension Filename.extension]''':
<syntaxhighlight lang="ocaml">let () =
let filenames = [
"http://example.com/download.tar.gz";
"CharacterModel.3DS";
".desktop";
"document";
"document.txt_backup";
"/etc/pam.d/login"]
in
List.iter (fun filename ->
Printf.printf " '%s' => '%s'\n" filename (Filename.extension filename)
) filenames</syntaxhighlight>
differs a little bit from the specification of this task.
{{out}}
<pre>
'http://example.com/download.tar.gz' => '.gz'
'CharacterModel.3DS' => '.3DS'
'.desktop' => ''
'document' => ''
'document.txt_backup' => '.txt_backup'
'/etc/pam.d/login' => ''
</pre>
 
=={{header|Oforth}}==
 
If extension is not valid, returns null, not "".
Easy to change if "" is required.
 
<langsyntaxhighlight Oforthlang="oforth">: fileExt( s -- t )
{
| i |
s lastIndexOf('.') dup ->i ifNull: [ null return ]
s extract(i 1 +, s size) conform(#isAlpha) ifFalse: [ null return ]
s extract(i, s size)
;</syntaxhighlight>
} </lang>
 
{{out}}
<pre>
>"http://example.com/download.tar.gz" fileExt .
fileExt("picture.jpg") println
.gz ok
fileExt("http://mywebsite.com/picture/image.png") println
>
fileExt("myuniquefile.longextension") println
ok
fileExt("IAmAFileWithoutExtension") println
>"CharacterModel.3DS" fileExt .
fileExt("/path/to.my/file") println
.3DS ok
fileExt("file.odd_one") println
>
ok
>".desktop" fileExt .
.desktop ok
>"document" fileExt .
null ok
>"document.txt_backup" fileExt .
null ok
>"/etc/pam.d/login" fileExt .
null ok
></pre>
=={{header|Pascal}}==
==={{header|Free Pascal}}===
<syntaxhighlight lang="pascal">
Program Extract_file_extension;
 
{FreePascal has the built-in function ExtractFileExt which returns the file extension.
* it does need charachters before the period to return the proper extension and it returns
* the extension including the period}
 
Uses character,sysutils;
 
Const arr : array of string = ('http://example.com/download.tar.gz','CharacterModel.3DS','.desktop',
'document','document.txt_backup','/etc/pam.d/login');
 
Function extractextension(fn: String): string;
Var
i: integer;
Begin
fn := 'prefix' + fn; {add charachters before the period}
fn := ExtractFileExt(fn);
For i := 2 to length(fn) Do {skip the period}
If Not IsLetterOrDigit(fn[i]) Then exit('');
extractextension := fn;
End;
 
Var i : string;
Begin
For i In arr Do
writeln(i:35,' -> ',extractextension(i))
End.
 
</syntaxhighlight>
{{out}}
<pre>
http://example.com/download.tar.gz -> gz
CharacterModel.3DS -> 3DS
.desktop -> desktop
document ->
document.txt_backup ->
/etc/pam.d/login ->
</pre>
 
=={{header|Perl}}==
 
{{trans|Raku}}
<syntaxhighlight lang="perl">sub extension {
my $path = shift;
$path =~ / \. [a-z0-9]+ $ /xi;
$& // '';
}</syntaxhighlight>
 
Testing:
<syntaxhighlight lang="perl">printf "%-35s %-11s\n", $_, "'".extension($_)."'"
for qw[
http://example.com/download.tar.gz
CharacterModel.3DS
.desktop
document
document.txt_backup
/etc/pam.d/login
];</syntaxhighlight>
 
{{out}}
<pre>
http://example.com/download.tar.gz '.gz'
CharacterModel.3DS '.3DS'
.desktop '.desktop'
document ''
document.txt_backup ''
/etc/pam.d/login ''
</pre>
 
=={{header|Phix}}==
<!--<syntaxhighlight lang="phix">(phixonline)-->
<span style="color: #008080;">with</span> <span style="color: #008080;">javascript_semantics</span>
<span style="color: #008080;">function</span> <span style="color: #000000;">getExtension</span><span style="color: #0000FF;">(</span><span style="color: #004080;">string</span> <span style="color: #000000;">filename</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">i</span><span style="color: #0000FF;">=</span><span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">filename</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">to</span> <span style="color: #000000;">1</span> <span style="color: #008080;">by</span> <span style="color: #0000FF;">-</span><span style="color: #000000;">1</span> <span style="color: #008080;">do</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">ch</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">filename</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">ch</span><span style="color: #0000FF;">=</span><span style="color: #008000;">'.'</span> <span style="color: #008080;">then</span> <span style="color: #008080;">return</span> <span style="color: #000000;">filename</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">..$]</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">if</span> <span style="color: #7060A8;">find</span><span style="color: #0000FF;">(</span><span style="color: #000000;">ch</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"\\/_"</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">then</span> <span style="color: #008080;">exit</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #008080;">return</span> <span style="color: #008000;">""</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span>
<span style="color: #008080;">constant</span> <span style="color: #000000;">tests</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{</span><span style="color: #008000;">"mywebsite.com/picture/image.png"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"http://mywebsite.com/picture/image.png"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"myuniquefile.longextension"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"IAmAFileWithoutExtension"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"/path/to.my/file"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"file.odd_one"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"http://example.com/download.tar.gz"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"CharacterModel.3DS"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">".desktop"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"document"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"document.txt_backup"</span><span style="color: #0000FF;">,</span>
<span style="color: #008000;">"/etc/pam.d/login"</span><span style="color: #0000FF;">}</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">i</span><span style="color: #0000FF;">=</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">tests</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">do</span>
<span style="color: #7060A8;">printf</span><span style="color: #0000FF;">(</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"%s ==&gt; %s\n"</span><span style="color: #0000FF;">,{</span><span style="color: #000000;">tests</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">],</span><span style="color: #000000;">getExtension</span><span style="color: #0000FF;">(</span><span style="color: #000000;">tests</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">])})</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<!--</syntaxhighlight>-->
{{out}}
<pre>
mywebsite.com/picture/image.png ==> .png
http://mywebsite.com/picture/image.png ==> .png
myuniquefile.longextension ==> .longextension
IAmAFileWithoutExtension ==>
/path/to.my/file ==>
file.odd_one ==>
http://example.com/download.tar.gz ==> .gz
CharacterModel.3DS ==> .3DS
.desktop ==> .desktop
document ==>
document.txt_backup ==>
/etc/pam.d/login ==>
</pre>
The builtin get_file_extension() could also be used, however that routine differs from the task description in that "libglfw.so.3.1" => "so", and all results are lowercase even if the input is not.
 
=={{header|PHP}}==
<syntaxhighlight lang="php">
$tests = [
['input'=>'http://example.com/download.tar.gz', 'expect'=>'.gz'],
['input'=>'CharacterModel.3DS', 'expect'=>'.3DS'],
['input'=>'.desktop', 'expect'=>'.desktop'],
['input'=>'document', 'expect'=>''],
['input'=>'document.txt_backup', 'expect'=>''],
['input'=>'/etc/pam.d/login', 'expect'=>'']
];
 
foreach ($tests as $key=>$test) {
$ext = pathinfo($test['input'], PATHINFO_EXTENSION);
// in php, pathinfo allows for an underscore in the file extension
// the following if statement only allows for A-z0-9 in the extension
if (ctype_alnum($ext)) {
// pathinfo returns the extension without the preceeding '.' so adding it back on
$tests[$key]['actual'] = '.'.$ext;
} else {
$tests[$key]['actual'] = '';
}
}
foreach ($tests as $test) {
printf("%35s -> %s \n", $test['input'],$test['actual']);
}</syntaxhighlight>
{{out}}
<pre>
http://example.com/download.tar.gz -> .gz
CharacterModel.3DS -> .3DS
.desktop -> .desktop
document ->
document.txt_backup ->
/etc/pam.d/login ->
</pre>
 
=={{header|PicoLisp}}==
<syntaxhighlight lang="picolisp">(de extension (F)
(and
(fully
'((C)
(or
(>= "Z" C "A")
(>= "z" C "a")
(>= "9" C "0") ) )
(setq F (stem (member "." (chop F)) ".")) )
(pack F) ) )
(println (extension "http://example.com/download.tar.gz"))
(println (extension "CharacterModel.3DS"))
(println (extension ".desktop"))
(println (extension "document"))
(println (extension "document.txt_backup"))
(println (extension "/etc/pam.d/login"))</syntaxhighlight>
{{out}}
<pre>
"gz"
"3DS"
"desktop"
NIL
NIL
NIL
</pre>
 
=={{header|Plain English}}==
The 'Extract' imperative extracts parts of a path. When extracting an extension, it starts from the last period (.) in the path string and goes until the end of the string.
<syntaxhighlight lang="text">
To run:
Start up.
Show the file extension of "http://example.com/download.tar.gz".
Show the file extension of "CharacterModel.3DS".
Show the file extension of ".desktop".
Show the file extension of "document".
Show the file extension of "document.txt_backup".
Show the file extension of "/etc/pam.d/login".
Wait for the escape key.
Shut down.
 
To show the file extension of a path:
Extract an extension from the path.
Write the extension to the console.
</syntaxhighlight>
{{out}}
<pre>
.gz
.3DS
.desktop
 
.txt_backup
.d/login
</pre>
 
=={{header|PowerShell}}==
<syntaxhighlight lang="powershell">function extension($file){
<lang PowerShell>
function extension($file){
$ext = [System.IO.Path]::GetExtension($file)
if (-not [String]::IsNullOrEmpty($ext)) {
Line 494 ⟶ 2,202:
$ext
}
extension "picturehttp://example.jpgcom/download.tar.gz"
extension "http://mywebsiteCharacterModel.com/picture/image.png3DS"
extension "myuniquefile.longextensiondesktop"
extension "IAmAFileWithoutExtensiondocument"
extension "/path/todocument.my/filetxt_backup"
extension "file/etc/pam.odd_oned/login"</syntaxhighlight>
</lang>
<b>Output:</b>
<pre>.gz
.jpg3DS
.desktop
.png
.longextension
 
 
</pre>
 
=={{header|Python}}==
Uses [https://docs.python.org/3/library/os.path.html#os.path.splitext os.path.splitext] and the extended tests from the Go example above.
 
Uses [https://docs.python.org/3/library/re.html#re.search re.search].
<lang python>Python 3.5.0a1 (v3.5.0a1:5d4b6a57d5fd, Feb 7 2015, 17:58:38) [MSC v.1900 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import os
>>> tests = ["picture.jpg",
"http://mywebsite.com/picture/image.png",
"myuniquefile.longextension",
"IAmAFileWithoutExtension",
"/path/to.my/file",
"file.odd_one",
# Extra, with unicode
"café.png",
"file.resumé",
# with unicode combining characters
"cafe\u0301.png",
"file.resume\u0301"]
>>> for path in tests:
print("Path: %r -> Extension: %r" % (path, os.path.splitext(path)[-1]))
 
<syntaxhighlight lang="python">import re
def extractExt(url):
m = re.search(r'\.[A-Za-z0-9]+$', url)
return m.group(0) if m else ""
</syntaxhighlight>
 
and one way of allowing for OS-specific variations in the character sets permitted in file extensions is to write a general and reusable curried function, from which we can obtain simpler OS-specific functions by specialisation:
 
<syntaxhighlight lang="python">'''Obtaining OS-specific file extensions'''
 
import os
import re
 
 
# OS-INDEPENDENT CURRIED FUNCTION -------------------------
 
# takeExtension :: Regex String -> FilePath -> String
def takeExtension(charSet):
'''The extension part (if any) of a file name.
(Given a regex string representation of the
character set accepted in extensions by the OS).'''
def go(fp):
m = re.search(
r'\.[' + charSet + ']+$',
(fp).split(os.sep)[-1]
)
return m[0] if m else ''
return lambda fp: go(fp)
 
 
# DERIVED (OS-SPECIFIC) FUNCTIONS -------------------------
 
 
# takePosixExtension :: FilePath -> String
def takePosixExtension(fp):
'''The file extension, if any,
of a Posix file path.'''
return takeExtension(r'A-Za-z0-9\-\_')(fp)
 
 
# takeWindowsExtension :: FilePath -> String
def takeWindowsExtension(fp):
'''The file extension, if any,
of a Windows file path.'''
return takeExtension(r'A-Za-z0-9')(fp)
 
 
# TEST ----------------------------------------------------
def main():
'''Tests'''
 
for f in [takePosixExtension, takeWindowsExtension]:
print(
tabulated(f.__name__ + ' :: FilePath -> String:')(
str
)(str)(f)([
"http://example.com/download.tar.gz",
"CharacterModel.3DS",
".desktop",
"document",
"document.txt_backup",
"/etc/pam.d/login"
])
)
print()
 
 
# GENERIC -------------------------------------------------
 
 
# tabulated :: String -> (a -> String) ->
# (b -> String) ->
# (a -> b) -> [a] -> String
def tabulated(s):
'''Heading -> x display function -> fx display function ->
number of columns -> f -> value list -> tabular string.'''
def go(xShow, fxShow, f, xs):
w = max(map(lambda x: len(xShow(x)), xs))
return s + '\n' + '\n'.join([
xShow(x).rjust(w, ' ') + ' -> ' + fxShow(f(x)) for x in xs
])
return lambda xShow: lambda fxShow: (
lambda f: lambda xs: go(
xShow, fxShow, f, xs
)
)
 
 
# MAIN ---
if __name__ == '__main__':
main()</syntaxhighlight>
{{Out}}
<pre>takePosixExtension :: FilePath -> String:
http://example.com/download.tar.gz -> .gz
CharacterModel.3DS -> .3DS
.desktop -> .desktop
document ->
document.txt_backup -> .txt_backup
/etc/pam.d/login ->
 
takeWindowsExtension :: FilePath -> String:
http://example.com/download.tar.gz -> .gz
CharacterModel.3DS -> .3DS
.desktop -> .desktop
document ->
document.txt_backup ->
/etc/pam.d/login -> </pre>
 
=={{header|Quackery}}==
 
<syntaxhighlight lang="quackery"> [ bit
[ 0
$ "abcdefghijklmnopqrstuvwxyz"
$ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
$ "1234567890." join join
witheach [ bit | ] ] constant
& 0 > ] is validchar ( c --> b )
[ dup $ "" = if done
Path: 'picture.jpg' -> Extension: '.jpg'
dup -1 peek char . = iff
Path: 'http://mywebsite.com/picture/image.png' -> Extension: '.png'
[ drop $ "" ] done
Path: 'myuniquefile.longextension' -> Extension: '.longextension'
$ "" swap
Path: 'IAmAFileWithoutExtension' -> Extension: ''
reverse witheach
Path: '/path/to.my/file' -> Extension: ''
[ dup dip join
Path: 'file.odd_one' -> Extension: '.odd_one'
dup validchar iff
Path: 'café.png' -> Extension: '.png'
[ char . = if
Path: 'file.resumé' -> Extension: '.resumé'
[ reverse conclude ] ]
Path: 'café.png' -> Extension: '.png'
else
Path: 'file.resumé' -> Extension: '.resumé'
[ 2drop $ "" conclude ] ]
>>> </lang>
dup $ "" = if done
dup 0 peek char . != if
[ drop $ "" ] ] is extension ( $ --> $ )
[ cr dup echo$ say " --> "
extension
dup $ "" = iff
[ drop say "no extension" ]
else echo$
cr ] is task ( $ --> )
$ "http://example.com/download.tar.gz" task
$ "CharacterModel.3DS" task
$ ".desktop" task
$ "document" task
$ "document.txt_backup" task
$ "/etc/pam.d/login" task</syntaxhighlight>
 
{{out}}
 
<pre>http://example.com/download.tar.gz --> .gz
 
CharacterModel.3DS --> .3DS
 
.desktop --> .desktop
 
document --> no extension
 
document.txt_backup --> no extension
 
/etc/pam.d/login --> no extension
</pre>
 
=={{header|Racket}}==
 
<lang Racket>#lang racket
<syntaxhighlight lang="racket">
#lang racket
 
;; Note that for a real implementation, Racket has a
Line 553 ⟶ 2,392:
;; and handles path values so might run into problems with unicode
;; string inputs.
 
(define (string-extension x)
(cadr (regexp-match #px"(\\.[[:alnum:]]+|)$" x)))
(define examples '("http://example.com/download.tar.gz"
"CharacterModel.3DS"
".desktop"
"document"
"document.txt_backup"
"/etc/pam.d/login"))
 
(for ([x (in-list examples)])
(define (string-extension/unicode x)
(printf "~a | ~a\n" (~a x #:width 34) (string-extension x)))
(cadr (regexp-match #px"(\\.(?:\\p{L}|\\p{N}|\\p{M})+|)$" x)))
</syntaxhighlight>
 
{{out}}
(define examples '("picture.jpg"
<pre>
"http://mywebsite.com/picture/image.png"
http://example.com/download.tar.gz | .gz
"myuniquefile.longextension"
CharacterModel.3DS | "IAmAFileWithoutExtension".3DS
.desktop | "/path/to.my/file"desktop
document "file.odd_one" |
document.txt_backup | ""
/etc/pam.d/login ;;| Extra, with unicode
</pre>
"café.png"
"file.resumé"
;; with unicode combining characters
"cafe\u0301.png"
"file.resume\u0301"))
 
=={{header|Raku}}==
(printf "Official task:\n")
(formerly Perl 6)
(for ([x (in-list examples)])
(printf "~s ==> ~s\n" x (string-extension x)))
 
The built-in <code>IO::Path</code> class has an <code>.extension</code> method:
(printf "\nWith unicode support:\n")
(for ([x (in-list examples)])
(printf "~s ==> ~s\n" x (string-extension/unicode x)))
</lang>
 
<syntaxhighlight lang="raku" line>say $path.IO.extension;</syntaxhighlight>
{{out}}
Contrary to this task's specification, it
<pre>Official task:
* doesn't include the dot in the output
"picture.jpg" ==> ".jpg"
* doesn't restrict the extension to letters and numbers.
"http://mywebsite.com/picture/image.png" ==> ".png"
"myuniquefile.longextension" ==> ".longextension"
"IAmAFileWithoutExtension" ==> ""
"/path/to.my/file" ==> ""
"file.odd_one" ==> ""
"" ==> ""
"café.png" ==> ".png"
"file.resumé" ==> ""
"café.png" ==> ".png"
"file.resumé" ==> ""
 
 
With unicode support:
Here's a custom implementation which does satisfy the task requirements:
"picture.jpg" ==> ".jpg"
 
"http://mywebsite.com/picture/image.png" ==> ".png"
<syntaxhighlight lang="raku" line>sub extension (Str $path --> Str) {
"myuniquefile.longextension" ==> ".longextension"
$path.match(/:i ['.' <[a..z0..9]>+]? $ /).Str
"IAmAFileWithoutExtension" ==> ""
}
"/path/to.my/file" ==> ""
 
"file.odd_one" ==> ""
# Testing:
"" ==> ""
 
"café.png" ==> ".png"
printf "%-35s %-11s %-12s\n", $_, extension($_).perl, $_.IO.extension.perl
"file.resumé" ==> ".resumé"
for <
"café.png" ==> ".png"
http://example.com/download.tar.gz
"file.resumé" ==> ".resumé"
CharacterModel.3DS
.desktop
document
document.txt_backup
/etc/pam.d/login
>;</syntaxhighlight>
 
{{out}}
<pre>
http://example.com/download.tar.gz ".gz" "gz"
CharacterModel.3DS ".3DS" "3DS"
.desktop ".desktop" "desktop"
document "" ""
document.txt_backup "" "txt_backup"
/etc/pam.d/login "" ""
</pre>
 
=={{header|REXX}}==
(Using this paraphrased Rosetta Code task's definition that a legal file extension ''only'' consists of mixed-case Latin letters and/or decimal digits.)
<lang rexx>/*REXX program extracts the (legal) file extension from a file name. */
@. = /*define default value for array.*/
if arg(1)\=='' then @.1 = arg(1) /*use the filename from the C.L. */
else do /*No filename given? Use defaults*/
@.1 = 'picture.jpg'
@.2 = 'http://mywebsite.com/pictures/image.png'
@.3 = 'myuniquefile.longextension'
@.4 = 'IAmAFileWithoutExtension'
@.5 = '/path/to.my/file'
@.6 = 'file.odd_one'
end
 
Using this paraphrased Rosetta Code task's definition that:
do j=1 while @.j\==''; $=@.j; x= /*process all of the file names. */
 
p=lastpos(.,$) /*find last position of a period.*/
a legal file extension &nbsp; ''only'' &nbsp; consists of mixed-case Latin letters and/or decimal digits.
if p\==0 then x=substr($,p+1) /*Found? Get the stuff after it.*/
<syntaxhighlight lang="rexx">/*REXX pgm extracts the file extension (defined above from the RC task) from a file name*/
if \datatype(x,'A') then x= /*upper & lower case letters+digs*/
@.= if x=='' then x=' [null]' /*usedefine adefault better namevalue for athe @ "null"array.*/
parse arg fID else x=. || x /*prefixobtain any extensionoptional witharguments afrom period.CL*/
if fID\=='' then @.1 = fID /*use the filename from the C.L. */
say 'file ext='left(x,20) 'for file name='$
else do /*No filename given? Then use defaults.*/
end /*j*/
@.1 = 'http:/*stick a fork in it, we're done/example.*com/</lang>download.tar.gz'
@.2 = 'CharacterModel.3DS'
'''output''' using the default inputs:
@.3 = '.desktop'
@.4 = 'document'
@.5 = 'document.txt_backup'
@.6 = '/etc/pam.d/login'
end
 
do j=1 while @.j\==''; x= /*process (all of) the file name(s). */
p=lastpos(., @.j) /*find the last position of a period. */
if p\==0 then x=substr(@.j, p+1) /*Found a dot? Then get stuff after it*/
if \datatype(x, 'A') then x= /*Not upper/lowercase letters | digits?*/
if x=='' then x= " [null]" /*use a better name for a "null" ext.*/
else x= . || x /*prefix the extension with a period. */
say 'file extension=' left(x, 20) "for file name=" @.j
end /*j*/ /*stick a fork in it, we're all done. */</syntaxhighlight>
'''output''' &nbsp; when using the default (internal) inputs:
<pre>
file extextension= .jpggz for file name=picture http://example.com/download.tar.jpggz
file extextension= .png3DS for file name=http://mywebsite.com/pictures/image CharacterModel.png3DS
file extextension= .longextensiondesktop for file name=myuniquefile .longextensiondesktop
file extextension= [null] for file name=IAmAFileWithoutExtension document
file extextension= [null] for file name=/path/to document.my/filetxt_backup
file extextension= [null] for file name=file /etc/pam.odd_oned/login
</pre>
 
=={{header|Ring}}==
<syntaxhighlight lang="ring">
# Project : Extract file extension
 
test = ["http://example.com/download.tar.gz",
"CharacterModel.3DS",
".desktop",
"document",
"document.txt_backup",
"/etc/pam.d/login"]
 
for n = 1 to len(test)
flag = 1
revtest = revstr(test[n])
nr = substr(revtest, ".")
if nr > 0
revtest2 = left(revtest, nr)
for m = 1 to len(revtest2)
if (ascii(revtest2[m]) > 64 and ascii(revtest2[m]) < 91) or
(ascii(revtest2[m]) > 96 and ascii(revtest2[m]) < 123) or
isdigit(revtest2[m]) or revtest2[m] = "."
else
flag = 0
ok
next
else
flag = 0
ok
if flag = 1
revtest3 = revstr(revtest2)
see test[n] + " -> " + revtest3 + nl
else
see test[n] + " -> (none)" + nl
ok
next
 
func revstr(cStr)
cStr2 = ""
for x = len(cStr) to 1 step -1
cStr2 += cStr[x]
next
return cStr2
</syntaxhighlight>
Output:
<pre>
http://example.com/download.tar.gz -> .gz
CharacterModel.3DS -> .3DS
.desktop -> .desktop
document -> (none)
document.txt_backup -> (none)
/etc/pam.d/login -> (none)
</pre>
 
=={{header|Ruby}}==
<syntaxhighlight lang="ruby">names =
%w(http://example.com/download.tar.gz
CharacterModel.3DS
.desktop
document
/etc/pam.d/login)
names.each{|name| p File.extname(name)}
</syntaxhighlight>
'''output'''
<pre>
".gz"
".3DS"
""
""
""
</pre>
Apparently, the built-in method does not consider ".desktop" to be a file extension (on Linux).
 
=={{header|Rust}}==
<syntaxhighlight lang="rust">use std::path::Path;
 
fn main() {
let filenames = &[
"http://example.com/download.tar.gz",
"CharacterModel.3DS",
".desktop",
"document",
"document.txt_backup",
"/etc/pam.d/login",
];
 
for filename in filenames {
println!(
"{:34} | {:8} | {:?}",
filename,
extension(filename),
Path::new(filename).extension()
);
}
}
 
fn extension(filename: &str) -> &str {
filename
.rfind('.')
.map(|idx| &filename[idx..])
.filter(|ext| ext.chars().skip(1).all(|c| c.is_ascii_alphanumeric()))
.unwrap_or("")
}</syntaxhighlight>
The built-in method requires a filename before the extension, allows any non-period character to appear in the extension, and returns <code>None</code> if no extension is found.
{{out}}
<pre>
http://example.com/download.tar.gz | .gz | Some("gz")
CharacterModel.3DS | .3DS | Some("3DS")
.desktop | .desktop | None
document | | None
document.txt_backup | | Some("txt_backup")
/etc/pam.d/login | | None
</pre>
 
=={{header|Scala}}==
<syntaxhighlight lang="scala">package rosetta
 
object FileExt {
private val ext = """\.[A-Za-z0-9]+$""".r
def isExt(fileName: String, extensions: List[String]) =
extensions.map { _.toLowerCase }.exists { fileName.toLowerCase endsWith "." + _ }
def extractExt(url: String) = ext findFirstIn url getOrElse("")
}
 
object FileExtTest extends App {
val testExtensions: List[String] = List("zip", "rar", "7z", "gz", "archive", "A##", "tar.bz2")
val isExtTestFiles: Map[String, Boolean] = Map(
"MyData.a##" -> true,
"MyData.tar.Gz" -> true,
"MyData.gzip" -> false,
"MyData.7z.backup" -> false,
"MyData..." -> false,
"MyData" -> false,
"MyData_v1.0.tar.bz2" -> true,
"MyData_v1.0.bz2" -> false
)
val extractExtTestFiles: Map[String, String] = Map(
"http://example.com/download.tar.gz" -> ".gz",
"CharacterModel.3DS" -> ".3DS",
".desktop" -> ".desktop",
"document" -> "",
"document.txt_backup" -> "",
"/etc/pam.d/login" -> "",
"/etc/pam.d/login.a" -> ".a",
"/etc/pam.d/login." -> "",
"picture.jpg" -> ".jpg",
"http://mywebsite.com/picture/image.png"-> ".png",
"myuniquefile.longextension" -> ".longextension",
"IAmAFileWithoutExtension" -> "",
"/path/to.my/file" -> "",
"file.odd_one" -> "",
// Extra, with unicode
"café.png" -> ".png",
"file.resumé" -> "",
// with unicode combining characters
"cafe\u0301.png" -> ".png",
"file.resume\u0301" -> ""
)
println("isExt() tests:")
 
for ((file, isext) <- isExtTestFiles) {
assert(FileExt.isExt(file, testExtensions) == isext, "Assertion failed for: " + file)
println("File: " + file + " -> Extension: " + FileExt.extractExt(file))
}
println("\nextractExt() tests:")
for ((url, ext) <- extractExtTestFiles) {
assert(FileExt.extractExt(url) == ext, "Assertion failed for: " + url)
println("Url: " + url + " -> Extension: " + FileExt.extractExt(url))
}
}</syntaxhighlight>
'''output'''
<pre>
Url: picture.jpg -> Extension: .jpg
Url: document.txt_backup -> Extension:
Url: .desktop -> Extension: .desktop
Url: CharacterModel.3DS -> Extension: .3DS
Url: file.resumé -> Extension:
Url: document -> Extension:
Url: café.png -> Extension: .png
Url: /etc/pam.d/login. -> Extension:
Url: http://mywebsite.com/picture/image.png -> Extension: .png
Url: IAmAFileWithoutExtension -> Extension:
Url: /etc/pam.d/login -> Extension:
Url: /etc/pam.d/login.a -> Extension: .a
Url: file.odd_one -> Extension:
Url: /path/to.my/file -> Extension:
Url: myuniquefile.longextension -> Extension: .longextension
Url: café.png -> Extension: .png
Url: file.resumé -> Extension:
Url: http://example.com/download.tar.gz -> Extension: .gz
</pre>
 
=={{header|sed}}==
<lang sed>s:.*\.:.:
s:\(^[^.]\|.*[/_]\).*::</lang> or <lang bash>sed -re 's:.*\.:.:' -e 's:(^[^.]|.*[/_]).*::'</lang>
 
<syntaxhighlight lang="sed">-Ene 's:.*(\.[A-Za-z0-9]+)$:\1:p'</syntaxhighlight>
 
Example of use:
<syntaxhighlight lang="bash">for F in "http://example.com/download.tar.gz" "CharacterModel.3DS" ".desktop" "document" "document.txt_backup" "/etc/pam.d/login"
do
EXT=`echo $F | sed -Ene 's:.*(\.[A-Za-z0-9]+)$:\1:p'`
echo "$F: $EXT"
done
</syntaxhighlight>
{{out}}
<pre>.jpg
http://example.com/download.tar.gz: .gz
.png
CharacterModel.3DS: .3DS
.longextension
.desktop: .desktop
IAmAFileWithoutExtension
document:
document.txt_backup:
/etc/pam.d/login:
</pre>
 
=={{header|Sidef}}==
<syntaxhighlight lang="ruby">func extension(filename) {
filename.match(/(\.[a-z0-9]+)\z/i).to_s
}
 
var files = [
'http://example.com/download.tar.gz',
'CharacterModel.3DS',
'.desktop',
'document',
'document.txt_backup',
'/etc/pam.d/login',
]
 
files.each {|f|
printf("%-36s -> %-11s\n", f.dump, extension(f).dump)
}</syntaxhighlight>
{{out}}
<pre>
"http://example.com/download.tar.gz" -> ".gz"
"CharacterModel.3DS" -> ".3DS"
".desktop" -> ".desktop"
"document" -> ""
"document.txt_backup" -> ""
"/etc/pam.d/login" -> ""
</pre>
 
=={{header|Smalltalk}}==
The Filename class has a convenient suffix method for that; so we convert the string to a filename and ask it:
<syntaxhighlight lang="smalltalk">names := #(
'http://example.com/download.tar.gz'
'CharacterModel.3DS'
'.desktop'
'a.desktop'
'document'
'document.txt_backup'
'/etc/pam.d/login'
).
names do:[:f |
'%-35s -> %s\n' printf:{ f . f asFilename suffix } on:Stdout
]</syntaxhighlight>
{{out}}
<pre>http://example.com/download.tar.gz -> gz
CharacterModel.3DS -> 3DS
.desktop ->
a.desktop -> desktop
document ->
document.txt_backup -> txt_backup
/etc/pam.d/login -> </pre>
Note: the task's description seems wrong to me; on a Unix machine, files beginning with "." are treated as hidden files (eg. in ls) and the suffix can be considered to be empty. As opposed to "a.desktop".
 
=={{header|SNOBOL4}}==
{{works with|SNOBOL4, SPITBOL for Linux}}
<syntaxhighlight lang="snobol4">
* Program: extract_extension.sbl
* To run: sbl extract_extension.sbl
* Description: Extract file extension
* Comment: Tested using the Spitbol for Linux version of SNOBOL4
 
filenames =
+ "http://example.com/download.tar.gz,"
+ "CharacterModel.3DS,"
+ ".desktop,"
+ "document,"
+ "document.txt_backup,"
+ "/etc/pam.d/login"
 
epat = ((span(&lcase &ucase '0123456789') ".") | "") . ext
p0
filenames ? (break(',') . s ',') | (len(1) rem) . s = "" :f(end)
reverse(s) ? epat
ext = reverse(ext)
output = ""
output = "Extension from file '" s "' is '" ext "'"
:(p0)
END
</syntaxhighlight>
{{out}}
<pre>
Extension from file 'http://example.com/download.tar.gz' is '.gz'
 
Extension from file 'CharacterModel.3DS' is '.3DS'
 
Extension from file '.desktop' is '.desktop'
 
Extension from file 'document' is ''
 
Extension from file 'document.txt_backup' is ''
 
Extension from file '/etc/pam.d/login' is ''
</pre>
 
=={{header|Standard ML}}==
 
This just demonstrates how to functionally extend the built-in function to the alpha-numeric restriction. Since file names starting with '.' are supposed to be "hidden" files in Unix, they're not considered as an extension.
<syntaxhighlight lang="sml">fun fileExt path : string =
getOpt (Option.composePartial (Option.filter (CharVector.all Char.isAlphaNum), OS.Path.ext) path, "")
 
val tests = [
"http://example.com/download.tar.gz",
"CharacterModel.3DS",
".desktop",
"document",
"document.txt_backup",
"/etc/pam.d/login"
]
 
val () = app (fn s => print (s ^ " -> \"" ^ fileExt s ^ "\"\n")) tests</syntaxhighlight>
{{out}}
<pre>http://example.com/download.tar.gz -> "gz"
CharacterModel.3DS -> "3DS"
.desktop -> ""
document -> ""
document.txt_backup -> ""</pre>
 
=={{header|Tcl}}==
Line 661 ⟶ 2,829:
Tcl's built in [http://wiki.tcl.tk/10072 file extension] command already almost knows how to do this, except it accepts any character after the dot. Just for fun, we'll enhance the builtin with a new subcommand with the limitation specified for this problem.
 
<langsyntaxhighlight Tcllang="tcl">proc assert {expr} { ;# for "static" assertions that throw nice errors
if {![uplevel 1 [list expr $expr]]} {
set msg "{$expr}"
Line 671 ⟶ 2,839:
proc file_ext {file} {
set res ""
regexp -nocase {(\.[a-z0-9]+)$} $file -> res
return $res
}
Line 681 ⟶ 2,849:
# and a test:
foreach {file ext} {
http://example.com/download.tar.gz .gz
picture.jpg .jpg
CharacterModel.3DS .3DS
http://mywebsite.com/picture/image.png .png
.desktop .desktop
myuniquefile.longextension .longextension
document ""
IAmAFileWithoutExtension ""
document.txt_backup ""
/path/to.my/file ""
/etc/pam.d/login ""
file.odd_one ""
} {
set res ""
assert {[file ext $file] eq $ext}
}</langsyntaxhighlight>
 
=={{header|TUSCRIPT}}==
<syntaxhighlight lang="tuscript">
$$ MODE DATA
$$ testcases=*
http://example.com/download.tar.gz
CharacterModel.3DS
.desktop
document
document.txt_backup
/etc/pam.d/login
picture.jpg
http://mywebsite.com/picture/image.png
myuniquefile.longextension
IamAFileWithoutExtension
path/to.my/file
file.odd_one
thisismine
 
$$ MODE TUSCRIPT,{}
 
BUILD C_GROUP A0 = *
DATA {&a}
DATA {\0}
 
BUILD S_TABLE legaltokens=*
DATA :.{1-00}{C:A0}{]}:
 
LOOP testcase=testcases
extension=STRINGS (testcase,legaltokens,0,0)
IF (extension=="") CYCLE
PRINT testcase, " has extension ", extension
ENDLOOP
</syntaxhighlight>
Output:
<pre>
http://example.com/download.tar.gz has extension .gz
CharacterModel.3DS has extension .3DS
.desktop has extension .desktop
picture.jpg has extension .jpg
http://mywebsite.com/picture/image.png has extension .png
myuniquefile.longextension has extension .longextension
</pre>
 
=={{header|VBScript}}==
<syntaxhighlight lang="vb">Function fileExt(fname)
Set fso = CreateObject("Scripting.FileSystemObject")
Set regex = new regExp
Dim ret
 
regex.pattern = "^[A-Za-z0-9]+$" 'Only alphanumeric characters are allowed
If regex.test(fso.GetExtensionName(fname)) = False Then
ret = ""
Else
ret = "." & fso.GetExtensionName(fname)
End If
fileExt = ret
End Function
'Real Start of Program
arr_t = Array("http://example.com/download.tar.gz", _
"CharacterModel.3DS", _
".desktop", _
"document", _
"document.txt_backup", _
"/etc/pam.d/login")
For Each name In arr_t
Wscript.Echo "NAME:",name
Wscript.Echo " EXT:","<" & fileExt(name) & ">"
Next</syntaxhighlight>
 
{{Out}}
<pre>NAME: http://example.com/download.tar.gz
EXT: <.gz>
NAME: CharacterModel.3DS
EXT: <.3DS>
NAME: .desktop
EXT: <.desktop>
NAME: document
EXT: <>
NAME: document.txt_backup
EXT: <>
NAME: /etc/pam.d/login
EXT: <></pre>
 
=={{header|Visual Basic}}==
{{works with|Visual Basic|6}}
<syntaxhighlight lang="vb">Option Explicit
'-----------------------------------------------------------------
Function ExtractFileExtension(ByVal Filename As String) As String
Dim i As Long
Dim s As String
i = InStrRev(Filename, ".")
If i Then
If i < Len(Filename) Then
s = Mid$(Filename, i)
For i = 2 To Len(s)
Select Case Mid$(s, i, 1)
Case "A" To "Z", "a" To "z", "0" To "9"
'these characters are OK in an extension; continue
Case Else
'this one is not OK in an extension
Exit Function
End Select
Next i
ExtractFileExtension = s
End If
End If
End Function
'-----------------------------------------------------------------
Sub Main()
Dim s As String
s = "http://example.com/download.tar.gz"
Debug.Assert ExtractFileExtension(s) = ".gz"
s = "CharacterModel.3DS"
Debug.Assert ExtractFileExtension(s) = ".3DS"
s = ".desktop"
Debug.Assert ExtractFileExtension(s) = ".desktop"
s = "document"
Debug.Assert ExtractFileExtension(s) = ""
s = "document.txt_backup"
Debug.Assert ExtractFileExtension(s) = ""
s = "/etc/pam.d/login"
Debug.Assert ExtractFileExtension(s) = ""
s = "desktop."
Debug.Assert ExtractFileExtension(s) = ""
s = "a.~.c"
Debug.Assert ExtractFileExtension(s) = ".c"
s = "a.b.~"
Debug.Assert ExtractFileExtension(s) = ""
s = "a.b.1~2"
Debug.Assert ExtractFileExtension(s) = ""
End Sub</syntaxhighlight>
 
=={{header|Wren}}==
{{trans|Kotlin}}
{{libheader|Wren-pattern}}
{{libheader|Wren-fmt}}
<syntaxhighlight lang="wren">import "./pattern" for Pattern
import "./fmt" for Fmt
 
var p = Pattern.new("/W") // matches any non-alphanumeric character
 
var extractFileExtension = Fn.new { |path|
if (path.isEmpty) return ""
var fileName = path.split("/")[-1]
if (path == fileName) fileName = path.split("\\")[-1]
var splits = fileName.split(".")
if (splits.count == 1) return ""
var ext = splits[-1]
return p.isMatch(ext) ? "" : "." + ext
}
 
var paths = [
"http://example.com/download.tar.gz",
"CharacterModel.3DS",
".desktop",
"document",
"document.txt_backup",
"/etc/pam.d/login",
"c:\\programs\\myprogs\\myprog.exe", // using back-slash as delimiter
"c:\\programs\\myprogs\\myprog.exe_backup" // ditto
]
for (path in paths) {
var ext = extractFileExtension.call(path)
Fmt.print("$-37s -> $s", path, ext.isEmpty ? "(empty string)" : ext)
}</syntaxhighlight>
 
=={{header|zkl}}==
The File object has a method splitFileName that does just that, returning a list of the parts. The method knows about the OS it was compiled on (Unix, Windows).
<lang zkl>T("picture.jpg","http://mywebsite.com/picture/image.png",
"myuniquefile.longextension","IAmAFileWithoutExtension",
"/path/to.my/file","file.odd_one").apply(File.splitFileName).println();</lang>
{{out}}
<pre>
http://example.com/download.tar.gz -> .gz
L(L("","","picture",".jpg"),
CharacterModel.3DS -> .3DS
L("","http://mywebsite.com/picture/","image",".png"),
.desktop -> .desktop
L("","","myuniquefile",".longextension"),
document -> (empty string)
L("","","IAmAFileWithoutExtension",""),
document.txt_backup -> (empty string)
L("","/path/to.my/","file",""),
/etc/pam.d/login -> (empty string)
L("","","file",".odd_one"))
c:\programs\myprogs\myprog.exe -> .exe
c:\programs\myprogs\myprog.exe_backup -> (empty string)
</pre>
 
The last one is an odd duck so some code is in order:
=={{header|XPL0}}==
<lang zkl>fcn exonly(fileName){
<syntaxhighlight lang="xpl0">func Ext(Str); \Return address of extension
var re=RegExp(0'|\.[a-zA-Z0-9]+$|);
char Str; int I, C, End;
ext:=File.splitFileName(fileName)[-1];
string 0;
if(not re.matches(ext)) return("");
[I:= 0;
while Str(I) do I:= I+1;
End:= I;
loop [I:= I-1;
if Str(I) = ^. then return @Str(I);
if I = 0 then return @Str(End); \no dot found, return null
C:= Str(I);
if C>=^A & C<=^Z ! C>=^a & C<=^z ! C>=^0 & C<=^9 then \OK
else return @Str(End); \illegal char, return null
];
];
 
[Text(0, Ext("http://example.com/download.tar.gz")); CrLf(0);
Text(0, Ext("CharacterModel.3DS")); CrLf(0);
Text(0, Ext(".desktop")); CrLf(0);
Text(0, Ext("document")); CrLf(0);
Text(0, Ext("document.txt_backup")); CrLf(0);
Text(0, Ext("/etc/pam.d/login")); CrLf(0);
]</syntaxhighlight>
 
{{out}}
<pre>
.gz
.3DS
.desktop
 
 
 
</pre>
 
=={{header|zkl}}==
 
The File object has a method splitFileName that does just that, returning a list of the parts. The method knows about the OS it was compiled on (Unix, Windows).
<syntaxhighlight lang="zkl">fcn extractFileExtension(name){
var [const] valid=Walker.chain(".",["a".."z"],["A".."Z"],["0".."9")).pump(String);
ext:=File.splitFileName(name)[-1];
if(ext - valid) ext="";
ext
}</langsyntaxhighlight>
<langsyntaxhighlight lang="zkl">foreach nm in (T("picture.jpg","http://mywebsiteexample.com/picture/imagedownload.tar.gz","CharacterModel.png3DS",
".desktop","document",
"myuniquefile.longextension","IAmAFileWithoutExtension",
"document.txt_backup","/etc/pam.d/login")){
"/path/to.my/file","file.odd_one").apply(exonly).println();</lang>
println("%35s : %s".fmt(nm,extractFileExtension(nm)));
}</syntaxhighlight>
{{out}}
Note: on Unix, .desktop is a hidden file, not an extension.
<pre>L(".jpg",".png",".longextension","","","")</pre>
<pre>
http://example.com/download.tar.gz : .gz
CharacterModel.3DS : .3DS
.desktop :
document :
document.txt_backup :
/etc/pam.d/login :
</pre>
2,069

edits