简短问题:任何人都有关于ForEach-Object的-RemainingScripts参数的详细信息?
长问题:
我刚从上周开始学习PowerShell,我将浏览每个Cmdlet以了解更多详细信息。基于公共文档,我们知道ForEach-Object可以有Begin-Process-End块,如下所示:
Get-ChildItem | foreach -Begin { "block1";
$fileCount = $directoryCount = 0} -Process { "block2";
if ($_.PsIsContainer) {$directoryCount++} else {$fileCount++}} -End {
"block3"; "$directoryCount directories and $fileCount files"}
预期结果:“block1”和“block3”为1次,对于传入的每个项目重复“block2”,并且dir计数/文件计数都是正确的。到目前为止一切都很好。
现在,有趣的是,以下命令也起作用并给出完全相同的结果:
Get-ChildItem | foreach { "block1"
$fileCount = $directoryCount = 0}{ "block2";
if ($_.PsIsContainer) {$directoryCount++} else {$fileCount++}}{
"block3"; "$directoryCount directories and $fileCount files"}
只有3个ScriptBlocks传递给foreach。根据手册,第一个进入-Process(位置1)。但剩下的2怎么样?根据手册,没有“位置2”的参数。所以我转向Trace-Command,发现后两个脚本块实际上是RemainingScripts的“IList with 2 elements”。
BIND arg [$fileCount = $directoryCount = 0] to parameter [Process]
BIND arg [System.Management.Automation.ScriptBlock[]] to param [Process] SUCCESSFUL
BIND arg [System.Collections.ArrayList] to parameter [RemainingScripts]
BIND arg [System.Management.Automation.ScriptBlock[]] to param [RemainingScripts] SUCCESSFUL
所以如果我将命令更改为:
# No difference with/without the comma "," between the last 2 blocks
Get-ChildItem | foreach -Process { "block1"
$fileCount = $directoryCount = 0} -RemainingScripts { "block2";
if ($_.PsIsContainer) {$directoryCount++} else {$fileCount++}},{
"block3"; "$directoryCount directories and $fileCount files"}
仍然是完全相同的结果。
正如您所注意到的,所有3个命令都给出了相同的结果。这提出了一个有趣的问题:后两个命令(隐式地)都指定了-Process,但 ForEach-Object令人惊讶地最终使用-Process的参数作为“-Begin”!(脚本块被执行)一旦开始)。
这个实验表明:
尽管如此,以上所有内容都只是我的猜测。我没有找到支持我猜测的文档
所以,最后我们回到我的简短问题:) 任何人都有关于ForEach-Object的-RemainingScripts参数的详细信息?
感谢。
答案 0 :(得分:3)
我做了更多的研究,现在有信心在传递多个ScriptBlocks时回答-RemainingScripts参数的行为。
如果您运行以下命令并仔细检查结果,您将找到该模式。这不是很简单,但仍然不难理解。
1..5 | foreach { "process block" } { "remain block" }
1..5 | foreach { "remain block" } -Process { "process block" }
1..5 | foreach { "remain block" } -End { "end block" } -Process { "process block" } -Begin { "begin block" }
1..5 | foreach { "remain block 1" } -End { "end block" } -Process { "process block" } { "remain block 2" }
1..5 | foreach { "remain block 1" } { "remain block 2" } -Process { "process block" } -Begin { "begin block" }
1..5 | foreach { "remain block 1" } { "remain block 2" } -Process { "process block" } { "remain block 3" }
1..5 | foreach { "process block" } { "remain block 1" } { "remain block 2" } -Begin { "begin block" }
1..5 | foreach { "process block" } { "remain block 1" } { "remain block 2" } { "remain block 3" }
那么这里的模式是什么?
当传入单个ScriptBlock时:简单,它只是转到-Process(最常见的用法)
当传入2个ScriptBlock时,有3种可能的组合
如果我们运行这两个陈述:
1..5 | foreach { "process block" } { "remain block" }
1..5 | foreach { "remain block" } -Process { "process block" }
# Both of them will return:
process block
remain block
remain block
remain block
remain block
remain block
正如您将发现的,这只是以下测试用例的一个特例:
当传入2个以上的ScriptBlock时,请遵循以下工作流程:
排序结果是ScriptBlocks的集合。我们将此集合称为 OrderedScriptBlocks
(内部)根据 OrderedScriptBlocks重新绑定参数
我们来看这个例子
1..5 | foreach { "remain block 1" } { "remain block 2" } -Process { "process block" } { "remain block 3" }
订单结果是:
{ "process block" } # new Begin
{ "remain block 1" } # new Process
{ "remain block 2" } # new Process
{ "remain block 3" } # new End
现在执行结果是完全可预测的:
process block
remain block 1
remain block 2
remain block 1
remain block 2
remain block 1
remain block 2
remain block 1
remain block 2
remain block 1
remain block 2
remain block 3
这是-RemainingScripts背后的秘密,现在我们了解ForEach-Object的更多内部行为!
我仍然不得不承认没有文件支持我的猜测(不是我的错!),但这些测试用例应足以解释我所描述的行为。
答案 1 :(得分:1)
这是它的细节。 ValueFromRemainingArguments
设置为true,因此您的猜测是正确的。
help ForEach-Object
-RemainingScripts <ScriptBlock[]>
Takes all script blocks that are not taken by the Process parameter.
This parameter is introduced in Windows PowerShell 3.0.
gcm ForEach-Object | select -exp parametersets
Parameter Name: RemainingScripts
ParameterType = System.Management.Automation.ScriptBlock[]
Position = -2147483648
IsMandatory = False
IsDynamic = False
HelpMessage =
ValueFromPipeline = False
ValueFromPipelineByPropertyName = False
ValueFromRemainingArguments = True
Aliases = {}
Attributes =
System.Management.Automation.ParameterAttribute
System.Management.Automation.AllowEmptyCollectionAttribute
System.Management.Automation.AllowNullAttribute
答案 2 :(得分:1)
还有更多支持@FangZhou排序假设的示例。
如果指定其他块,那么它的工作方式似乎很有意义:
PS C:\> 1..5 | ForEach-Object -Begin { "Begin: $_" } -End { "End: $_" } -Process { "Process: $_" } -RemainingScripts { "R1: $_" },{ "R2: $_" },{ "R3: $_" }
Begin:
Process: 1
R1: 1
R2: 1
R3: 1
Process: 2
R1: 2
R2: 2
R3: 2
Process: 3
R1: 3
R2: 3
R3: 3
Process: 4
R1: 4
R2: 4
R3: 4
Process: 5
R1: 5
R2: 5
R3: 5
End:
即使您传递了空块:
PS C:\> 1..5 | ForEach-Object -Begin {} -End {} -Process { "Process: $_" } -RemainingScripts { "R1: $_" },{ "R2: $_" },{ "R3: $_" }
Process: 1
R1: 1
R2: 1
R3: 1
Process: 2
R1: 2
R2: 2
R3: 2
Process: 3
R1: 3
R2: 3
R3: 3
Process: 4
R1: 4
R2: 4
R3: 4
Process: 5
R1: 5
R2: 5
R3: 5
但是,如果您未指定-End
,它将执行完全不同的操作。传递给命令的最后一个脚本块用于-End
。
PS C:\> 1..5 | ForEach-Object -Begin { "Begin: $_" } -Process { "Process: $_" } -RemainingScripts { "R1: $_" },{ "R2: $_" },{ "R3: $_" }
Begin:
Process: 1
R1: 1
R2: 1
Process: 2
R1: 2
R2: 2
Process: 3
R1: 3
R2: 3
Process: 4
R1: 4
R2: 4
Process: 5
R1: 5
R2: 5
R3:
您可以通过更改属性的顺序来更改将发生的事情:
PS C:\> 1..5 | ForEach-Object -RemainingScripts { "R1: $_" },{ "R2: $_" },{ "R3: $_" } -Begin { "Begin: $_" } -Process { "Process: $_" }
Begin:
R1: 1
R2: 1
R3: 1
R1: 2
R2: 2
R3: 2
R1: 3
R2: 3
R3: 3
R1: 4
R2: 4
R3: 4
R1: 5
R2: 5
R3: 5
Process:
如果您未指定-Begin
,则再次有所不同。现在,第一个传递的脚本块用于-Begin
:
PS C:\> 1..5 | ForEach-Object -End { "End: $_" } -Process { "Process: $_" } -RemainingScripts { "R1: $_" },{ "R2: $_" },{ "R3: $_" }
Process:
R1: 1
R2: 1
R3: 1
R1: 2
R2: 2
R3: 2
R1: 3
R2: 3
R3: 3
R1: 4
R2: 4
R3: 4
R1: 5
R2: 5
R3: 5
End:
如果您既未指定-Begin
也未指定-End
,它将两者结合在一起。现在,第一个脚本块替换-Begin
,最后一个脚本块替换-End
:
PS C:\> 1..5 | ForEach-Object -Process { "Process: $_" } -RemainingScripts { "R1: $_" },{ "R2: $_" },{ "R3: $_" }
Process:
R1: 1
R2: 1
R1: 2
R2: 2
R1: 3
R2: 3
R1: 4
R2: 4
R1: 5
R2: 5
R3:
据我所知,它旨在支持您要在其中编写的位置脚本块:
1..5 | ForEach-Object { "Begin: $_" } { "Process1: $_" } { "Process2: $_" } { "Process3: $_" } { "End: $_" }
或者像这样:
1..5 | ForEach-Object { "Begin: $_" },{ "Process1: $_" },{ "Process2: $_" },{ "Process3: $_" },{ "End: $_" }
两者的输出:
Begin:
Process1: 1
Process2: 1
Process3: 1
Process1: 2
Process2: 2
Process3: 2
Process1: 3
Process2: 3
Process3: 3
Process1: 4
Process2: 4
Process3: 4
Process1: 5
Process2: 5
Process3: 5
End:
答案 3 :(得分:1)
我相信-remainingscripts(具有属性'ValueFromRemainingArguments')是要从 Windows Powershell in Action 中启用这样的成语,这种成语几乎没有人知道(20%的Powershell仅记录在其中)本书):
Get-ChildItem | ForEach {$sum=0} {$sum++} {$sum}
这些块最终就像开始进程结束一样起作用。实际使用的参数是-process和-remainingscripts。
trace-command -name parameterbinding { Get-ChildItem | ForEach-Object {$sum=0} {$sum++} {$sum} } -PSHost
该跟踪命令似乎证实了这一点。
这是带有脚本块的ValueFromRemainingArguments的简单演示。
function remaindemo {
param ($arg1, [Parameter(ValueFromRemainingArguments)]$remain)
& $arg1
foreach ($i in $remain) {
& $i
}
}
remaindemo { 'hi' } { 'how are you' } { 'I am fine' }
具有ValueFromRemainingArguments参数的其他命令:
gcm -pv cmd | select -exp parametersets | select -exp parameters |
where ValueFromRemainingArguments |
select @{n='Cmdname';e={$cmd.name}},name
Cmdname Name
------- ----
ForEach-Object RemainingScripts
ForEach-Object ArgumentList
Get-Command ArgumentList
Get-Command ArgumentList
Join-Path AdditionalChildPath
New-Module ArgumentList
New-Module ArgumentList
Read-Host Prompt
Trace-Command ArgumentList
Write-Host Object
Write-Output InputObject
答案 4 :(得分:0)
源代码似乎同意。从powershell on github中,我可以找到ForEach-Object
cmdlet的以下相关资源。
/* ... */
注释是我的,否则我对代码进行了一些更改,因此请参考实际来源以获取适当的参考。
第一部分是最初读取参数的方式。要注意的重要一点是,所有<ScripBlock>
都放入同一个数组中,除了-End
。 -End
参数是经过特殊处理的,可能是因为这样做更容易(例如,很容易确定列表中的第一项只是添加到列表的第一项,但是如果您这样做,则很容易)。确保某项是列表中的最后一项要求您知道自己不会在其后追加任何其他内容。
private List<ScriptBlock> _scripts = new List<ScriptBlock>();
public ScriptBlock Begin {
/* insert -Begin arg to beginning of _scripts */
set { _scripts.Insert(0, value); }
}
ScriptBlock[] Process {
/* append -Process args to _scripts */ }
set {
if (value == null) { _scripts.Add(null); }
else { _scripts.AddRange(value); }
}
}
private ScriptBlock _endScript;
private bool _setEndScript;
ScriptBlock End {
set {
_endScript = value;
_setEndScript = true;
}
}
public ScriptBlock[] RemainingScripts {
set {
if (value == null) { _scripts.Add(null); }
else { _scripts.AddRange(value); }
}
}
以下是调用<ScriptBlock>
数据成员中的_script
的函数。基本上,如果<ScripBlock>
中有多个_scripts
,则_start
设置为1。如果至少有三个,并且-End
未被明确设置,{ {1}}设置为_end
。然后调用_scripts[_scripts.Count - 1]
,为_script[0]
打开流,然后调用_script[1.._end-1]
(_endScript
或_script[_end]
)。
-End
这不是完全令人满意的,因为如果文档中未明确说明,则可以将其视为实现细节,但是,它给了我足够的信心来假设这是意图。 Powershell in Action 中也讨论了这种模式,该模式似乎得到了Powershell团队的某种祝福(至少是Snover)。
我想其中的一部分将“开源”,而将更多的“ * nix-y”定义为文档的一部分:)