如何在PowerShell中使用FINDSTR查找搜索字符串中所有单词以任何顺序匹配的行

Ray*_*ker 3 powershell boolean-logic findstr

以下findstr.exe命令几乎可以实现我想要的功能,但并不完全:

findstr /s /i /c:"word1 word2 word3" *.abc
Run Code Online (Sandbox Code Playgroud)

我用过:

  • /s 用于搜索所有子文件夹.
  • /c:

    使用指定的文本作为文字搜索字符串

  • /i 指定搜索不区分大小写.
  • *.abc abc类型的文件.

上述验看word1 word2 word3作为文字,因此只发现在的话,准确的顺序.

相比之下,我想所有的字匹配个别,在任何顺序(与逻辑,结合).

如果我/c:从上面的命令中删除,那么返回匹配任何单词的行(OR逻辑,析取),这不是我想要的.

可以在PowerShell中完成吗?

Mat*_*sen 6

您可以使用Select-String通过多个文件进行基于正则表达式的搜索.

要将单个字符串中的所有多个搜索项与正则表达式匹配,您必须使用外观断言:

Get-ChildItem -Filter *.abc -Recurse |Select-String -Pattern '^(?=.*\bword1\b)(?=.*\bword2\b)(?=.*\bword3\b).*$'
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,这是第一个命令发生的事情:

Get-ChildItem -Filter *.abc -Recurse
Run Code Online (Sandbox Code Playgroud)

Get-ChildItem搜索当前目录
-Filter *.abc中的文件只显示以*.abc
-Recurse搜索所有子文件夹结尾的文件

然后,我们将生成的FileInfo对象传递给Select-String并使用以下正则表达式模式:

^(?=.*\bword1\b)(?=.*\bword2\b)(?=.*\bword3\b).*$
^              # start of string  
 (?=           # open positive lookahead assertion containing
    .*         # any number of any characters (like * in wildcard matching)
      \b       # word boundary
        word1  # the literal string "word1"
      \b       # word boundary
 )             # close positive lookahead assertion
 ...           # repeat for remaining words
 .*            # any number of any characters
$              # end of string
Run Code Online (Sandbox Code Playgroud)

由于每个前瞻组只是为了正确而断言,并且字符串中的搜索位置永远不会改变,因此顺序无关紧要.


如果您希望它匹配包含任何单词的字符串,您可以使用简单的非捕获组:

Get-ChildItem -Filter *.abc -Recurse |Select-String -Pattern '\b(?:word1|word2|word3)\b'
Run Code Online (Sandbox Code Playgroud)
\b(?:word1|word2|word3)\b
\b          # start of string  
  (?:       # open non-capturing group
     word1  # the literal string "word1"
     |      # or
     word2  # the literal string "word2"
     |      # or
     word3  # the literal string "word3"
  )         # close positive lookahead assertion
\b          # end of string
Run Code Online (Sandbox Code Playgroud)

这些当然可以通过简单的代理功能抽象出来.

我生成了param块和Select-Match下面函数定义的大部分主体:

$slsmeta = [System.Management.Automation.CommandMetadata]::new((Get-Command Select-String))
[System.Management.Automation.ProxyCommand]::Create($slsmeta)
Run Code Online (Sandbox Code Playgroud)

然后删除不必要的参数(包括-AllMatches-Pattern),然后添加模式生成器(参见内联注释):

function Select-Match
{
    [CmdletBinding(DefaultParameterSetName='Any', HelpUri='http://go.microsoft.com/fwlink/?LinkID=113388')]
    param(
        [Parameter(Mandatory=$true, Position=0)]
        [string[]]
        ${Substring},

        [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
        [Alias('PSPath')]
        [string[]]
        ${LiteralPath},

        [Parameter(ParameterSetName='Any')]
        [switch]
        ${Any},

        [Parameter(ParameterSetName='Any')]
        [switch]
        ${All},

        [switch]
        ${CaseSensitive},

        [switch]
        ${NotMatch},

        [ValidateNotNullOrEmpty()]
        [ValidateSet('unicode','utf7','utf8','utf32','ascii','bigendianunicode','default','oem')]
        [string]
        ${Encoding},

        [ValidateNotNullOrEmpty()]
        [ValidateCount(1, 2)]
        [ValidateRange(0, 2147483647)]
        [int[]]
        ${Context}
    )

    begin
    {
        try {
            $outBuffer = $null
            if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
            {
                $PSBoundParameters['OutBuffer'] = 1
            }

            # Escape literal input strings
            $EscapedStrings = foreach($term in $PSBoundParameters['Substring']){
                [regex]::Escape($term)
            }

            # Construct pattern based on whether -Any or -All was specified 
            if($PSCmdlet.ParameterSetName -eq 'Any'){
                $Pattern = '\b(?:{0})\b' -f ($EscapedStrings -join '|')
            } else {
                $Clauses = foreach($EscapedString in $EscapedStrings){
                    '(?=.*\b{0}\b)' -f $_
                }
                $Pattern = '^{0}.*$' -f ($Clauses -join '')
            }

            # Remove the Substring parameter argument from PSBoundParameters
            $PSBoundParameters.Remove('Substring') |Out-Null

            # Add the Pattern parameter argument
            $PSBoundParameters['Pattern'] = $Pattern

            $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Select-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
        }
    }
    <#

    .ForwardHelpTargetName Microsoft.PowerShell.Utility\Select-String
    .ForwardHelpCategory Cmdlet

    #>

}
Run Code Online (Sandbox Code Playgroud)

现在你可以像这样使用它,它的行为几乎就像Select-String:

Get-ChildItem -Filter *.abc -Recurse |Select-Match word1,word2,word3 -All
Run Code Online (Sandbox Code Playgroud)


Ans*_*ers 5

另一种(公认不太复杂的)方法是简单的菊花链滤波器,因为单词的顺序无关紧要。过滤您的文件一个字,然后再过滤输出也包含第二个单词,然后过滤线输出线也拥有这个第三个单词。

findstr /s /i "word1" *.abc | findstr /i "word2" | findstr /i "word3"
Run Code Online (Sandbox Code Playgroud)

使用 PowerShell cmdlet 上面的内容如下所示:

Get-ChildItem -Filter '*.abc' -Recurse | Get-Content | Where-Object {
  $_ -like '*word1*' -and
  $_ -like '*word2*' -and
  $_ -like '*word3*'
}
Run Code Online (Sandbox Code Playgroud)

或(使用别名):

ls '*.abc' -r | cat | ? {
  $_ -like '*word1*' -and
  $_ -like '*word2*' -and
  $_ -like '*word3*'
}
Run Code Online (Sandbox Code Playgroud)

请注意,别名只是为了节省在命令行上输入的时间,因此我不建议在脚本中使用它们。