如何使用函数从管道中获取对象作为字符串?

iot*_*top 8 powershell

以字符串而不是对象形式输出结果的命令:

ls | Out-String -Stream
Run Code Online (Sandbox Code Playgroud)

输出:

    Directory: C:\MyPath\dir1

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2022-01-22  5:34 PM              0 1.txt
-a---          2022-01-22  5:34 PM              0 2.txt
-a---          2022-01-22  5:34 PM              0 3.txt
Run Code Online (Sandbox Code Playgroud)

我尝试使用函数获得相同的结果:

function f {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline,
            ValueFromPipelineByPropertyName)]
        $Content
    )
    
    process {
        $Content | Out-String -Stream
    }
}

ls | f
Run Code Online (Sandbox Code Playgroud)

但是,每个项目的输出都是分开的:

    Directory: C:\MyPath\dir1

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2022-01-22  5:34 PM              0 1.txt


    Directory: C:\MyPath\dir1

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2022-01-22  5:34 PM              0 2.txt


    Directory: C:\MyPath\dir1

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2022-01-22  5:34 PM              0 3.txt
Run Code Online (Sandbox Code Playgroud)

如何使用该函数获得与第一个命令相同的结果?

San*_*zon 10

正如亚伯拉罕在评论中指出的那样,您可以首先捕获来自管道的所有对象,然后将对象作为流输出输出,以便在控制台中正确显示。

值得注意的是,下面显示的两个示例都不是真正的“流函数”,正如mklement0在他的有用答案中指出的那样,这两个函数首先收集来自管道的所有输入,然后立即将对象作为字符串流输出,而不是逐个对象

function f {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [object] $Content
    )

    begin { $output = [System.Collections.Generic.List[object]]::new() }
    process { $output.Add($Content) }
    end { $output | Out-String -Stream }
}
Run Code Online (Sandbox Code Playgroud)

作为上面发布的高级函数的替代方案,下面的示例也可以工作,因为自动变量$input如何通过枚举函数的所有输入的集合来end中工作:

function f { $input | Out-String -Stream }
Run Code Online (Sandbox Code Playgroud)


iRo*_*Ron 5

我认为这个问题需要一个反问:

\n

为什么要从管道中以字符串形式获取对象?

\n

我怀疑你要么有一个罕见的需求,要么没有完全理解PowerShell Pipeline

\n

一般来说,我会避免Out-String在 pipeline\xc2\xb9 的中间使用,因为虽然可以从 pipeline 的中间调用它,但它已经格式化了输出,类似于Format-Table通常在流末尾完成的操作。关键还在于,如果不知道接下来会发生什么,就很难预先确定列的宽度。

\n
    \n
  1. 除了@mklement0提到的例外:
    \n有一个很好的理由使用Out-String -Stream: 来弥补Select-String使用非字符串输入对象的无用行为。事实上,PowerShell 附带了代理函数 oss,它封装了Out-String -Stream- 另请参阅:GitHub 问题 #10726和他的有用答案
  2. \n
\n

Out-String -Stream参数对此也无济于事,因为它所做的只是将多行字符串分解为单独的字符串:

\n
\n

默认情况下,Out-String输出单个字符串,其格式与您在控制台中看到的一样,包括任何空白标题或尾随换行符。Stream 参数可以Out-String逐行输出。唯一的例外是多行字符串。在这种情况下,Out-String仍会将字符串输出为单个多行字符串。

\n
\n
(ls  | Out-String -Stream).count\n8\n\n(ls  | Out-String -Stream)[3]\nMode                 LastWriteTime         Length Name\n
Run Code Online (Sandbox Code Playgroud)\n

但是,如果您确实需要将PowerShell 对象(针对流式传输进行了优化)贬值为字符串(不会造成阻塞)管道),您实际上可以这样做:

\n
function f {\n    [CmdletBinding()] param ([Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]$Content)\n    begin { $First = $True }\n    process {\n        if ($First) { $Content |Out-String -Stream |Select-Object -SkipLast 1 }\n        else { $Content |Out-String -Stream |Select-Object -Skip 5 |Select-Object -SkipLast 1 }\n        $First = $False\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

它显示了您正在尝试做的事情,但正如所说的,如果您没有很好的理由,我真的建议您不要这样做。执行此操作并尊重管道的通常方法是将Out-String外部 cmdlet 放置在流的末尾:

\n
ls |f |Out-String -Stream\n
Run Code Online (Sandbox Code Playgroud)\n


mkl*_*nt0 5

正如您所经历的,调用Out-String -Stream 每个输入对象并不能按预期工作,因为除了效率低之外,单独格式化每个对象总是会重复(表)格式输出中的标头。

圣地亚哥的有用答案中的解决方案是有效的,但缺点是在处理之前首先收集所有管道输入,如以下示例所示:

function f { $input | Out-String -Stream }

# !! Output doesn't appear until after the sleep period.
& { Get-Item $PROFILE; Start-Sleep 2; Get-Item $PROFILE } | f
Run Code Online (Sandbox Code Playgroud)

注意:输出时序是一方面,另一个方面是内存使用;在给定的用例中,这两个方面都可能重要,也可能不重要。


要以流方式包装 cmdlet 调用(其中对象可用时对其进行处理) ,您需要一个利用可步进管道的所谓代理(包装器)函数

事实上,PowerShell 附带了一个oss函数,该函数恰好是 的代理函数Out-String -Stream,作为后者的便捷快捷方式:

# Streaming behavior via the built-in proxy function oss:
# First output object appears *right away*.
& { Get-Item $PROFILE; Start-Sleep 2; Get-Item $PROFILE } | oss
Run Code Online (Sandbox Code Playgroud)

代理函数的定义oss(wraps Out-String -Stream);函数体通过以下方式获得$function:oss

function oss {

  [CmdletBinding()]
  param(
    [ValidateRange(2, 2147483647)]
    [int]
    ${Width},
  
    [Parameter(ValueFromPipeline = $true)]
    [psobject]
    ${InputObject})
  
  begin {
    $PSBoundParameters['Stream'] = $true
    $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Out-String', [System.Management.Automation.CommandTypes]::Cmdlet)
    $scriptCmd = { & $wrappedCmd @PSBoundParameters }
  
    $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
    $steppablePipeline.Begin($PSCmdlet)
  }
  
  process {
    $steppablePipeline.Process($_)
  }
  
  end {
    $steppablePipeline.End()
  }
  <#
  .ForwardHelpTargetName Out-String
  .ForwardHelpCategory Cmdlet
  #>
  
}
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 主体的大部分是生成的代码 - 只有包装的 cmdlet 的名称 - Out-String- 及其参数 - -Stream- 特定于函数。

  • 请参阅此答案以获取更多信息。