本文用来收集一些使用 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 很是像的。
🐢🕷🐌