BETA

.NetのDirectory.GetFilesメソッドで余計なファイル名を取得してしまう

投稿日:2020-01-10
最終更新:2020-01-10

ファイル検索したいときにちょっと困ったので備忘録として書きます。

"*.拡張子"でファイルを検索すると、余計なファイルまで取得する

// Testフォルダから、拡張子が.xlsのファイルを検索したい  
var files = Directory.GetFiles("Test","*.xls", SearchOption.TopDirectoryOnly);  

// 結果 xls以外も取れてしまう  
Test\testXLS.xls  
Test\testXLSX.xlsx    <-- いらない  
Test\testXLSM.xlsm    <-- いらない  

Directory.GetFilesでは、Windowsのエクスプローラのように、ワイルドカードとして「*」や「?」を使った検索ができる。
しかし、検索する拡張子が"*.xls"のようにぴったり3文字だと、".xlsx"や、".xlsm"のような、xlsから始まる余計なファイルを取得してしまう

Directory.GetFiles メソッド (System.IO) | Microsoft Docsにも、

"*.txt" などの searchPattern でアスタリスクのワイルドカード文字を使用すると、指定した拡張機能の文字数は次のようになります。

指定された拡張子の長さが完全に3文字である場合、メソッドは、指定された拡張子で始まる拡張子を持つファイルを返します。 たとえば、"*.xls" は、"book.xls" と "book.xlsx" の両方を返します。

それ以外の場合、メソッドは、指定された拡張子と完全に一致するファイルを返します。 たとえば、"*.ai" は "file.ai" を返しますが、"file. aif" は返しません。

と書かれている。まとめると、

  • 拡張子がぴったり3文字(.xls .csv .txt など)
    • "*.xls"では、拡張子が".xls"で始まるファイル全てが対象になる
    • 余計なファイルをはじきたいなら、対処が必要
  • それ以外(.md .c .xlsxなどの2文字以下または、4文字以上)
    • "*.md"や"*.xlsx"で検索しても、指定の拡張子のファイルだけが取れる。問題ない

対処法

上で書いたように、拡張子がぴったり3文字(今回は.xls)のファイルを検索するには、何かしらの対処が必要となる。以下に手順を示す。

  1. Directory.GetFilesメソッドで、検索パターンとして"*.拡張子"でファイル名の配列を取得する
  2. 取得した配列を拡張子でフィルタリングする
// 1. この時点では、拡張子が.xlsから始まるファイルすべてを取得する  
var files = Directory.GetFiles("Test", "*.xls", SearchOption.TopDirectoryOnly)  
// 2. 取得した配列の末尾が.xlsかどうかチェックする  
var query = files.Where(  
    x => x.EndsWith(".xls", // 文字列が .xlsで終わるかどうか  
    StringComparison.OrdinalIgnoreCase // 大文字、小文字を区別しない  
);  

// 結果  
Test\testXLS.xls  

xlsだけを取得することが出来ました。
このように拡張子を指定して、厳密なファイルの検索をする際は面倒ですが2重にフィルタリングする必要があります。

ほかのファイル取得メソッドについて

.Netにはファイルを取得するメソッドがほかにもあります。
DirectoryクラスのEnumerateFilesメソッドや、DirectoryInfoクラスのGetFilesメソッドがそうです。
これらのメソッドも、2重のフィルタリングが必要になります。

なんで余計なファイル名を取得するの?

Directory.GetFiles メソッド (System.IO) | Microsoft Docsによると、

この方法では、8.3形式のファイル名形式と長いファイル名形式の両方を使用してファイル名をチェックするため、「* 1 * .txt」のような検索パターンで予期しないファイル名が返される場合があります。 たとえば、「* 1 * .txt」という検索パターンを使用すると、「longfilename.txt」が返されます。これは、8.3形式の同等のファイル名形式が「LONGFI1.TXT」であるためです。

どうやら、8.3形式なるものが原因らしい。

8.3形式とは

8.3形式 - Wikipediaによると、

8.3形式は、MS-DOSや Windows 3.x までのWindowsのファイル名、Windows 95 以降のWindowsの「短いファイル名 (short filename; SFN)」などに適用される、ファイル名の形式である。

拡張子以外に最大8バイト、拡張子に最大3バイト(ドットを数えずに)が使えるため、「8.3」と表現される。拡張子は最大1つだけ使える。

最大255文字の「長いファイル名 (long filename; LFN)」がサポートされるようになった Windows 95 以降のWindowsでは、おのおののファイルに対し、8.3形式の「短いファイル名 (short filename; SFN)」が自動的に生成される

まとめると、

  • 昔のWindowsではファイル名は最大「8文字.3文字」の制限があった
  • 今はこの制限はないけど、各ファイルには「8文字.3文字」の名前を自動的に生成している

MS-DOSのdirコマンドで見てみる

Dir /xでディレクトリ内のファイルを「短いファイル名」と一緒に出力できるので、実行してみた。

2020/01/10  09:25    <DIR>                       .  
2020/01/10  09:25    <DIR>                       ..  
2019/12/26  14:42               143              testXLS.xls  
2019/12/26  14:42               143 TESTXL~2.XLS testXLSM.xlsm  
2019/12/26  14:42               143 TESTXL~1.XLS testXLSX.xlsx  
               3 個のファイル                 429 バイト  
               2 個のディレクトリ  41,476,804,608 バイトの空き領域  

一番右が正確なファイル名で、その左隣には「短いファイル名」が表示されています。

".xlsx"や".xlsm"の「短いファイル名」での拡張子は、".xls"になります。
Directory.GetFilesメソッドは実行結果にある「短いファイル名」からも"*.xls"を検索します。
その結果、".xls"以外のファイルも取得してしまうようです。

まとめ

  • 拡張子が3文字のファイルを検索するときは、2重にフィルタリングする
// 1. この時点では、拡張子が.xlsから始まるファイルすべてを取得する  
var files = Directory.GetFiles("Test", "*.xls", SearchOption.TopDirectoryOnly)  

// 2. 取得した配列の末尾が.xlsかどうかチェックする  
var query = files.Where(  
    x => x.EndsWith(".xls", // 文字列が .xlsで終わるかどうか  
    StringComparison.OrdinalIgnoreCase // 大文字、小文字を区別しない  
);  
  • それ以外(拡張子が2文字以下または、4文字以上)はフィルタリングしなくてもよい
var files = Directory.GetFiles("Test","*.xlsx", SearchOption.TopDirectoryOnly);  
技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

@takatokaの技術ブログ

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう