大文字小文字を区別してファイルパスを取得する

C#

(2020/02/05)渡されたパスが存在しないファイルのパスだった場合に、
存在するフォルダまでは大文字小文字を

正確にしたうえで返すように
サンプルコードを改善しました。

Windowsでは、
ファイルシステム上大文字小文字を区別しないという特徴があります。
(実はWindows10から区別するように変更できるのですが…
実際には変更していない人が大多数だと思います)

そのため、File.Existsや、Directory.Existsといったメソッドに、
大文字小文字があべこべな文字列を渡しても、
ファイルが存在する場合はtrueが返ってきます。

static void Main(string[] args)
{
    var filePath = @"D:\Users\threeshark\a.txt";

    File.WriteAllText(filePath, "abcde");

    Console.WriteLine(File.Exists(@"D:\users\Threeshark\A.TxT"));
    // ↑ True
}

しかし、Windowsとしてはそれでよくても、
時に大文字小文字を正確に区別したパスを得たいケースがあります。

  • パスを画面に表示したい場合
    • 大文字小文字が適当なパスを出してしまうと
      知識のないユーザーには無用に混乱を与えてしまう
  • やり取りするサーバーが大文字小文字を区別してしまう場合
    • たとえばPerforceというバージョン管理システムは
      UNIXサーバー上で動作させていると
      大文字小文字を区別するため、
      不整合が起こり厄介な問題に発展する

本記事では、大文字小文字をきちんと区別した、
正確なパスを返す方法について紹介します。

スポンサーリンク

Directory.EnumerateDirectoriesを多段に使う方法

EnumerateDirectories
メソッドを使うと、
指定したフォルダに存在するフォルダ名一覧
を取得できます。

ここで取得できるフォルダ名は、
大文字小文字が正確です。

よって、これを上から多段に呼ぶことで、
正確なファイルパスが取得できます。

以下は、サンプルコードです。

static void Main(string[] args)
{
    var filePath = @"D:\Users\threeshark\a.txt";

    File.WriteAllText(filePath, "abcde");

    var pathResult = CalcCaseSensitivePath(@"D:\users\Threeshark\A.TxT");
    Console.WriteLine(pathResult); // D:\Users\threeshark\a.txt
    var dirResult = CalcCaseSensitivePath(@"D:\uSers\ThReeshark");
    Console.WriteLine(dirResult); // D:\Users\threeshark
    var noExistsPathResult = CalcCaseSensitivePath(@"d:\UseRS\ThREESHARK\hoge\Fuga.txt");
    Console.WriteLine(noExistsPathResult); // D:\Users\threeshark\hoge\Fuga.txt
}

private static string CalcCaseSensitivePath(string path)
{
    // パス区切り文字で分割
    var splitPaths = path.Split(new[] { '\\', '/' });

    var sb = new StringBuilder(260);
    var i = 0;
    for (; i < splitPaths.Length - 1; i++)
    {
        // ドライブレターは確実に大文字
        sb.Append(i == 0 ? splitPaths[i].ToUpper() : splitPaths[i]);
        sb.Append('\\');

        // ディレクトリを探す
        var dir = Directory.EnumerateDirectories(sb.ToString(), splitPaths[i + 1])
            .FirstOrDefault();
        if (!string.IsNullOrWhiteSpace(dir))
        {
            // 見つかったら大文字小文字が正しいので置き換え
            splitPaths[i + 1] = Path.GetFileName(dir);
        }
        else
        {
            // 見つからなかったらそれ以降の文字列はそのまま採用
            i++;
            break;
        }
    }

    sb.Append(string.Join('\\', splitPaths.Skip(i)));

    return sb.ToString();
}

実行結果です。

いい感じですね。

途中でフォルダが見つからなくなった場合は、
見つかったところまでは正確なパスにし、
それ以降の文字列はそのまま返しています。

GetLongPathNameを使う方法はうまくいかない

全て小文字で入力したパスの実際の名前(大文字小文字区別)を取得したい 

というページによるとWin32APIである
GetLongPathNameを使う方法が紹介されています。
試してみましたが、これはうまく動きませんでした

以下は試してみたコードです。

static void Main(string[] args)
{
    var filePath = @"D:\Users\threeshark\a.txt";

    File.WriteAllText(filePath, "abcde");

    var pathResult = GetCaseSensitivePath(@"D:\users\ThrEeshark\A.TxT");
    Console.WriteLine(pathResult);
    var dirResult = GetCaseSensitivePath(@"d:\uSers\ThReeShark");
    Console.WriteLine(dirResult);
    var noExistsPathResult = GetCaseSensitivePath(@"d:\UseRS\ThREESHARK\hoge\Fuga.txt");
    Console.WriteLine(noExistsPathResult);
}

private static string GetCaseSensitivePath(string path)
{
    var sb = new StringBuilder(260);
    GetLongPathNameA(path, sb, sb.Capacity);
    return sb.ToString();
}

[System.Runtime.InteropServices.DllImport("kernel32.dll")]
static extern int GetLongPathNameA(string path, StringBuilder result, int cchBuffer);

実行してみたところ、途中のフォルダだけ不正確、
という何とも微妙な結果になりました。

また、存在しないファイルを指定した場合は取得できていません。

Windowsのバージョンの問題でしょうか…。
とにかく、この方法は2020年2月現在では使えないようです。

まとめ

まとめです。

  • Windowsではファイルパスの大文字小文字を区別しません
  • しかし、パスを画面に表示する場合は
    大文字小文字を正確に提示してあげた方が
    ユーザーフレンドリーです
  • Directory.EnumerateDirectoriesを駆使することで
    大文字小文字が正確なパスを取得することができます

最後までお読みいただきありがとうございました。

以下のページを参考にさせていただきました。感謝

関連記事

関連記事です。

ファイルパスを表示するだけでなく、
「フォルダを開く」機能を付けてあげると、
より気が利いているアプリケーションになります。

explorer.exeに/selectオプション
を付けて呼び出すと、
任意のフォルダを開くことができますが、
ここに1つ罠があります。

explorerの/selectに「/」区切りのパスは無効
にて問題点と取れる対策の解説をしています。

逆に、文字列を大文字小文字の区別なく
比較したいことがあります。

【C#】大文字小文字を区別せずに文字列比較
にて、その具体的方法を紹介しています。

コメント

タイトルとURLをコピーしました