File extension is in extensions list

From Rosetta Code
Revision as of 12:38, 2 September 2016 by rosettacode>Smls (→‎{{header|Perl 6}}: update to comply with new task description)
File extension is in extensions list is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Filename extensions are a rudimentary but commonly used way of identifying files types.

Given an arbitrary filename and a list of extensions, tell whether the filename has one of those extensions.

Notes:

  • The check should be case insensitive.
  • The extension must occur at the very end of the filename, and be immediately preceded by a dot (.).
  • You may assume that none of the given extensions are the empty string, and none of them contain a dot. Other than that they may be arbitrary strings.


Extra credit:
Allow extensions to contain dots. This way, users of your function/program have full control over what they consider as the extension in cases like archive.tar.gz.
Please state clearly whether or not your solution does this.
Test cases

The following test cases all assume this list of extensions:   zip, rar, 7z, gz, archive, A##

Filename Result
MyData.a## true
MyData.tar.Gz true
MyData.gzip false
MyData.7z.backup false
MyData... false
MyData false

If your solution does the extra credit requirement, add tar.bz2 to the list of extensions, and check the following additional test cases:

Filename Result
MyData_v1.0.tar.bz2 true
MyData_v1.0.bz2 false
Motivation

Checking if a file is in a certain category of file formats with known extensions (e.g. archive files, or image files) is a common problem in practice, and may be approached differently than extracting and outputting an arbitrary extension (see e.g. FileNameExtensionFilter in Java).

It also requires less assumptions about the format of an extension, because the calling code can decide what extensions are valid.

For these reasons, this task exists in addition to the Extract file extension task.

Related tasks

AWK

<lang AWK>

  1. syntax: GAWK -f FILE_EXTENSION_IS_IN_EXTENSIONS_LIST.AWK

BEGIN {

   n = split("txt,gz,bat,c,c++,exe,pdf",arr,",")
   for (i=1; i<=n; i++) {
     ext_arr[tolower(arr[i])] = ""
   }
   filenames = "c:,txt,text.txt,text.TXT,test.tar.gz,test/test2.exe,test,foo.c,foo.C,foo.C++,foo.c#,foo.zkl,document.pdf"
   n = split(filenames,fn_arr,",")
   print("      EXT   FILENAME")
   for (i=1; i<=n; i++) {
     ext = ""
     if (fn_arr[i] ~ /\./) {
       ext = tolower(fn_arr[i])
       while (match(ext,/\./) > 0) {
         ext = substr(ext,RSTART+1)
       }
     }
     ans = (ext in ext_arr) ? "true" : "false"
     printf("%-5s %-5s %s\n",ans,ext,fn_arr[i])
   }
   exit(0)

} </lang>

Output:
      EXT   FILENAME
false       c:
false       txt
true  txt   text.txt
true  txt   text.TXT
true  gz    test.tar.gz
true  exe   test/test2.exe
false       test
true  c     foo.c
true  c     foo.C
true  c++   foo.C++
false c#    foo.c#
false zkl   foo.zkl
true  pdf   document.pdf


C

According to this, an extension is whatever comes after the last dot. Dotless filename won't match anything.

<lang c>#include <stdio.h>

  1. include <string.h>
  2. include <stdbool.h>

/* this should be the way we check if we can use posix/bsd strcasecmp */

  1. if !defined(_BSD_SOURCE) && !defined(_DEFAULT_SOURCE)
  2. include <ctype.h>

int strcasecmp(const char *s1, const char *s2) {

 for(; tolower(*s1) == tolower(*s2); ++s1, ++s2)
   if(*s1 == 0)
     return 0;
 return *(unsigned char *)s1 < *(unsigned char *)s2 ? -1 : 1;

}

  1. else
  2. include <strings.h>
  3. endif


bool ext_is_in_list(const char *filename, const char *extlist[]) {

 size_t i;
 const char *ext = strrchr(filename, '.');
 if (ext) {
   for (i = 0; extlist[i] != NULL; ++i) {
     if (strcasecmp(ext, extlist[i]) == 0)

return true;

   }
 }
 return false;

}


