[心得] PowerShell 那些惱人的路徑 BUG

作者: falcon (falken)   2024-09-18 23:54:43
首先,目錄結構如下
- D:\test`[0-2]\test`[0].txt
- D:\test`[0-2]\test`[1].txt
- D:\test`[0-2]\test`[2].txt
PowerShell 的 Cmdlet 預設使用 -Path 接受萬用字元模式路徑,
但 Cmdlet 無法完全正確解讀萬用字元模式路徑中的跳脫處理。
例如,一旦利用跳脫處理來符合目錄或檔案名稱中的特殊字元,
若也使用 *, ?, [ ] 要符合多種組合,
即使字串模式無誤,也找不到任何項目,如下方範例所示。
Get-Item 'D:\test```[0-2`]\test```[[0-2]`].txt'
Get-Item 'D:\test```[0-2`]\test```[*`].txt'
Get-Item 'D:\test```[0-2`]\test```[?`].txt'
以上三個 Get-Item 命令回傳都是 null
若要避免此問題,由於路徑中其他位置的名稱不受此問題影響,
可以先用 Drive:\path\to\dir\* 符合所有子項目的路徑,
再用沒這問題的 -like 運算子篩選子項目名稱。
$items = Get-Item -Path 'D:\test```[0-2`]\*'
$items | Where-Object {$_.Name -like 'test```[[0-2]`].txt'}
$items | Where-Object {$_.Name -like 'test```[*`].txt'}
$items | Where-Object {$_.Name -like 'test```[?`].txt'}
工作目錄路徑本身帶有反引號字元與其他特殊字元時,
PowerShell 無法正確解讀相對路徑。無論是使用 -LiteralPath 指定路徑;
或是使用 -Path 指定萬用模式路徑,即使做了正確的跳脫處理。
Set-Location -LiteralPath 'D:\test`[0-2]'
Get-Item -Path '*'
0 .. 2 | ForEach-Object {
'test`[' + $_ + '].txt'} | Where-Object {
Test-Path -LiteralPath $_} |
Get-Item -LiteralPath {$_}
以上兩個 Get-Item 命令回傳都是 null
若要繞過此問題,可以使用完整路徑,如下方範例所示。
即根目錄開頭 Drive:\ 的路徑,
例如 C:\Users\UserName\Desktop\MyFile
這樣不行 C:Desktop\MyFile
(新版的 PowerShell 已經修復了 -LiteralPath 使用相對路徑時問題)
Get-Item -Path 'D:\test```[0-2`]\*'
0 .. 2 | ForEach-Object {
'D:\test`[0-2]\test`[' + $_ + '].txt'} |
Where-Object {Test-Path -LiteralPath $_} |
Get-Item -LiteralPath {$_}
除了相對路徑,磁碟機代號也受此問題影響。
(新版的 PowerShell 已經修復了,使用磁碟機代號時,不會遇到此問題)
Set-Location -LiteralPath 'D:\test`[0-2]'
Test-Path -LiteralPath 'D:'
以上 Test-Path 命令回傳 False,明顯是錯誤
另外,若在此目錄執行外部程式,例如 cmd /c "echo %CD% & pause",
則將意外在新視窗執行,並將 D:\ 視為工作目錄。
若要避免此問題,其中一個解決分法如下,改用 Start-Process 執行程式,
並使用 -WorkingDirectory 設定工作目錄。
Set-Location -LiteralPath 'D:\test`[0-2]'
Start-Process `
-FilePath 'cmd' `
-ArgumentList '/c "echo %CD% & pause"' `
-WorkingDirectory ($PWD.Path -replace '[`\[\]]', '`$0') `
-Wait `
-NoNewWindow
執行結果如下,正常了
D:\test`[0-2]
Press any key to continue . . .
總結
對於 PowerShell 的 Cmdlet 若要對應任何萬元字元模式路徑,
也只能遞迴方法一層一層使用 -like 去比對所有子項目的名稱
對於外部程式,使用 Start-Process 就代表不能用 | 符號來通過管線傳遞資料
不過 Windwos PowerShell 5.1 的管線也暗藏陷阱
PowerShell 把所有經由管線傳遞的資料都當作字串解碼
若要使用影音程式,還是需要 Start-Process 來呼叫 CMD
使用 CMD 的管線功能來避開此問題
PowerShell 雖然功能強大,但一堆反人類設計與 BUG
Windows 的命令殼層也就 CMD 與 PowerSell
用 PowerShell 還是比較容易實現較複雜的操作
雖然反直覺的地方可能改不了
但還是希望微軟好好加把勁修 BUG
作者: labbat (labbat)   2024-09-19 02:46:00
直接用c語言寫,或者改用python script腳本
作者: smallreader (小讀者)   2024-09-19 03:32:00
`````````這麼多顆到底要怎麼看 哦對吼\被拿去當路徑分隔符了 歷史共業XD
作者: microloft (微閣)   2024-09-19 13:21:00
用 PS 還得繞過這些 bug 真的是活受罪,我最後都乾脆改用 WSL 或 MSYS2 了
作者: jyhfang   2024-09-20 04:04:00
高手 踩地雷大全
作者: hunandy14 (Charlott.HonG)   2024-09-27 12:11:00
其實真實的情況是 pwsh 社群決議改掉預設萬用了他不是bug就是當初設計 不符合直覺所以應該不會修了,那是式樣不是bug測試結果確實沒有bug存在,只是惱人的設計細節可以參考這篇 https://bit.ly/3Y2Tn4s第二個問題管道傳送是字符而不是二進制這個就也是為了方便性犧牲掉的設計這邊推另一個 C# 的解 https://bit.ly/4dmIXAQ趁早換了pwsh至少有社群幫你扛著改掉這些白癡設計

Links booklink

Contact Us: admin [ a t ] ucptt.com