Windows 下的路徑分隔符是 \
而 Linux 和 Mac 下的路徑分隔符是 \
。正常如果你的數據不跨 Windows 和 Linux 平臺流通的話,不怎麼會遇到多種換行符並存的問題的。但如果真發生了流通,那麼如何將它們格式化爲統一的當前平臺認識的分隔符呢?
現有方案
沒有原生方案(.NET)
System.IO.Path
帶了一堆方法用來處理路徑。各大文檔博客和書籍也都推薦大家使用 Path
來處理路徑字符串的拼接、拆分和提取等,這可以很大程度避免不同遭遇不同平臺下路徑分隔字符串不一致導致的各種問題。
不過,本文想告訴大家的是,Path
處理路徑字符串也不是萬能的,這體現在處理跨操作系統的路徑字符串時。
現在,我列舉了 6 個不同的路徑字符串:
var part0 = @"/mnt/d/walterlv/";
var part1 = @"D:\walterlv\";
var part2 = @"Foo/Bar.cs";
var part3 = @"Foo\Bar.cs";
var part4 = @"/mnt/d/walterlv/Foo/Bar.cs";
var part5 = @"D:\walterlv\Foo\Bar.cs";
分成三組。前兩個是路徑的前半部分,中間兩個是路徑的後半部分,最後兩個是完整路徑。每組裏面,前者是 Linux 風格的路徑分隔符,後者是 Windows 風格的路徑分隔符。
現在,我將試圖將以下幾種混合情況下的路徑拼接使用 Path
可能格式化的方法輸出出來:
// 看看 Linux 風格和 Windows 風格直接拼接的換行符使用 Path.Combine 能否格式化成功。
var pathFromCombine0 = Path.Combine(part0, part3);
var pathFromCombine1 = Path.Combine(part1, part2);
Console.WriteLine($"Path.Combine(part0, part3) = {pathFromCombine0}");
Console.WriteLine($"Path.Combine(part1, part2) = {pathFromCombine0}");
// 通過 Path.GetFullPath 轉相對路徑到完整路徑時,看看能否將路徑格式化成當前平臺。
var pathFromFull0 = Path.GetFullPath(part2);
var pathFromFull1 = Path.GetFullPath(part3);
Console.WriteLine($"Path.GetFullPath(part2) = {pathFromFull0}");
Console.WriteLine($"Path.GetFullPath(part3) = {pathFromFull1}");
// 通過 new FileInfo(file).FullName 的一層轉換看看能否將混合路徑格式化成當前平臺。
var pathFromFileInfo0 = new FileInfo(pathFromCombine0).FullName;
var pathFromFileInfo1 = new FileInfo(pathFromCombine1).FullName;
Console.WriteLine($"FileInfo(part0 + part3).FullName = {pathFromFileInfo0}");
Console.WriteLine($"FileInfo(part1 + part2).FullName = {pathFromFileInfo1}");
// 通過 new FileInfo(file).FullName 的一層轉換看看能否將非當前平臺的路徑格式化成當前平臺。
var pathFromFileInfo2 = new FileInfo(part4).FullName;
var pathFromFileInfo3 = new FileInfo(part5).FullName;
Console.WriteLine($"Path.GetFullPath(part4) = {pathFromFileInfo2}");
Console.WriteLine($"Path.GetFullPath(part5) = {pathFromFileInfo3}");
猜猜以上代碼在 Windows 和 Linux 平臺會輸出什麼?
看圖!
圖是拼接的,上面一半是 Windows 平臺下的運行結果,下面一半是 Linux Ubuntu 18.04 發行版的運行結果。運行時是 .NET Core 3.1。
可以發現這些點:
Path.Combine
的路徑拼接僅決定如何合併兩段字符串,不會將已有的路徑格式化成當前平臺的路徑分隔符。Path.GetFullPath
在生成完整路徑的時候,雖然補全的部分是當前平臺的,但已有的部分依然是原本字符串。new FileInfo().FullName
在 Windows 平臺下可以完美將路徑字符串統一成 Windows 平臺的風格;但在 Linux 平臺上不會統一,已有的\
不會變成/
;無論是拼接的字符串,還是原本別的平臺的字符串,都是一樣的結論。
爲什麼 .NET 原生不做統一化?
看前面結論可知,在 Windows 平臺下是可以將 /
和 \
全部格式化成 Windows 平臺的 \
的,但 Linux 下卻不行。
這並不是因爲 .NET 沒去做,而是無法做!
在 Linux 下,\
是合理的文件名!
另外,路徑經常使用在 Shell 中,而在 Shell 中,\
是個轉義字符!
例如,你可以有一個文件,名字是 foo\bar.txt
。
所以,.NET 絕對不能擅自給你將 \
當作路徑分隔符進行格式化!
關於 \
在 Linux Shell 中的轉義,你可以閱讀我的另外兩篇博客瞭解:
自己實現
知道了 Linux 是合理的文件名後,當然不能再指望有某個通用的解決方法了。因爲通用代碼不可能知道在你的上下文下,\
是否是合理的文件名。在信息不足的情況下,前面 .NET 的 new FileInfo().FullName
已經是最好的解決方案了。
所以,如果你明確這些不同種類的路徑字符串的來源你都清楚(沒錯,就是你自己挖出來的坑),拼接出來之後的後果你才能知道是否是符合業務的。這時你才應該決定是否真的要做路徑的格式化。
簡單省事型
var path = path
.Replace('/', Path.DirectorySeparatorChar)
.Replace('\\', Path.DirectorySeparatorChar);
高性能型
自己實現去。
如何避免
從前面的分析可以知道,如果每個框架、庫還有業務開發者都不去作死把平臺特定的路徑傳遞到其他平臺,那麼根本就不會存在不同平臺的路徑會拼接的情況。
另外,開發者也不應該隨便在代碼中寫死 /
或者 \\
作爲路徑的分隔符。
就這樣……
參考資料
我的博客會首發於 https://blog.walterlv.com/,而 CSDN 會從其中精選發佈,但是一旦發佈了就很少更新。
如果在博客看到有任何不懂的內容,歡迎交流。我搭建了 dotnet 職業技術學院 歡迎大家加入。
本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名呂毅(包含鏈接:https://walterlv.blog.csdn.net/),不得用於商業目的,基於本文修改後的作品務必以相同的許可發佈。如有任何疑問,請與我聯繫。