// testing const char *fnames[] = {

   "text.txt",
   "text.TXT",
   "test.tar.gz",
   "test/test2.exe",
   "test\\test2.exe",
   "test",
   "a/b/c\\d/foo",
   "foo.c",
   "foo.C",
   "foo.C++",
   "foo.c#",
   "foo.zkl",
   "document.pdf",
   NULL

};

const char *exts[] = {

   ".txt", ".gz", ".bat", ".c", 
   ".c++", ".exe", ".pdf",
   NULL

};

int main(void) {

 size_t i;
 for (i = 0; fnames[i]; ++i) {
   printf("%s: %s\n", fnames[i], 

ext_is_in_list(fnames[i], exts) ? "yes" : "no");

 }
 return 0;

} </lang>

D

<lang d>void main() {

   import std.stdio, std.string, std.path, std.algorithm;
   immutable exts = [".txt", ".gz", ".bat", ".c", ".c++", ".exe", ".pdf"];
   immutable fileNames =
   "text.txt
   text.TXT
   test.tar.gz
   test/test2.exe
   test\\test2.exe
   test
   a/b/c\\d/foo
   foo.c
   foo.C
   foo.C++
   foo.c#
   foo.zkl
   document.pdf";
   foreach (fName; fileNames.split)
       writeln(fName, ": ", exts.canFind(fName.extension.toLower));

}</lang>

Output:
text.txt: true
text.TXT: true
test.tar.gz: true
test/test2.exe: true
test\test2.exe: true
test: false
a/b/c\d/foo: false
foo.c: true
foo.C: true
foo.C++: true
foo.c#: false
foo.zkl: false
document.pdf: true

Fortran

The plan is to use the extractor function for file name extensions that was defined in another problem to obtain the extension's text, then use that text to index a character string of approved extensions. These are defined with the period included, but no extension's text can include a period - all start with a period and continue with letters or digits only - so the specification of a list of such items need not mess about with quotes and commas and so forth. The deed is done via function EXTIN(FNAME,LIST), but there is a lot of support stuff in the absence of an existing library to call upon.

Petty details include the list employing capitals only, as the match is to not distinguish capital from lower case letters, so in the usual way the candidate extension's text is converted to capitals. The list could be subjected to UPCASE, but it is bad form to damage what might be a constant, and one possibly in read-only storage at that. An internal working copy could be made which would then be fed to UPCASE, except that this would be a waste on every invocation. A further trick involves appending a period to the candidate text so that for example ".JP" becomes ".JP." - otherwise a ".JP" would be found in the sequence ".JPG" which would be wrong, so as a result, the list of texts must have a period appended to its last entry, otherwise it would not be findable. Again, this could be done internally, via INDEX(LIST//".",EXT(1:L)//".") at a run-time cost.

Some systems supply an UPCASE (or similar name) to convert text that hopefully would run faster than this example, which relies on searching for the position in a list of letters. A very common alternative is to calculate using character code numerical values, via something like<lang Fortran> IT = ICHAR(TEXT(I:I)) - ICHAR("a") !More symbols precede "a" than "A".

       IF (IT.GE.0 .AND. IT.LE.25) TEXT(I:I) = CHAR(IT + ICHAR("A"))	!In a-z? Convert!</lang> except that this relies on the letters having contiguous character codes, and in EBCDIC they don't - other symbols are mixed in. (Honest!) Faster still would be to use the character code to index an array of 256 pre-computed values.

A final annoyance is the presence of trailing spaces because character variables are of fixed size and so must be made "surely long enough" for the longest expectation. This may not cause trouble on output as spaces look just as blank as blank space, but they may well cause trouble in the internal tests. Thus integer function LSTNB reports the last non-blank, and so a test text can be presented with no trailing spaces, as in TEST(I)(1:LSTNB(TEST(I))) or similar. With Fortran 2003, there is a facility for redefining the sizes of character variables on-the-fly so that this problem can be evaded.

The MODULE protocol is employed for the convenience of not having to respecify the type of the functions in every calling routine, and also to facilitate the collection of types of characters. Otherwise, prior to F90 there would have to be various COMON statements, or routines would each simply respecify whatever character sets they needed.<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)
      INTEGER MEXT		!A fixed bound.
      PARAMETER (MEXT = 28)	!This should do.
      CONTAINS
       INTEGER FUNCTION LSTNB(TEXT)  !Sigh. Last Not Blank.

