作者falcon (falken)
看板Windows
標題Re: [心得] PowerShell 那些惱人的路徑 BUG
時間Sat Sep 28 07:47:00 2024
: hunandy14: 其實真實的情況是 pwsh 社群決議改掉預設萬用了
: hunandy14: 他不是bug就是當初設計 不符合直覺
: hunandy14: 所以應該不會修了,那是式樣不是bug
: hunandy14: 測試結果確實沒有bug存在,只是惱人的設計
bug 是指對於萬元字元路徑的跳脫處理有誤
預設萬元字元我只當它是反人類設計而已
https://github.com/PowerShell/PowerShell/issues/7999
主要是講這 bug 的影響
順便提了兩句微軟的雞婆的
設計導致多餘的困擾,不是本篇的重點
由於 -like 運算子跟 cmdlet 的路徑參數
對於萬用字元的解讀不同
這兩者都是 PowerShell 原生的功能
對於同樣的東西應該有相同行為
所以說,在這問題上最多只能有一方是正確的
https://i.imgur.com/YiXffzL.png
如過這是特性不是 bug 的話
那工作目錄中的特殊字元應該要做獨特的跳脫處理
畢竟這是在 cmdlet 內部處理的
不應該發生錯誤
Set-Location -LiteralPath 'D:\test`[0-2]'
Resolve-Path -Path .
Resolve-Path -LiteralPath .
使用 -Path . 與使用 -LiteralPath .
前者在任何版本都會發生錯誤
則只有在新的跨平台版 PS 才能得到正確路徑
https://i.imgur.com/m5uoXdF.png
另外,我不是說管線那設計是 bug
bug 是指在這個工作目錄把程式作為命令執行
會因為因為工作目錄路徑導致異常行為
https://i.imgur.com/mbG9jTo.png
Start-Process 有 -WorkingDirectory 可以用
它會直接拿你跳脫處理過的路徑當 base 去組出完整路徑
就能避開對於工作目錄路徑本身包含 ` 時的問題
而 System.Diagnostics.Process 不是 PS 的 cmdlet 所以沒此問題
拿 Start-Process 舉例,只因為它是 PowerShell 的 Cmdlet
--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 110.28.1.89 (臺灣)
※ 文章網址: https://webptt.com/m.aspx?n=bbs/Windows/M.1727480822.A.7BF.html
※ 編輯: falcon (110.28.1.89 臺灣), 09/28/2024 09:15:36
1F:→ hunandy14: 圖1並不是因為解讀不同導致的,而是路徑相關的cmdlet09/28 23:00
2F:→ hunandy14: 會多做一次雙引號的解釋,這個估計是三張圖的bug根源09/28 23:01
3F:→ hunandy14: 多做一次雙引號用說的可能不好解釋,放張圖給你看09/28 23:03
5F:→ hunandy14: 這個邏輯雖然無誤,但是就是個反人類設計09/28 23:07
6F:→ hunandy14: 圖3估計是這個設計實際引發的bug...09/28 23:08
7F:→ hunandy14: 到圖2圖3這一步,明顯是這個設計導致的bug了09/28 23:10
8F:→ hunandy14: 摁...我認為是bug了,太腦殘了這09/28 23:11
9F:→ hunandy14: 圖中的檔案真實路徑是 "D:\test\Test`[1].txt"09/28 23:13
如果邏輯一致就能說是語言特性
要 user 吞下去就算了
但 PowerShell 自己內部處理都搞不定實在太蠢了
下圖是我自己實作以比對名稱的方式遞迴搜尋路徑
https://i.imgur.com/ZK32zgK.png
這樣的結果才是我想要的
可以直接用管道餵路徑其他 cmdlet 如 get-item
※ 編輯: falcon (110.28.1.89 臺灣), 09/29/2024 00:05:02
10F:→ falcon: 網路上都找好久,都沒有比較全面的方案,也只能自己動手了09/29 00:09
11F:→ falcon: 對了,如果是 te`st`[0].txt 呢?數字部分一樣用萬用字元 09/29 00:34
12F:→ falcon: 我試過兩倍量 ` 不管用… 09/29 00:40
13F:→ falcon: 感覺要改成正規表示法才可靠了 09/29 00:42
15F:→ hunandy14: 我猜你應該快摸到他的邏輯了,就是解2次雙引號 09/29 12:03
16F:→ hunandy14: 八成是為了區別那括號到底是字串還是萬用字元 09/29 12:04
17F:→ hunandy14: 寫一個依照萬用字元表添加反引號的函式或許能解 09/29 12:06
18F:→ hunandy14: 圖2應該是吃了這個虧導致的,官方cmdlet自己出bug 09/29 12:08
19F:→ smallreader: 高手過招(眼花撩亂ing 09/29 13:36
我覺得就只是最初開發者的低級失誤
可能 user 用多了,改不了而已
20F:→ hunandy14: 試了一下 Resolve-Path 應該無解。改用GetFullPath吧09/29 14:07
GetFullPath() 不適用於所有 PSDrive 的路徑
例如 HKLM:\
所以我才需要自己寫模組
用於所有 Get-ltem 的使用場景
話說回來 萬用字元
原來就只是要重複做跳脫而已
$literal = 'te`st`[1].txt'
$pattern = $literal -replace '[`\*\?\[\]]', '`$0'
if ($literal -match '[`\*\?\[\]]') {
$pattern = $pattern -replace '[`\*\?\[\]]', '`$0'
}
PowerShell 的萬用字元底層應該是正規表示法
這是轉換失誤的BUG吧,用久了就變語言特性了
就我的理解應該要像下面這麼做
$wildcardPattern | Select-String -Pattern '`?.' -AllMatches |
ForEach-Object { $_.Matches } | ForEach-Object { $_.Value } |
ForEach-Object {
if ($_ -match '`([\[\]])') { '\' + $Matches[1] }
elseif ($_ -match '`(.)') { [regex]::escape($Matches[1]) }
elseif ($_ -match '\*') { '.*' }
elseif ($_ -match '\?') { '.' }
elseif ($_ -match '[\[\]]') { $Matches[0] }
else { [regex]::escape($_) }
} | Set-Variable regexPatternParts
$regexPattern = $regexPatternParts -join ''
而跳脫處理只需要下面這樣
$wildcardPattern = $literal -replace '[`\*\?\[\]]', '`$0'
※ 編輯: falcon (110.28.1.89 臺灣), 09/29/2024 15:02:46
22F:→ falcon: 這樣看起來,如果以cmdlet的處理方式為準的話09/29 15:06
23F:→ falcon: -like 運算子反而才是壞的,他的行為更不規律09/29 15:06
24F:→ falcon: -like能夠同時符合一般的跳脫規則,與cmdlet獨特的規則09/29 15:07
25F:→ falcon: 麻煩了,不知道要按一般邏輯處理跳脫,還是照這反智規則09/29 15:16
※ 編輯: falcon (110.28.1.89 臺灣), 09/29/2024 15:21:06
26F:→ falcon: 看來只能按照一般的規則,將萬用字元模式轉成正規表示法09/29 17:21
27F:→ falcon: 還需要一個參數來決定要不要先把輸入的萬元字元中的``取代09/29 17:21
28F:→ falcon: 為單個`,這樣就能在兩種規則中切換09/29 17:21
※ 編輯: falcon (110.28.1.89 臺灣), 09/30/2024 15:27:01
29F:→ hunandy14: 今天閒著把github上的討論串都看了 2018就有了...10/09 14:23
30F:→ hunandy14: 一個可以不用自己處理邏輯的解法是這樣的10/09 14:24
32F:→ hunandy14: 不過這有效範圍只有到父資料夾名稱10/09 15:07
33F:→ falcon: 我目前是將萬用字元路徑轉成正規表示法,遞迴方法一層用10/09 18:04
34F:→ falcon: -match運算子比對名稱。結果100%可靠。但效率就不高了,遞10/09 18:04
35F:→ falcon: 迴方法只能停止從不正確的子目錄繼續往下尋找路徑,但也要10/09 18:04
36F:→ falcon: 先比對過所有子目錄名稱,才會知道要從哪個目錄往下走10/09 18:04
37F:→ falcon: 這能處理任何一層目錄名稱中的*或?的多重符合10/09 18:08
我有一個想法是用同名的 function 去代替 cmdlet
例如下面這樣
function Get-Item {
$newArgs = $args
# 在此修改 $newargs
# 找出單獨的字串或 -Path, -LiteralPath 參數的引數
# 將其作為路徑擴展為根目錄 ("PSDrive:\") 開頭的完整的路徑
Microsoft.PowerShell.Management\Get-Item @newArgs
}
但不知道 cmdlet 是如何從 $args 中區分字串與參數名稱
由於引號被去掉了,取值也一樣
用 .GetType() 列出類型也都一樣
看起來也不是依照先後順序與開頭字元為準
https://i.imgur.com/uFynvgK.png
※ 編輯: falcon (39.9.131.112 臺灣), 10/09/2024 22:09:05
※ 編輯: falcon (39.9.131.112 臺灣), 10/09/2024 22:10:30
39F:→ hunandy14: 我一開始有想過轉發,只是也不知道如何實現 10/09 23:04
40F:→ hunandy14: 就算參數能實現還有個大魔王管道 10/09 23:04
41F:→ hunandy14: 沒找到可靠的方法估計也只能取捨了 10/09 23:05
42F:→ hunandy14: 然後就是這應該不是完全解,不然這事不會拖6年還沒解 10/09 23:11