先幫沒踩過的同學補充這顆小地雷 - Windows 檔名不分大小寫阱陷

我們都知道 Windows 的檔案系統不區分大小寫,對 Windows 來說,Logo_TW.png 跟 Logo_tw.png 是同一個檔案。因此,你無法在檔案總管將 Logo_TW.png 更名成 Logo_tw.png、logo_tw.png、LOGO_TW.PNG。(如下圖)

一般來說,檔名沒精準調成指定大小寫組合問題不大,反正字元一致便能找對檔案,大小寫不同無所謂。但如果你將檔名拉到 .NET 進行比對,大小寫不一致就會是個要處理的問題。(另一個類似情境是 SQL WHERE 比對時會忽略字尾空白,將欄位讀到 .NET 再比較,結尾空白會導致在 SQL 相等的資料在 .NET 世界不相等,有處理過 CHAR()/NCHAR() 型別的老鳥應該知道讀完先 Trim() 的 SOP)

今天再遇到類以的檔案同步情境,又踩到檔名只差在大小寫不同無法覆寫一致檔名的問題。這回我想改變戰略 - 既然檔名由系統全權控制,何不一開始就讓檔名完全一致,而不是每次要比對時都忽略大小寫,感覺更合理有效率。

研究了一下,解法出奇簡單,File.WriteAllText()/WriteAllBytes() 遇到只有大小寫不同的同樣檔名會維持舊檔名,但用 FileInfo.Move() 或 File.Move() 即可強制換成指定的大小寫組合,使用以下範例驗證:

Action chkFileName = () => {
    Console.ForegroundColor = ConsoleColor.Cyan;
    Console.WriteLine(Directory.GetFiles(".", "*.txt").First());    
};
Action<string> printTitle = (msg) => {
    Console.ForegroundColor = ConsoleColor.Yellow;
    Console.WriteLine(msg);
};

Directory.SetCurrentDirectory("D:\\");
var fileName = "test.txt";
if (File.Exists(fileName))
{
    File.Delete(fileName);
}

printTitle($"Write {fileName}");
File.WriteAllText(fileName, "Hello World");
chkFileName();

var upCaseFileName = fileName.ToUpper();
printTitle($"Write {upCaseFileName}");
File.WriteAllText(upCaseFileName, "Hello World");
chkFileName();

printTitle($"FileInfo.MoveTo() {upCaseFileName}");
FileInfo fi = new FileInfo(fileName);
fi.MoveTo(upCaseFileName);
chkFileName();

printTitle($"File.Move() {upCaseFileName} to {fileName}");
File.Move(upCaseFileName, fileName);
chkFileName();

Console.ResetColor();

如下圖,File.WriteAll("TEST.TXT", ...) 不會將 "test.txt" 檔名換成 "TEST.TXT",但 FileInfo.MoveTo()、File.Move() 可以:

掌握這點,我們只需在每次寫入檔案後,檢查檔名是否不一致,若不相同就用 FileInfo.MoveTo() 更名,搞定收工。

var upCaseFileName = fileName.ToUpper();
printTitle($"Write {upCaseFileName}");
File.WriteAllText(upCaseFileName, "Hello World");
// 檢查檔案名稱,若大小寫不同,使用 MoveTo 更名
var fi = Directory.GetFiles(
    Path.GetDirectoryName(Path.GetFullPath(upCaseFileName)), 
    Path.GetFileName(upCaseFileName))
    .Select(x => new FileInfo(x)).FirstOrDefault();
if (fi != null && fi.Name != Path.GetFileName(upCaseFileName)) 
{
    fi.MoveTo(upCaseFileName);
}
chkFileName();

【同場加映】利用 \\?\ 前置詞(延伸閱讀:冷知識 - 刪不掉的 Windows 檔案)強制更名大小寫的小技巧:

The file system of Windows always ignore file name letter case sometimes cause issue, this article provide a easy way to fix it.


Comments

# by ChrisTorng

奇怪,我在 Windows 10/11 Beta 檔案總管中變更檔名,都可以換大小寫啊,剛剛再試也是一樣。我從不記得有無法換大小寫的問題!?

# by Jeffrey

to ChrisTorng,試了一下的確如此,檔案總管可以改大小寫。 查了一下,在不同作業系統版本、NTFS 或 FAT32/exFAT、檢視模式的結果不一樣。https://reurl.cc/RzxLG9 (List, Icons, Tiles 時,改成功但要 F5 重新整理才會看到正確結果)

# by felix

"若不相同就用 FileInof.MoveTo() 更名", typo error

# by Jeffrey

to felix, 已更正,謝。

# by felix

To Jeffrey, 你的文章對我寫 C# 程式幫助很大,謝謝分享!

Post a comment