如何在PowerShell管道中引用前一个“管道”的输出?

3

我编写了以下代码以获取一些(相对的)文件路径:

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输出为null - 但实际上不是 - 当我像这样编写脚本时,它是正常的:

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

因此问题在于 $_ 是空的。我期望 $_ 是对前一个管道输出的引用。MSDN 文档 也是这样建议的,但似乎它自己立即就矛盾了:

$_ 包含管道对象中的当前对象。您可以在执行针对每个对象或管道中选择的对象执行操作的命令时使用此变量。

在我的代码上下文中,$_ 似乎符合“管道对象中的当前对象”的条件,但我没有将其与在每个对象上执行操作或在管道中选择的对象一起使用的命令配合使用。
看起来 $$$^ 很有用,但 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

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


如果Get-ExecutingScriptDirectory返回一个路径值,那么你可以直接将它传递到Join-Path中,因为-Path参数通过管道接受值 --> Get-ExecutingScriptDirectory | Join-Path -ChildPath ...。这里没有当前的输入对象$_,因为你没有使用一个带有脚本块的命令来处理它。如果你使用Get-ExecutingScriptDirectory | Foreach-Object { },你将在Foreach脚本块中访问$_,并且可以创建其他你喜欢的变量。 - AdminOfThings
@AdminOfThings 在这里使用 ForEach-Object 似乎有些奇怪,因为之前的函数(Get-ExecutingScriptDirectory)只输出了一个东西(一个字符串)。我还不清楚它是否在技术上是“字符串类型的对象”。另外,我想将 Get-ExecutingScriptDirectory 的整个输出都管道传递到 Join-Path 的一个参数中,但我没有看到合适的自动变量。 - alex
你只能在脚本块{ }中使用$_。这是非常基础的。文档中说明了哪些参数可以被管道传递,有些只需要按值传递,有些需要按属性名称传递。 - js2010
在这些文档中搜索“接受管道输入”。 - js2010
@js2010 哇 - 天啊。好的,我猜那回答了我的问题。谢谢。 - alex
显示剩余6条评论
3个回答

2

以下是一个简单的示例。在文档中搜索“接受管道输入”。使用带有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

1
只需使用代码块。
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

您可以使用括号将管道中的对象隔离开来。

例如:

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

"|" 处理其左侧的对象,因此执行 get-content | get-content | 对脚本没有意义。如果您要执行这样的操作,则需要使用分号将其分成多个命令。或者使用 "Foreach" cmdlet。 gc (Get-ExecutingScriptDirectory | Join-Path -Path {$_} -ChildPath "foo.json");gc (Get-ExecutingScriptDirectory | Join-Path -Path {$_} -ChildPath "bar.json") 以上代码会获取 foo.json 和 bar.json 的内容。

当然可以,但这并没有帮助我理解为什么 $_ 只在脚本块上下文中起作用。此外,使用脚本块方法会导致 Get-ExecutingScriptDirectory | Join-Path -Path {$_} -ChildPath "foo.json" | Get-Content {$_}Get-ExecutingScriptDirectory | Join-Path -Path {$_} -ChildPath "foo.json" | Get-Content $_ 都失败。 - alex

1

通过执行以下命令,您可以实现您想要的功能:

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

一些命令支持管道参数绑定。选项有按值或按属性进行管道。有关参数绑定的良好参考资料是关于函数高级参数

在网上搜索要使用的命令将产生参数绑定信息。例如Join-Path,有一个参数部分。每个参数都会有一个描述,包括字段接受管道输入:。对于一个参数来接受管道输入,这必须是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

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