【问题标题】:How do I reference the output of the previous "pipe" in a Powershell pipeline?如何在 Powershell 管道中引用前一个“管道”的输出?
【发布时间】:2020-02-14 19:13:01
【问题描述】:

我编写这段代码是为了获取一些(相对)文件路径:

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 也暗示了这一点,但它似乎立即自相矛盾:

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

在我的代码上下文中,$_ 似乎符合“管道对象中的当前对象”的条件 - 但我没有将它与对每个对象或管道中选定对象执行操作的命令一起使用.

$$$^ 看起来很有希望,但 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 ForEach-Object 在这里看起来很奇怪,因为前面的函数 (Get-ExecutingScriptDirectory) 只输出一个东西(一个字符串)。我不清楚它在技术上是否是“字符串类型的对象”。另外,我想将Get-ExecutingScriptDirectory 的整个输出通过管道传递给Join-Path 的一个参数,但我没有看到一个似乎合适的自动变量。
  • 只需删除 -Path $_ 即可。
  • @AdminOfThings 是的,但是 Get-ExecutingScriptDirectory | Join-Path -ChildPath "foo.json" | Get-Content $_Get-ExecutingScriptDirectory | Join-Path -ChildPath "foo.json" | Get-Content 都无法工作。我不确定为什么前一个管道的标准输出会自动路由到 -Path 参数(与 -ChildPath 参数或其他参数相反)。
  • 那是因为Get-Content 没有从管道中按值绑定的参数。您要么需要通过管道进入Foreach-Object 并使用-Path $_,要么创建一个名为Path 的对象属性,并将路径作为值,然后再通过管道传输到Get-Content

标签: powershell visual-studio-code powershell-5.0


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    (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
    

    【讨论】:

      【解决方案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"
      $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-ExecutingScriptDirectory | Join-Path -Path {$_} -ChildPath "foo.json" | Get-Content {$_}Get-ExecutingScriptDirectory | Join-Path -Path {$_} -ChildPath "foo.json" | Get-Content $_ 的脚本块方法都失败了。
      猜你喜欢
      • 1970-01-01
      • 2011-07-16
      • 1970-01-01
      • 1970-01-01
      • 2021-06-14
      • 1970-01-01
      • 1970-01-01
      • 2012-03-10
      • 1970-01-01
      相关资源
      最近更新 更多