日々常々

ふつうのプログラマがあたりまえにしたいこと。

コマンドプロンプトのdirとかで予期しないファイルが引っ掛かる

Windowsコマンドプロンプトでディレクトリの中身を見るには dir を使います。例えば C:\sample で実行するとこんな感じの表示になります。

C:\sample>dir
 ドライブ C のボリューム ラベルがありません。
 ボリューム シリアル番号は ABCD-EFGH です

 C:\sample のディレクトリ

2010/10/22  23:39    <DIR>          .
2010/10/22  23:39    <DIR>          ..
2010/10/22  23:23                 0 abcdefg.txt
2010/10/22  23:24                 0 abcdefg1.txt
2010/10/22  23:23                 0 abcdefghij2.txt
2010/10/22  23:23                 0 abcdefghij3.txt
2010/10/22  23:24                 0 xyz.txt
2010/10/22  23:24                 0 xyz.txt1
2010/10/22  23:24                 0 テキストファイル1.txt
2010/10/22  23:24                 0 テキストファイル2.txt
2010/10/22  23:24                 0 文章1.txt
2010/10/22  23:24                 0 文章2.txt
              10 個のファイル                   0 バイト
               2 個のディレクトリ              32 バイトの空き領域

dir だけだと一覧を表示するだけですが、続けてファイル名を入力すると目的のファイルだけを表示します。ファイル名を全て入力したら1ファイル出るだけなのであまり面白くありませんが、ワイルドカードが使用できます。使えるのは二種類で、"*"が任意の文字列、"?"が任意の1文字になります。1文字は全角半角問わずに1文字です。*1

C:\sample>dir /b ????ファイル*
テキストファイル1.txt
テキストファイル2.txt

「文章」から始まるファイルを探したければ "dir 文章*" と入力すれば良いです。ここまでは多くの人も知ってると思いますし、特に問題はないと思います。

本題。ワイルドカードを使って目的のファイルを抽出しているつもりなのに、意図しないファイルまでヒットしてしまう事があります。今まで例示しているディレクトリで "dir *1.txt" と入力して下さい。

C:\sample>dir /b *1.txt
abcdefg1.txt
abcdefghij2.txt
xyz.txt1
テキストファイル1.txt

……全然意図通りじゃありませんね。"abcdefg1.txt"が唯一目的のファイルです。他ははずれ。"テキストファイル1.txt"は1が全角なのであたって欲しく無かったです。全角半角を問わないのならば"文章1.txt"が当たらないのはおかしいです。"xyz.txt1"とか何がどうして当たってるのやら。



答え。知ってる人は知ってるのでしょうが、知らない人は知りません。私は記憶の片隅にはあったのですが、思い当たりませんでした。これは「8.3形式」と呼ばれる、短い名前がヒットしています。短い名前*2は、 /x を指定すれば表示されます。

C:\sample>dir /x
 ドライブ C のボリューム ラベルがありません。
 ボリューム シリアル番号は ABCD-EFGH です

 C:\sample のディレクトリ

2010/10/22  23:39    <DIR>                       .
2010/10/22  23:39    <DIR>                       ..
2010/10/22  23:23                 0              abcdefg.txt
2010/10/22  23:24                 0              abcdefg1.txt
2010/10/22  23:23                 0 ABCDEF~1.TXT abcdefghij2.txt
2010/10/22  23:23                 0 ABCDEF~2.TXT abcdefghij3.txt
2010/10/22  23:24                 0              xyz.txt
2010/10/22  23:24                 0 XYZ~1.TXT    xyz.txt1
2010/10/22  23:24                 0 テキス~1.TXT テキストファイル1.txt
2010/10/22  23:24                 0 テキス~2.TXT テキストファイル2.txt
2010/10/22  23:24                 0              文章1.txt
2010/10/22  23:24                 0              文章2.txt
              10 個のファイル                   0 バイト
               2 個のディレクトリ              32 バイトの空き領域

ご覧のように、拡張子より前が8文字を超えるか拡張子が3文字を超える場合に短い名前が生成されています。短い名前はかぶらないように勝手に連番が振られたりしてディレクトリ内で一意に付けられます。
ディレクトリ内で一意なので、例えばこのディレクトリ内に"テキス~1.txt"なんてファイルを新規作成した場合は、"テキストファイル1.txt"の短い名前が勝手に変わります。


何でこのエントリを書いてるかと言うと、VB.NETでディレクトリ配下の特定の命名パターンに沿ったファイルを開く必要があった時にはまりました。VB.NETの System.IO.Directory.GetFiles メソッドを使用して特定ディレクトリ内のファイルを検索した場合に、不要なファイルまで引っかかって来たので、同じパターンで dir を打ってみたというのが経緯です。MSDNのにも一応以下のように書かれています。*3

メモ :
このメソッドは、8.3 形式のファイル名と長いファイル名の両方の形式を使用してファイル名をチェックするため、"*1*.txt" に類似した検索パターンを使用すると予期しないファイル名が返されることがあります。たとえば、"*1*.txt" という検索パターンを使用すると "longfilename.txt" が返されます。その理由は、このファイル名は 8.3 形式で "LONGFI~1.TXT" となるためです。

Directory.GetFiles メソッド (String, String) (System.IO)

この辺りのことを知らずにバッチファイル書いたりしてました。思い返すと綱渡りだった訳だ。もしかしたらと言うか、もしかしなくてもバグったスクリプト書いてた自信がある。反省すると同時に、これだからWindowsは嫌いなんだ、とか言ってみる。仕事の大半はWindowsですけど。


おまけ。多重拡張子の場合はこんな感じになっちゃってます。

2010/10/23  00:13                 0 ABCD~1.E     a.b.c.d.e
2010/10/23  00:13                 0 AABB~1.CC    aa.bb.cc

*1:以降は出力量軽減のために /b (ファイル名のみ表示)をつけます。

*2:この「短い名前」と言う呼び名はコマンドプロンプトのヘルプで使用されています。

*3:.NET Framework 2.0 の方には無いけど。