Concocted yet again by R.N.McLean (whom God preserve) December MM. Code checking reveals that the Compaq compiler generates a copy of the string and then finds the length of that when using the latter-day intrinsic LEN_TRIM. Madness! Can't DO WHILE (L.GT.0 .AND. TEXT(L:L).LE.' ') !Control chars. regarded as spaces. Curse the morons who think it good that the compiler MIGHT evaluate logical expressions fully. Crude GO TO rather than a DO-loop, because compilers use a loop counter as well as updating the index variable. Comparison runs of GNASH showed a saving of ~3% in its mass-data reading through the avoidance of DO in LSTNB alone. Crappy code for character comparison of varying lengths is avoided by using ICHAR which is for single characters only. Checking the indexing of CHARACTER variables for bounds evoked astounding stupidities, such as calculating the length of TEXT(L:L) by subtracting L from L! Comparison runs of GNASH showed a saving of ~25-30% in its mass data scanning for this, involving all its two-dozen or so single-character comparisons, not just in LSTNB.

        CHARACTER*(*),INTENT(IN):: TEXT	!The bumf. If there must be copy-in, at least there need not be copy back.
        INTEGER L		!The length of the bumf.
         L = LEN(TEXT)		!So, what is it?
   1     IF (L.LE.0) GO TO 2	!Are we there yet?
         IF (ICHAR(TEXT(L:L)).GT.ICHAR(" ")) GO TO 2	!Control chars are regarded as spaces also.
         L = L - 1		!Step back one.
         GO TO 1		!And try again.
   2     LSTNB = L		!The last non-blank, possibly zero.
        RETURN			!Unsafe to use LSTNB as a variable.
       END FUNCTION LSTNB	!Compilers can bungle it.
       SUBROUTINE UPCASE(TEXT)	!In the absence of an intrinsic...

Converts any lower case letters in TEXT to upper case... Concocted yet again by R.N.McLean (whom God preserve) December MM. Converting from a DO loop evades having both an iteration counter to decrement and an index variable to adjust. Could use the character code to index an array, instead of searching a sequence...

        CHARACTER*(*) TEXT	!The stuff to be modified.
        INTEGER I,L,IT	!Fingers.
         L = LEN(TEXT)		!Get a local value, in case LEN engages in oddities.
         I = L			!Start at the end and work back..
   1     IF (I.LE.0) RETURN 	!Are we there yet? Comparison against zero should not require a subtraction.
         IT = INDEX(LITTLELETTERS,TEXT(I:I))	!Well?
         IF (IT .GT. 0) TEXT(I:I) = BIGLETTERS(IT:IT)	!One to convert?
         I = I - 1		!Back one.
         GO TO 1		!Inspect..
       END SUBROUTINE UPCASE	!Easy. In EBCDIC, letters are NOT contiguous, and other symbols appear.
       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.
       LOGICAL FUNCTION EXTIN(FNAME,LIST)	!Which file name extension?
        CHARACTER*(*) FNAME	!The file name.
        CHARACTER*(*) LIST	!A sequence, separated by the periods. Like ".EXR.TXT.etc"
        CHARACTER*(MEXT) EXT	!A scratchpad.
         EXT = FEXT(FNAME)	!So, obtain the file name's extension.
         L = LSTNB(EXT)	!Find its last non-blank.
         IF (L.LE.0) THEN	!A null text?
           EXTIN = .FALSE.		!Yep. Can't be in the list.
          ELSE			!Otherwise,
           CALL UPCASE(EXT(1:L))	!Simplify.
           EXTIN = INDEX(LIST,EXT(1:L)//".") .GT. 0	!Found somewhere?
         END IF		!The period can't be a character in an extension name.
       END FUNCTION EXTIN	!So, possibilities collapse.
     END MODULE TEXTGNASH	!Enough for this.
     PROGRAM POKE
     USE TEXTGNASH
     INTEGER I,L
     CHARACTER*80 TEST(6)	!A collection.
     DATA TEST/
    1 "Picture.jpg",
    2 "http://mywebsite.com/picture/image.png",
    3 "myuniquefile.longextension",
    4 "IAmAFileWithoutExtension",
    5 "/path/to.my/file",
    6 "file.odd_one"/
     CHARACTER*(*) IMAGE
     PARAMETER (IMAGE = ".PNG.JPG.")	!All in capitals, and ending with a . too.
     WRITE (6,1) IMAGE
   1 FORMAT ("To note file name extensions that are amongst ",A,/
    1 "File name",40X,"Extension",7X,"Test")
     DO I = 1,6	!Step through the candidates.
      L = LSTNB(TEST(I))	!Thus do without trailing spaces.
      WRITE (6,2) TEST(I),FEXT(TEST(I)(1:L)),EXTIN(TEST(I)(1:L),IMAGE)
   2  FORMAT (A48,A16,L)
     END DO
     END</lang>

Results, with "image" style files being the ones to approve.

To note file name extensions that are amongst .PNG.JPG.
File name                                        Extension       Test
Picture.jpg                                     .jpg             T
http://mywebsite.com/picture/image.png          .png             T
myuniquefile.longextension                      .longextension   F
IAmAFileWithoutExtension                                         F
/path/to.my/file                                                 F
file.odd_one                                                     F

Haskell

<lang Haskell> import Data.List import qualified Data.Char as Ch

toLower :: String -> String toLower = map Ch.toLower

isExt :: String -> [String] -> Bool isExt filename extensions = any (`elem` (tails . toLower $ filename)) $ map toLower extensions </lang>

The code defining isExt could be done point free: <lang Haskell> isExt filename = any (`elem` (tails . toLower $ filename)) . map toLower </lang>

Overcoming the one-liner urge on behalf of being more comprehensible would give: <lang Haskell> isExt filename extensions = any (`elem` allTails) lowerExtensions

                           where allTails = tails . toLower $ filename

lowerExtensions = map toLower extensions </lang>

J

Solution: <lang j>getExt=: }.~ i:&'.' isExt=: e.~&:(tolower&.>) getExt&.>@boxopen</lang> Usage: <lang j> Exts=: <;._1 ' .o .ijs .ij .p'

  TestFiles=: <;._1 ' foo.o blab.iJ dram. lat.P win.dll foo'
  Exts isExt TestFiles

1 1 0 1 0 0</lang>

Java

Works with: Java version 1.5+

<lang java5>import java.util.Arrays; import java.util.Comparator;

public class FileExt{ public static void main(String[] args){ String[] tests = {"text.txt", "text.TXT", "test.tar.gz", "test/test2.exe", "test\\test2.exe", "test", "a/b/c\\d/foo"}; String[] exts = {".txt",".gz","",".bat"};

System.out.println("Extensions: " + Arrays.toString(exts) + "\n");

for(String test:tests){ System.out.println(test +": " + extIsIn(test, exts)); } }

public static boolean extIsIn(String test, String... exts){ int lastSlash = Math.max(test.lastIndexOf('/'), test.lastIndexOf('\\')); //whichever one they decide to use today String filename = test.substring(lastSlash + 1);//+1 to get rid of the slash or move to index 0 if there's no slash

//end of the name if no dot, last dot index otherwise int lastDot = filename.lastIndexOf('.') == -1 ? filename.length() : filename.lastIndexOf('.'); String ext = filename.substring(lastDot);//everything at the last dot and after is the extension

Arrays.sort(exts);//sort for the binary search

return Arrays.binarySearch(exts, ext, new Comparator<String>() { //just use the built-in binary search method @Override //it will let us specify a Comparator and it's fast enough public int compare(String o1, String o2) { return o1.compareToIgnoreCase(o2); } }) >= 0;//binarySearch returns negative numbers when it's not found } } </lang>

Output:
Extensions: [.txt, .gz, , .bat]

text.txt: true
text.TXT: true
test.tar.gz: true
test/test2.exe: false
test\test2.exe: false
test: true
a/b/c\d/foo: true

Using FileNameExtensionFilter

Works with: Java version 6+

This version is the same as the main version only replace the definition for extIsIn with: <lang java5>public static boolean extIsIn(String test, String... exts){ for(int i = 0; i < exts.length; i++){ exts[i] = exts[i].replaceAll("\\.", ""); } return (new FileNameExtensionFilter("extension test", exts)).accept(new File(test)); }</lang> It would be one line if not for the dot requirement. FileNameExtensionFilter requires that the extensions have no dots. It also requires that the extensions contain characters (i.e. not the empty string) so that test would need to be removed.

jq

Works with: jq version 1.4

<lang jq># Input: filename

  1. Output: if the filename ends with one of the extensions (ignoring case), output that extension; else output null.
  2. Assume that the list of file extensions consists of lower-case strings, including a leading period.

def has_extension(list):

 def ascii_downcase: explode | map( if 65 <= . and . <= 90 then . + 32  else . end) | implode;
 rindex(".") as $ix
 | if $ix then (.[$ix:] | ascii_downcase) as $ext
      | if list | index($ext) then $ext else null end
   else null
   end;</lang>

Examples: <lang jq>("c:", "txt", "text.txt", "text.TXT", "foo.c", "foo.C++", "document.pdf") | has_extension([".txt", ".c"]) as $ext | if $ext then "\(.) has extension \($ext)"

 else "\"\(.)\" does not have an admissible file extension"
 end</lang>
Output:

<lang jq>$ jq -r -n -f File_extension_is_in_extensions_list.jq "c:" does not have an admissible file extension "txt" does not have an admissible file extension text.txt has extension .txt text.TXT has extension .txt foo.c has extension .c "foo.C++" does not have an admissible file extension "document.pdf" does not have an admissible file extension</lang>

PARI/GP

The examples were taken from the AWK code.

<lang parigp>lower(s)= {

 my(v=Vecsmall(s));
 for(i=1,#v,
   if(v[i]<91 && v[i]>64, v[i]+=32)
 );
 \\Strchr(v); \\ Use to return a string rather than a t_VECSMALL
 v;

} checkExt(ext, file)= {

 ext=apply(lower,ext);
 my(v=lower(file),e);
 for(i=1,#ext,
   e=ext[i];
   if(#v>#e && v[#v-#e+1..#v]==e && v[#v-#e]==46,
     return(1)
   )
 );

} ext=["txt","gz","bat","c","c++","exe","pdf"]; filenames=["c:","txt","text.txt","text.TXT","test.tar.gz","test/test2.exe","test","foo.c","foo.C","foo.C++","foo.c#","foo.zkl","document.pdf"]; select(f -> checkExt(ext, f), filenames)</lang>

Output:
%1 = ["text.txt", "text.TXT", "test.tar.gz", "test/test2.exe", "foo.c", "foo.C", "foo.C++", "document.pdf"]

Perl 6

Does the extra credit requirement.

<lang perl6>sub check-extension ($filename, *@extensions) {

   so $filename ~~ /:i '.' @extensions $/

}</lang>

Testing: <lang perl6>my @extensions = <zip rar 7z gz archive A## tar.bz2>; my @files= <

   MyData.a##  MyData.tar.Gz  MyData.gzip  MyData.7z.backup  MyData...  MyData
   MyData_v1.0.tar.bz2  MyData_v1.0.bz2

>; say "{$_.fmt: '%-19s'} - {check-extension $_, @extensions}" for @files;</lang>

Output:
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

Python

<lang Python> import os

def isExt(filename, extensions):

   return os.path.splitext(filename.lower())[-1] in [e.lower() for e in extensions]

</lang>

Racket

<lang racket>#lang racket/base (require (only-in srfi/13 string-suffix-ci?)

        (only-in racket/format ~a))

(define ((in-extensions-list? extns) f)

 (findf (λ (e) (string-suffix-ci? e f)) extns))

(define e.g.-extns '(".txt" ".gz" ".bat" ".c" ".c++" ".exe" ".pdf")) (define in-e.g.-extns-list? (in-extensions-list? e.g.-extns)) (define file-names

 (list "c:" "txt" "text.txt" "text.TXT" "test.tar.gz" "test/test2.exe"
       "test" "foo.c" "foo.C" "foo.C++" "foo.c#" "foo.zkl" "document.pdf"))

(for ((f (in-list file-names)))

 (printf "~a ~a~%" (~a #:width 20 f) (or (in-e.g.-extns-list? f) "[NO EXTENSION]")))</lang>
Output:
c:                   [NO EXTENSION]
txt                  [NO EXTENSION]
text.txt             .txt
text.TXT             .txt
test.tar.gz          .gz
test/test2.exe       .exe
test                 [NO EXTENSION]
foo.c                .c
foo.C                .c
foo.C++              .c++
foo.c#               [NO EXTENSION]
foo.zkl              [NO EXTENSION]
document.pdf         .pdf

REXX

Programming note:   extra code was added to:

  •   display some error/warning messages
  •   handle the case of the filename having a path
  •   handle the case of different types of path separators
  •   handle the case of the filename having no file extension
  •   handle cases of the filename ending in a period
  •   handle cases of the filename ending in multiple periods
  •   handle cases of blanks in the filename and/or extension

<lang rexx>/*REXX program displays if a filename has a known extension (as per a list of EXTs).*/

   /*╔═════════════════════════════════════════════════════════════════════╗
     ║ The list of extensions below have  blanks  encoded as  'ff'x.       ║
     ║ The extension pointed to has a  'ff'x  after the period ───►──┐     ║
     ║ (which is used to indicate a true blank,  due to              │     ║
     ║ REXX's use of blanks in lists as delimiters).                 │     ║
     ╚═══════════════════════════════════════════════════════════════↓═════╝*/

extensions= '.bat .cmd .com .dat .dll .exe .ini .jpg .jpeg .log .txt . ys'

                                                /* [↑]  above would be some file EXTs. */

parse arg fn /*obtain the required argument from CL.*/ if fn== then exit /*No filename specified? Then exit.*/ afn=translate( fn, '/', "\") /*handle both versions of pathSep. */ afn=translate(afn, 'ff'x, " ") /* ··· and also handle true blanks. */ afn=substr(afn, lastpos('/', afn) +1) /*pick off the filename from the path. */ p=lastpos(., afn) /*find last position of a dot in name.*/ if p==0 | p==length(afn) then do /*no dot, or dot is at the end of name.*/

                              say 'Filename ' fn " has no extension."
                              exit
                              end

ft=substr(afn, p) /*pickoff the fileType (aka, fileExt).*/ say 'ft=' ft upper ft extensions /*uppercase a couple of REXX variables.*/ if wordpos(ft,extensions)==0 then _='an unknown'

                             else _= 'a known'

say 'Filename ' fn "has" _ 'extension.' /*stick a fork in it, we're all done. */</lang>

Ruby

<lang ruby>extensions = [".c",".o",""] ["foo.C","foo.zkl","foo","foo."].each do |f|

 puts "%5s : %s" % [extensions.include?( File.extname(f).downcase ), f]

end</lang>

Output:
 true : foo.C
false : foo.zkl
 true : foo
 true : foo.

Scala

<lang Scala>def isExt(fileName: String, extensions: List[String]): Boolean = {

   extensions.map { _.toLowerCase }.exists { fileName.toLowerCase endsWith _ }

}</lang>

Tcl

<lang tcl># Note that these are already all in lower case set exts {".txt" ".gz" ".bat" ".c" ".c++" ".exe" ".pdf"} set filenames {

   text.txt
   text.TXT
   test.tar.gz
   test/test2.exe
   test\\test2.exe
   test
   a/b/c\\d/foo
   foo.c
   foo.C
   foo.C++
   foo.c#
   foo.zkl
   document.pdf

}

foreach name $filenames {

   set ext [file extension $name]
   if {[string tolower $ext] in $exts} {

puts "'$ext' (of $name) is present"

   } else {

puts "'$ext' (of $name) is absent"

   }

}</lang>

Output:
'.txt' (of text.txt) is present
'.TXT' (of text.TXT) is present
'.gz' (of test.tar.gz) is present
'.exe' (of test/test2.exe) is present
'.exe' (of test\test2.exe) is present
'' (of test) is absent
'' (of a/b/c\d/foo) is absent
'.c' (of foo.c) is present
'.C' (of foo.C) is present
'.C++' (of foo.C++) is present
'.c#' (of foo.c#) is absent
'.zkl' (of foo.zkl) is absent
'.pdf' (of document.pdf) is present

zkl

<lang zkl>var exts=T(".c",".o",""); fcn hasExtension(fname){ exts.holds(File.splitFileName(fname)[3].toLower()) } T("foo.C","foo.zkl","foo","foo.").apply(hasExtension).println();</lang>

Output:
L(True,False,True,True)