本文用來收集一些使用 Powershell 的時候的一些奇奇怪怪的體驗。html
0x00 :: 生存仍是毀滅?
在操做 集合
(好比 數組
、 鏈表
、 哈希映射表
等)的時候,有一種叫 算子
的概念。
在 Scala 裏它本質上是一些對象的方法,一些語言裏它被叫作「高級函數」。shell
這裏主要須要先知道這倆:編程
map
: 對 集合
裏的每個 元素
,都指定一個計算的邏輯,該 算子
能夠返回一個新的 集合
,裏面每一個 元素
都是上一個集合每一個 元素
通過該邏輯後的值。數組
{2,3,3}
這個集合,通過 map(大家每一個都乘以3)
處理後,就能返回 {6,9,9}
這樣的集合。flatten
: 把一個 集合
裏,如有的 元素
也是 集合
,那麼該 算子
會返回一個打破一層裏面集合的新集合。函數
{{1,2},7,{2,3,3},{2,{6,6,6},12},4}
這個集合,通過 flatten
處理後,返回的集合就是 {1,2,7,2,3,3,2,{6,6,6},12,4}
這樣的。flatmap
: 其實, flatmap(xxx)
就等同於先 map(xxx)
而後 flatten
。測試
好比:
假如,集合乘法效果是這樣:{1,2,3} * 2 => {1,2,3,1,2,3}
,
那麼, {{1,2},7,{{2,3,3}}}
通過 flatmap(每一個元素給我乘2)
後,就等於先被變成 {{1,2,1,2},14,{{2,3,3},{2,3,3}}}
這樣,再變成 {1,2,1,2,14,{2,3,3},{2,3,3}}
這樣。ui
這其實就好像,你有一個小破船,船上運者一些貨物,有的貨是普通貨、有的貨也是小破船,就叫小小破船好了。如今,小破船被魚雷擊中了,這除了致使裏面的貨物一樣被顛簸了一下之外,那些小小破船都被顛簸側漏了。小小破船上的貨物就都漏了出來,從而它們再也不在小小破船上了、而是漏到了小破船上,亂七八糟地、平鋪地,攤在那裏。
foreach
: 在 Scala 裏的話這個其實就是無返回(即返回 Unit
表示返回空)的 map
。通常是要用每一個元素作一樣一件有反作用的事。(好比打印)(這裏只要信息不僅經過返回傳出 or
信息不僅經過參數進入函數就都算有反作用)上面的flatmap
是前二者的組合而foreach
可理解爲特殊的 map
那麼上面的確只是說了倆算子
,沒錯。
而本部分要說的,就是 Powershell 的 foreach
彷佛有着相似於 flatmap
的做用這件事了。.net
就是說,「裏面的東西」,存在仍是毀滅?要不要擊破小小破船、以使其側漏?
先簡單測試,再演示一個稍微複雜的例子進一步驗證。設計
0.0
1,2,(1,2,3) | foreach { $_ * 3 }
應有輸出:code
3 6 1 2 3 1 2 3 1 2 3
能夠看到其中的 (1,2,3)
被重複了三次而沒有數值三倍
0.1
1,2,(1,2,3) | foreach { $_ * 3 } | foreach { $_ * 7 }
應有輸出:
21 42 7 14 21 7 14 21 7 14 21
能夠看到上一步的結果的 集合
裏其實就已是平坦的了,那個 (1,2,3)
在乘以三的時候仍是一個 集合
,但乘以三完了之後也一併被側漏了開來,在乘以七的時候,就是漏出來的每一個元素像前面兩個元素同樣都被做爲外層 集合
的直屬元素來對待了。
1.0
1,2,,(1,2,3) | foreach { $_ * 3 } | foreach { $_ * 7 }
這裏給開頭代碼斷下句:兩個逗號 ,,
並非一個總體,像 1,2
的逗號做用就像 1+2
的 +
同樣是一個運算符,二元的,至關於一個兩參數的函數,只不過,算式 1,2
的返回結果是個元素爲 1
和 2
的數組;而 ,2
其實也能夠類比着 +2
來理解,也是運算符,一元運算符,只不過 ,2
的返回仍是個數組,一個單元素的數組。
因此,不難理解,這裏的 ,(1,2,3)
纔是一個總體,手動明確一下運算符的優先級的話,開頭那塊兒的 1,2,,(1,2,3)
就應該是這樣寫了: 1,2,(,(1,2,3))
。
它應有這樣的輸出:
21 42 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3
能夠看見這回裏面的 (1,2,3)
沒有輕易側漏。但真的是這樣嗎?
1,2,,(1,2,3) | foreach { $_ * 3 } | foreach { $_ * 7 } | foreach { $_ * 2 }
看看輸出:
42 84 2 4 6 2 4 6 2 4 6 2 4 6 2 4 6 2 4 6 2 4 6 2 4 6 2 4 6 2 4 6 2 4 6 2 4 6 2 4 6 2 4 6 2 4 6 2 4 6 2 4 6 2 4 6 2 4 6 2 4 6 2 4 6
1.1
能夠看到,其實通過兩層 flatten
後,裏面的 數字元素們
都已經平鋪到了外面,再到第三層的邏輯爲 讓全部元素乘以二
的轉化操做的結果,就是都被做爲數字來操做了。
就如同是,小破船上的小小破船,在顛簸後終於上完了廁所,而後永遠地離開了這個它們本來所在的小破船。
多嘴一句,其實用 -join
也能夠查看它前面的集合對象的狀況:
(1,2,(4,5)|select) -join ' x ' # ret: 1 x 2 x System.Object[] (1,2,(4,5)|%{$_}) -join ' x ' # ret: 1 x 2 x 4 x 5
這個的返回已是字符串了。不信試試這個:
((1,2,(4,5)|select) -join ' x ').split('S')
應有輸出:
1 x 2 x ystem.Object[]
可見那個 System.Object[]
也只是返回值的內容的一部分而已。
上面的 -join
我理解爲糖:
寫法: $a -join ','
等同於: [system.String]::Join(',', $a)
參考自:
https://qastack.cn/programming/7723584/how-do-i-convert-an-array-object-to-a-string-in-powershell
可見,在 Powershell 裏, foreach
其實和 flatmap
有相似的邏輯,或者說兩者就是同樣的。
其實它確切的名字是 ForEach-Object
,而 foreach
則是別名,並且它還有個更短的別名: %
。
(這部分的知識來源: https://www.jb51.net/article/115518.htm
)
而它爲什麼會成爲 flatmap
,或許就能夠講另外一個故事了吧。我我的的猜想是這是 Powershell 上的一個特性(都這麼久了應該不是BUG了吧),即在被返回時會自動拆一次。你能夠試試 1,2,(1,2,3)
和 1,2,,(1,2,3)
對比一下並看看後者的提示。
之因此同 flatmap
聯繫起來是由於看了這些:
https://stackoverflow.com/questions/10442333/what-is-the-powershell-equivalent-to-linqs-select
https://stackoverflow.com/questions/5241441/powershell-equivalent-of-linq-selectmany-method
https://insbex.jixun.moe/linq-in-pwsh
https://or1ko.hatenablog.com/entry/20100125/1264425759
裏面有很不錯的例子。第四個連接提到了 Map ,並指出它相似於 Linux 上的 Awk 。
不過並沒說起 FlatMap 。不過,若是元素確保不會有 集合
,那麼 %
的表現的確等同於 map
算子了。
而之因此能把 flatmap
和 foreach
聯繫起來,仍是由於前兩個連接裏有的回答舉的例子,特別是第一個連接裏,直接問了這方面的事情。
0x01 :: 是..但也沒有徹底是....
跟別的語言的函數不太同樣, Powershell 的函數彷佛是能夠有多個返回的。而 return
關鍵字只是標誌函數在哪被打斷而已。(這簡直就是妥妥的命令式編程的理念嘛!)
定義這麼個函數:
function aax { 2;3;5;7 }
而後用於管道:
aax | % { $_ * 2 }
你會看到輸出:
4 6 10 14
若是用 -join
的話:
(aax) -join ',,' # ret: 2,,3,,5,,7
或許這就能夠做爲判斷,多個返回會編程一個序列的依據。不過其實也不必定:
aax -join ',,'
它的輸出效果就和沒有 -join ',,'
是徹底同樣的,而我還沒找到辦法肯定這究竟是由於什麼。。。。
這樣定義一個函數:
function aaxx { 2;3;5;7;11,13 }
注意後面兩個數之間是逗號。但其實它會返回啥呢?
aaxx -join ',,'
這樣的輸出和只是執行 aaxx
效果徹底同樣。
(aaxx) -join ',,' # ret: 2,,3,,5,,7,,11,,13
這則說明那個 11,13
在返回的時候被自動拆開了,在這裏的效果變得像 11;13
同樣了。
不過接下來的事或許能說明什麼,也或許不能:
(aaxx) -is [array] # ret: True
而不加括號的話:
aaxx -is [array]
輸出的效果和只是執行 aaxx
依然是徹底同樣的。。。
定義好的函數在 Powershell 裏彷佛有着很是複雜的玩法。
下面用 aaxx
舉例——這仨寫法的輸出效果是同樣的:
aaxx
{aaxx}.Invoke()
{return aaxx}.Invoke()
甚至後面跟上 | %{$_*2}
也徹底同樣。
那麼它們真的同樣嗎?同樣,但也不徹底同樣。
function aaxx { 2;3;5;7;@(11,13) } ########################## (aaxx) -is [array] # ret: True {aaxx}.Invoke() -is [array] # ret: False ({aaxx}.Invoke()) -is [array] # ret: False {aaxx}.Invoke().Count # ret: 6 (aaxx).Count # ret: 6 {aaxx}.Count # ret: 1 {aaxx} # out: aaxx {return aaxx}.Invoke() -join '``' # ret: 2``3``5``7``11``13 {aaxx}.Invoke() -join '``' # ret: 2``3``5``7``11``13 (aaxx) -join '``' # ret: 2``3``5``7``11``13
這麼 TM 多玩法?
先無論了。毀滅吧,趕忙的。
這樣看來,上面說 %
是 flatmap
其實也確實是不嚴謹的:
對於 (1..10),(11..20)
,首先有:
(1..10),(11..20) -join ';::;' # ret: System.Object[];::;System.Object[]
那麼有:
先給出以下定義:
function pxn ($Num) { process { return $_ * $Num } }
則有:
( (1..10),(11..20) | % { $_ * 2 } ) -join ',,' # ret: 1,,2,,3,,4,,5,,6,,7,,8,,9,,10,,1,,2,,3,,4,,5,,6,,7,,8,,9,,10,,11,,12,,13,,14,,15,,16,,17,,18,,19,,20,,11,,12,,13,,14,,15,,16,,17,,18,,19,,20 ( (1..10),(11..20) | pxn(2) ) -join ',,' # ret: 1,,2,,3,,4,,5,,6,,7,,8,,9,,10,,1,,2,,3,,4,,5,,6,,7,,8,,9,,10,,11,,12,,13,,14,,15,,16,,17,,18,,19,,20,,11,,12,,13,,14,,15,,16,,17,,18,,19,,20
這說明, %
被稱爲 Map 其實也是沒錯的。
而 flatten
的效果,則應也的確是來自 Powershell 的返回所帶來的效果了。就像,前面用 aaxx
試探的那樣。
這裏用 -join
只是對顯示格式化。若是對 -join
感到疑惑,你也能夠本身執行如下下面幾個,看看輸出。
(1..10),(11..20) | % { $_ * 2 }
(1..10),(11..20) | pxn(2)
(1..10),(11..20) | % { $_ * 2 } | % { $_ * 3 }
(1..10),(11..20) | pxn(2) | pxn(3)
這幾個應該可以證實,可讀管道的命名函數和 % { ... }
結構的效果是徹底同樣的。
函數怎麼讀管道能夠參考這個連接:
https://www.pstips.net/powershell-func-filters-pipeline.html
—— 並且,你還會發現,這管道函數的設計,其實跟 Awk 很是像的。
🐢🕷🐌