首页 > 解决方案 > PowerShell,管道兼容的 Select-String 函数

问题描述

这更多是因为我对管道和功能的理解不足,所以希望能得到帮助。举个例子:

Get-Service | Select-String win   # fails
Get-Service | Out-String -Stream | Select-String win   # works

我已经看到了为什么Out-String -Stream需要的解释,但这让我有点沮丧,所以我想要一个oss可以完成这Out-String -Stream部分的函数。我的想法是这样的:

function oss { Out-String -Stream }
Get-Service | oss | Select-String win

但这失败了。有人可以告诉我如何使这项工作,即使oss功能管道兼容?

然后,我们如何执行以下操作(显然这不起作用,但我真的很好奇如何实现)?

function slx { Out-String -Stream | Select-String <and allow all of the same switches as Select-String> }
Get-Service | slx win

那么,实际上,该部分slx的变体是否Select-String也总是如此Out-String -Stream

标签: stringpowershellpipeline

解决方案


我很感激你想学习,所以我不仅会给你一个可行的解决方案,还会解释一些事情。

您似乎对函数和管道的工作方式有基本的了解。如果您想了解有关它的更多信息,请查看about_Pipelinesabout_Functions(以及相关的帮助主题),或者在评论中告诉我您特别想知道的内容。

如果您是 Powershell 的新手,您需要了解的一件事是它是基于对象的,而不是基于文本的。它是通过管道传递的对象,您应该像这样使用它们,而不是将它们转换为文本(除非用于文件输出)。

有关Select-String的文档特别指出,它用于在字符串和文件中查找文本。但这不是你在这里想要做的。正如我所说,使用对象,而不是文本。所以省略Out-String. 过滤管道中对象的正确命令是Where-Object或其别名之一where?.

例子:

Get-Service | where { $_.DisplayName -like *win* }
# in newer Powershell versions, this shorter syntax is also possible:
Get-Service | where DisplayName -like *win*

但是正如您特别询问有关使功能“管道感知”的那样,这可以通过多种方式完成。但是您的问题不仅在于从管道获取输入,还在于您将其传递给的方式Out-String,因为如果您一次只传递一个项目,输出将会改变。

基本上你必须一次传递所有输入。因此,在您的情况下实现此目的的最简单方法是使用$Input自动变量:

 function oss { $Input | Out-String -Stream }

如果您需要一次处理一个管道项,则有更多方法可以获取管道输入。例如一个高级功能

function example { 
    process {
        $_ # current item
    }
}

您还可以使用参数属性将管道输入绑定到函数参数:

function example {
    param(
        [Parameter(ValueFromPipeline = $true)]
        $InputObject
    )
    process {
        $InputObject # current item
    }
}

我能想到的最后一种方法是使用filter,这是一种特殊的函数,专门设计用于对每个管道元素执行操作:

filter example {
    $_ # current item
}

要了解有关它的更多信息,我建议使用 google 搜索,它会快速为您提供有用的文章,例如为管道构建 PowerShell 函数或将管道输入合并到 PowerShell 函数中。

如果你真的想构建一个基本上是另一个 cmdlet 的包装器的方法,Out-String或者Select-String具有所有相同的开关,你应该看看proxy functions。但我认为这对于你想要做的事情来说太过分了:

function oss {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline = $true)]
        [psobject]$InputObject
    )
    begin
    {
        try {
            $outBuffer = $null
            if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
            {
                $PSBoundParameters['OutBuffer'] = 1
            }
            # set -Stream switch always
            $PSBoundParameters["Stream"] = $true
            $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Out-String', [System.Management.Automation.CommandTypes]::Cmdlet)
            $scriptCmd = {& $wrappedCmd @PSBoundParameters }
            $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
            $steppablePipeline.Begin($PSCmdlet)
        } catch {
            throw
        }
    }
    process
    {
        try {
            $steppablePipeline.Process($_)
        } catch {
            throw
        }
    }
    end
    {
        try {
            $steppablePipeline.End()
        } catch {
            throw
        }
    }
}

推荐阅读