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

看板Windows作者 (falken)時間2月前 (2024/09/28 07:47), 2月前編輯推噓0(0042)
留言42則, 3人參與, 2月前最新討論串2/3 (看更多)
: 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://www.ptt.cc/bbs/Windows/M.1727480822.A.7BF.html ※ 編輯: falcon (110.28.1.89 臺灣), 09/28/2024 09:15:36

09/28 23:00, 2月前 , 1F
圖1並不是因為解讀不同導致的,而是路徑相關的cmdlet
09/28 23:00, 1F

09/28 23:01, 2月前 , 2F
會多做一次雙引號的解釋,這個估計是三張圖的bug根源
09/28 23:01, 2F

09/28 23:03, 2月前 , 3F
多做一次雙引號用說的可能不好解釋,放張圖給你看
09/28 23:03, 3F

09/28 23:05, 2月前 , 4F

09/28 23:07, 2月前 , 5F
這個邏輯雖然無誤,但是就是個反人類設計
09/28 23:07, 5F

09/28 23:08, 2月前 , 6F
圖3估計是這個設計實際引發的bug...
09/28 23:08, 6F

09/28 23:10, 2月前 , 7F
到圖2圖3這一步,明顯是這個設計導致的bug了
09/28 23:10, 7F

09/28 23:11, 2月前 , 8F
摁...我認為是bug了,太腦殘了這
09/28 23:11, 8F

09/28 23:13, 2月前 , 9F
圖中的檔案真實路徑是 "D:\test\Test`[1].txt"
09/28 23:13, 9F
如果邏輯一致就能說是語言特性 要 user 吞下去就算了 但 PowerShell 自己內部處理都搞不定實在太蠢了 下圖是我自己實作以比對名稱的方式遞迴搜尋路徑 https://i.imgur.com/ZK32zgK.png
這樣的結果才是我想要的 可以直接用管道餵路徑其他 cmdlet 如 get-item ※ 編輯: falcon (110.28.1.89 臺灣), 09/29/2024 00:05:02

09/29 00:09, 2月前 , 10F
網路上都找好久,都沒有比較全面的方案,也只能自己動手了
09/29 00:09, 10F

09/29 00:34, 2月前 , 11F
對了,如果是 te`st`[0].txt 呢?數字部分一樣用萬用字元
09/29 00:34, 11F

09/29 00:40, 2月前 , 12F
我試過兩倍量 ` 不管用…
09/29 00:40, 12F

09/29 00:42, 2月前 , 13F
感覺要改成正規表示法才可靠了
09/29 00:42, 13F

09/29 12:02, 2月前 , 14F
09/29 12:02, 14F

09/29 12:03, 2月前 , 15F
我猜你應該快摸到他的邏輯了,就是解2次雙引號
09/29 12:03, 15F

09/29 12:04, 2月前 , 16F
八成是為了區別那括號到底是字串還是萬用字元
09/29 12:04, 16F

09/29 12:06, 2月前 , 17F
寫一個依照萬用字元表添加反引號的函式或許能解
09/29 12:06, 17F

09/29 12:08, 2月前 , 18F
圖2應該是吃了這個虧導致的,官方cmdlet自己出bug
09/29 12:08, 18F

09/29 13:36, 2月前 , 19F
高手過招(眼花撩亂ing
09/29 13:36, 19F
我覺得就只是最初開發者的低級失誤 可能 user 用多了,改不了而已

09/29 14:07, 2月前 , 20F
試了一下 Resolve-Path 應該無解。改用GetFullPath吧
09/29 14:07, 20F

09/29 14:11, 2月前 , 21F
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

09/29 15:06, 2月前 , 22F
這樣看起來,如果以cmdlet的處理方式為準的話
09/29 15:06, 22F

09/29 15:06, 2月前 , 23F
-like 運算子反而才是壞的,他的行為更不規律
09/29 15:06, 23F

09/29 15:07, 2月前 , 24F
-like能夠同時符合一般的跳脫規則,與cmdlet獨特的規則
09/29 15:07, 24F

09/29 15:16, 2月前 , 25F
麻煩了,不知道要按一般邏輯處理跳脫,還是照這反智規則
09/29 15:16, 25F
※ 編輯: falcon (110.28.1.89 臺灣), 09/29/2024 15:21:06

09/29 17:21, 2月前 , 26F
看來只能按照一般的規則,將萬用字元模式轉成正規表示法
09/29 17:21, 26F

09/29 17:21, 2月前 , 27F
還需要一個參數來決定要不要先把輸入的萬元字元中的``取代
09/29 17:21, 27F

09/29 17:21, 2月前 , 28F
為單個`,這樣就能在兩種規則中切換
09/29 17:21, 28F
※ 編輯: falcon (110.28.1.89 臺灣), 09/30/2024 15:27:01

10/09 14:23, 2月前 , 29F
今天閒著把github上的討論串都看了 2018就有了...
10/09 14:23, 29F

10/09 14:24, 2月前 , 30F
一個可以不用自己處理邏輯的解法是這樣的
10/09 14:24, 30F

10/09 14:27, 2月前 , 31F

10/09 15:07, 2月前 , 32F
不過這有效範圍只有到父資料夾名稱
10/09 15:07, 32F

10/09 18:04, 2月前 , 33F
我目前是將萬用字元路徑轉成正規表示法,遞迴方法一層用
10/09 18:04, 33F

10/09 18:04, 2月前 , 34F
-match運算子比對名稱。結果100%可靠。但效率就不高了,遞
10/09 18:04, 34F

10/09 18:04, 2月前 , 35F
迴方法只能停止從不正確的子目錄繼續往下尋找路徑,但也要
10/09 18:04, 35F

10/09 18:04, 2月前 , 36F
先比對過所有子目錄名稱,才會知道要從哪個目錄往下走
10/09 18:04, 36F

10/09 18:08, 2月前 , 37F
這能處理任何一層目錄名稱中的*或?的多重符合
10/09 18:08, 37F

10/09 18:32, 2月前 , 38F
我有一個想法是用同名的 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

10/09 23:04, 2月前 , 39F
我一開始有想過轉發,只是也不知道如何實現
10/09 23:04, 39F

10/09 23:04, 2月前 , 40F
就算參數能實現還有個大魔王管道
10/09 23:04, 40F

10/09 23:05, 2月前 , 41F
沒找到可靠的方法估計也只能取捨了
10/09 23:05, 41F

10/09 23:11, 2月前 , 42F
然後就是這應該不是完全解,不然這事不會拖6年還沒解
10/09 23:11, 42F
文章代碼(AID): #1czqFsU_ (Windows)
文章代碼(AID): #1czqFsU_ (Windows)