PowerShell:在Start-Job中,$input到底是什么?

5
请考虑以下代码。我只是将一个32位有符号整数数组[Int32[]]通过使用-InputObject参数传递到Start-Job cmdlet中。
$Job = Start-Job -ScriptBlock { $input.GetType().FullName; } -InputObject @(1,2,3);
Wait-Job -Job $Job;
Receive-Job -Keep $Job;

这段代码的结果是:
System.Management.Automation.Runspaces.PipelineReader`1+<GetReadEnumerator>d__0[[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]

查看PipelineReader .NET 类的文档,它有一个 ReadToEnd() 方法。因此,以下代码应该可以工作:

$Job = Start-Job -ScriptBlock { $input.ReadToEnd(); } -InputObject @(1,2,3);
Wait-Job -Job $Job;
Receive-Job -Keep $Job;

但是,我收到了一个错误消息:

方法调用失败,因为[System.Int32]不包含名为“ReadToEnd”的方法。 + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : MethodNotFound + PSComputerName : localhost

于是我想,我将使用PSBase属性来获取“真实”的对象。

$Job = Start-Job -ScriptBlock { $input.psbase.ReadToEnd(); } -InputObject @(1,2,3);
Wait-Job -Job $Job;
Receive-Job -Keep $Job;

但是我收到了类似的错误消息:

方法调用失败,因为[System.Management.Automation.PSInternalMemberSet]不包含名为'ReadToEnd'的方法。 + CategoryInfo : InvalidOperation: (ReadToEnd:String) [], RuntimeException + FullyQualifiedErrorId : MethodNotFound + PSComputerName : localhost

我注意到已有一个Microsoft Connect bug关于这个混乱的问题,但更加让我感到困惑。显然,PipelineReader类有一个令人困惑的命名属性<>4__this,它有一个Read()方法,但你无法通过使用Get-Member看到它。 底线:有没有人知道如何简单地“解开”$input自动变量的内容,当输入通过-InputObject参数提交给Start-Job cmdlet时,以便我可以逐个处理对象?
此脚本应该只返回1,而不是1,2,3
$Job = Start-Job -ScriptBlock { $input[0]; } -InputObject @(1,2,3);
Wait-Job -Job $Job;
Receive-Job -Keep $Job;

3
$Job = Start-Job -ScriptBlock { $($input)[0]; } -InputObject @(1,2,3) 对您有用吗? - jscott
可以进一步解释一下$input是什么吗?这才是我最终想要理解的。 - user189198
2
我认为我有一个更好的例子。about_Automatic_Variables中指出,在枚举器中$input不能像数组一样进行索引。尝试这个:$Job = Start-Job -ScriptBlock { $input.getEnumerator()[0] } -InputObject @(1,2,3) - jscott
Pwsh仍然让人困惑。我不能说我喜欢它的任何东西,但我的解决方法是做类似于Start-Job -Name $name -ScriptBlock { $_argv = $input|%{$_}; $_argv[0], $_argv[1] | Write-Host } -InputObject @('arg1', 'arg2')这样的事情。我遇到了问题,因为我认为-InputObject意味着我可以传递一个Hashtable(我猜你仍然可以,但结果不会让你发疯)。应该称为-InputArray-InputEnumerable或其他一些指示您无法真正传递“对象”的内容。 - Nathan Chappell
3个回答

5

假设$input是一个枚举器,就像标准管道中的一样。 为了处理项目,我们应该使用带有自动变量$_process块,或在end块中将$input传递到另一个管道中(如果未指定,则隐式)。

# process each item separately
$Job = Start-Job -ScriptBlock {process{$_}} -InputObject @(1,2,3);
Wait-Job -Job $Job;
Receive-Job -Keep $Job;

# process the whole $input
$Job = Start-Job -ScriptBlock {$input | %{$_}} -InputObject @(1,2,3);
Wait-Job -Job $Job;
Receive-Job -Keep $Job;

# compare with script blocks in standard pipelines

# each item
@(1,2,3) | . {process{$_}}

# whole input
@(1,2,3) | . {$input | %{$_}}

也许有其他方法可以枚举$input的项目,但据我所知,这些方法在实践中并不常用。

3
$Input
   Contains an enumerator that enumerates all input that is passed to a
   function. The $input variable is available only to functions and script
   blocks (which are unnamed functions).  In the Process block of a
   function, the $input variable enumerates the object that is currently
   in the pipeline. When the Process block  completes, there are no objects
   left in the pipeline, so the $input variable enumerates an empty
   collection. If the function does not have a Process block, then in the
   End block, the $input variable enumerates the collection of all input to
   the function.

来源: about_Automatic_Variables

总之,$input 是一个自动变量,以枚举形式包含整个管道,而不像 $_ 是管道中的“当前对象”。

这里是如何使用它的示例。

function test {
    #$input is an enumerator that you should use $input | foreach-object { } to access the objects.

    #To to get all items you could e.g. convert the enumerator to an array.
    $arr = @($input)

    #If you need to use the $input enumerator for something else, you need to call .Reset() first as the enumerator has reached the end.
    $input.Reset()

    #Print some values from the data
    $arr.count
    $arr[0]
}

PS> "hello", "world" | test


2
hello

更新:这也适用于Start-Job场景,以下示例已进行注释以进行解释。

$Job = Start-Job -ScriptBlock { 
    #Read the complete pipeline to an array
    $data= @($input)
    "`$data is an $($data.GetType().Name) with $($data.count) objects"

    #Unlike in a pipeline, where the `Start-Job` command would be called once per object like `Start-Job ...... -InputObject $_`, 
    #you're inputing a single `object[]` object the the pipeline. So you only have one item in your $input pipeline.

    #Get our inputobject (our array)
    $arr= $data[0]
    "`$arr is an $($arr.GetType().Name)"

    #Use array
    "$($arr[0]) is less than $($arr[1]) which is less than $($arr[2])"
} -InputObject @(1,2,3);

Wait-Job -Job $Job;
Receive-Job -Keep $Job;

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command                  
--     ----            -------------   -----         -----------     --------             -------                  
54     Job54           BackgroundJob   Completed     True            localhost             ...                     
$data is an Object[] with 1 objects
$arr is an ArrayList
1 is less then 2 which is less then 3

为了证明我的有关管道的“理论”。这是经过流水线处理的版本:
$Job = 1, 2, 3 | Start-Job -ScriptBlock { 
    #Read the complete pipeline to an array
    $data= @($input)
    "`$data is an $($data.GetType().Name) with $($data.count) objects"

    #Now the command is run per object, so the $input enumerator contained our 3 seperate Int32 values.

    #Get a single object in the pipeline
    $OneOfTheValues= $data[0]
    "`$OneOfTheValues is an $($OneOfTheValues.GetType().Name)"

    #Use data
    "$($data[0]) is less than $($data[1]) which is less than $($data[2])"
}

Wait-Job -Job $Job;
Receive-Job -Keep $Job;

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command                  
--     ----            -------------   -----         -----------     --------             -------                  
58     Job58           BackgroundJob   Completed     True            localhost             ...                     
$data is an Object[] with 3 objects
$OneOfTheValues is an Int32
1 is less then 2 which is less then 3

所以我坚持我的原始答案。行为是相等的,你只是以不同的方式使用管道/cmdlet。 :)

"Start-Job" 不会以这种方式运作。请参考我的示例。 - user189198

2

这似乎是有效的:

$Job = Start-Job -ScriptBlock { $input.movenext();$input.current[0] } -InputObject @(1,2,3);
Wait-Job -Job $Job;
Receive-Job -Keep $Job;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接