奇怪的 Powershell

本文用來收集一些使用 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 的返回結果是個元素爲 12 的數組;而 ,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 算子了。

而之因此能把 flatmapforeach 聯繫起來,仍是由於前兩個連接裏有的回答舉的例子,特別是第一個連接裏,直接問了這方面的事情。

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 很是像的。

🐢🕷🐌

相關文章
相關標籤/搜索