如何在 Powershell 管道中引用前一个 "pipe" 的输出?

How do I reference the output of the previous "pipe" in a Powershell pipeline?

我写这段代码来获取一些(相对)文件路径:

function Get-ExecutingScriptDirectory() {
    return Split-Path $script:MyInvocation.MyCommand.Path  # returns this script's directory
}

$some_file_path = Get-ExecutingScriptDirectory | Join-Path -Path $_ -ChildPath "foo.json"

这引发了错误:

Join-Path : Cannot bind argument to parameter 'Path' because it is null.
+ $some_file_path  = Get-ExecutingScriptDirectory | Join-Path -Path $_ -ChildPath "fo ...
+                                                     ~~
    + CategoryInfo          : InvalidData: (:) [Join-Path], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.JoinPathCommand

这向我表明 Get-ExecutingScriptDirectory 的输出为空 - 但它不是 - 当我这样写脚本时,没问题:

$this_directory = Get-ExecutingScriptDirectory
$some_file_path = Join-Path -Path $this_directory -ChildPath "foo.json"

所以问题是 $_ 为空。我希望 $_ 参考前一个管道的标准输出。 MSDN documentation 也暗示了这一点,但它似乎立即自相矛盾:

$_ contains the current object in the pipeline object. You can use this variable in commands that perform an action on every object or on selected objects in a pipeline.

在我的代码上下文中,$_ 似乎符合 "current object in the pipeline object" - 但我没有将它与对每个对象或选定对象执行操作的命令一起使用管道。

$$$^ 看起来很有希望,但 MSDN 文档只是在这里对词法标记说了一些模糊的事情。 $PSItem 上的文档同样简洁。

我实际上喜欢做的是创建一个大管道:

$some_file_path = Get-ExecutingScriptDirectory | Join-Path -Path {{PREVIOUS STDOUT}} -ChildPath "foo.json" | Get-Content {{PREVIOUS STDOUT}} | Convert-FromJson {{PREVIOUS STDOUT}} | {{PREVIOUS STDOUT}}.data

我想知道我在概念和技术层面上哪里出了问题。

只需使用一个代码块。

function Get-ExecutingScriptDirectory() {
    return Split-Path $script:MyInvocation.MyCommand.Path  # returns this script's directory
}

$some_file_path = Get-ExecutingScriptDirectory | Join-Path -Path {$_} -ChildPath "foo.json"
$some_file_path 

read-host

它在此代码@get-content 中不起作用的原因是它在那里的计算结果为假。

Get-ExecutingScriptDirectory | Join-Path -Path {$_} -ChildPath "foo.json" | Get-Content {$_} and Get-ExecutingScriptDirectory | Join-Path -Path {$_} -ChildPath "foo.json" | Get-Content $_

如果对象的计算结果为 $True,则只能使用 $_ 或 $psItem 传递对象。

这就是它在第一个示例中起作用的原因。

function Get-ExecutingScriptDirectory() {
    return Split-Path $script:MyInvocation.MyCommand.Path  # returns this script's directory
}

if(Get-ExecutingScriptDirectory -eq $True){
    write-host This is true
}

Output: This is true

您也可以使用 Parethesis 来隔离管道中的对象。

示例:

Get-Content (Get-ExecutingScriptDirectory | Join-Path -Path {$_} -ChildPath "foo.json")

"|"处理它左边的对象,所以做 get-content |获取内容 |对脚本没有意义。如果你正在做类似的事情,你需要用分号将它分成多个命令。或者使用 "Foreach" cmdlet。

gc (Get-ExecutingScriptDirectory | Join-Path -Path {$_} -ChildPath "foo.json");gc (Get-ExecutingScriptDirectory | Join-Path -Path {$_} -ChildPath "bar.json")

你可以通过执行下面的命令来做你想做的事:

(Get-Content -Path (Get-ExecutingScriptDirectory | Join-Path -ChildPath "foo.json" | ConvertFrom-Json)).data

一些命令支持管道参数绑定。选项是按值流水线或 属性 流水线。参数绑定的一个很好的参考是 About Functions Advanced Parameters.

在线搜索您要使用的命令将得到参数绑定信息。例如 Join-Path,有一个参数部分。每个参数都有一个描述,包括字段 Accept pipeline input:。对于接受管道输入的参数,这必须是 True。通常,它会说明如何将值传递到管道中(ByPropertyNameByValue)。

ByPropertyName 表示必须输出一个包含与参数名称匹配的 属性 名称的对象。然后一旦对象被管道传输,参数将绑定到匹配的 属性 名称的值。请参阅下面的示例:

$filePath = [pscustomobject]@{Path = 'c:\temp\test1\t.txt'}
$filePath

Path
----
c:\temp\test1\t.txt

$filePath.Path # This binds to -Path in Get-Content
c:\temp\test1\t.txt
$filepath | Get-Content 

ByValue 表示传入的任何值都将尝试绑定到该参数。如果在绑定时存在类型差异,则可能会抛出错误。见下面的例子:

"c:\temp" | Join-Path -ChildPath "filepath" # c:\temp binds to `-Path`
c:\temp\filepath

关于$_,与$PSItem同义,是脚本块中的当前输入对象。您通常会看到它与 Foreach-ObjectWhere-Object 一起使用。如果您没有脚本块,您将无法使用 $_.

从技术上讲,您可以将任何内容通过管道传输到 Foreach-ObjectWhere-Object。那么当前的管道对象将被表示为$_。您不需要集合,因为可以通过管道输入单个项目。见下文:

"c:\temp" | Foreach-Object { $_ }
c:\temp

$filePath | Foreach-Object { $_ }

Path
----
c:\temp\test1\t.txt

$filePath | Foreach-Object { $_.Path }
c:\temp\test1\t.txt

这是一个简单的例子。在文档中搜索 "accept pipeline input"。使用带有 get-content 的 -path 参数的脚本块有点高级。大多数人改用 foreach-object。它之所以有效,是因为 -path 接受管道输入,但只能通过 属性 名称。 join-path 的 -path 参数可以按值通过管道,因此更容易。一旦你理解了它,它就会派上用场。也许有时候尝试一下会更容易。

echo '"hi"' > foo.json

'.\' | Join-Path -ChildPath foo.json | Get-Content -Path { $_ } | ConvertFrom-Json

hi

或者使用 foreach,在本例中是 foreach-object 的缩写。但是 $_ 必须始终在花括号内。

'.\' | Join-Path -ChildPath foo.json | foreach { Get-Content $_ } | ConvertFrom-Json