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

如何在PowerShell中使用FINDSTR查找搜索字符串中所有单词以任意顺序匹配的行,powershell,boolean-logic,findstr,Powershell,Boolean Logic,Findstr,下面的findstr.exe命令几乎满足了我的要求,但还不够: findstr /s /i /c:"word1 word2 word3" *.abc 我使用了: /s用于搜索所有子文件夹 /c: 将指定的文本用作文字搜索字符串 /i指定搜索不区分大小写 *.abcabc类型的文件 上面的代码将word1 word2 word3作为文本查找,因此只查找按该确切顺序排列的单词 相比之下,我希望所有单词以任何顺序(以及逻辑、连接)单独匹配。 如果我从上面的命令中删除/c:,那么将返回与任何单词

下面的
findstr.exe
命令几乎满足了我的要求,但还不够:

findstr /s /i /c:"word1 word2 word3" *.abc
我使用了:

  • /s
    用于搜索所有子文件夹
  • /c:
    将指定的文本用作文字搜索字符串

  • /i
    指定搜索不区分大小写
  • *.abc
    abc类型的文件
上面的代码将
word1 word2 word3
作为文本查找,因此只查找按该确切顺序排列的单词

相比之下,我希望所有单词以任何顺序(以及逻辑、连接)单独匹配。

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


这可以在PowerShell中完成吗?

您可以使用
Select String
在多个文件中执行基于正则表达式的搜索

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

在上面的示例中,第一个命令就是这样:

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

然后,我们通过管道将生成的FileInfo对象传递到
选择字符串
,并使用以下正则表达式模式:

由于每个前瞻组只是为了正确性而被断言,并且字符串中的搜索位置从未改变,因此顺序并不重要


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

Get-ChildItem -Filter *.abc -Recurse |Select-String -Pattern '\b(?:word1|word2|word3)\b'

当然,这些可以抽象成一个整体

我生成了
param
块和
Select Match
函数定义的大部分主体,如下所示:

$slsmeta = [System.Management.Automation.CommandMetadata]::new((Get-Command Select-String))
[System.Management.Automation.ProxyCommand]::Create($slsmeta)
然后删除不必要的参数(包括
-AllMatches
-Pattern
),然后添加模式生成器(参见内联注释):

注:

  • 此答案的第一部分并不能解决OP的问题-有关解决方案,请参阅和;或者,请参阅此答案的底部,它提供了一个根据Mathias代码改编的通用解决方案

    • (由于对问题的最初误读),答案的这一部分使用析取逻辑——至少有一个匹配搜索项的匹配行——这是唯一支持
      findstr.exe
      和PowerShell的
      选择字符串
      (直接)的逻辑

    • 相比之下,OP要求连接逻辑,这需要额外的工作

  • 对于使用
    Select String
    findstr.exe
    命令转换为PowerShell,这部分答案可能仍然很有意义


问题中的
findstr
命令的PowerShell等价物,但没有
/c:
-
FINDSTR/s/i“word1 word2 word3”*.abc

  • 是:

    (Get ChildItem-File-Filter*.abc-Recurse| 选择String-SimpleMatch-Pattern'word1'、'word2'、'word3')。计数

  • /s
    ->
    Get ChildItem-File-Filter*.abc-Recurse
    输出当前目录子树中匹配的所有文件
    *.abc

    • 请注意,wile能够接受文件名模式(通配符表达式),例如
      *.abc
      ,它不支持递归,因此需要单独的
      Get ChildItem
      调用,其输出通过管道传输到
      Select String
  • findstr
    ->
    选择字符串
    ,PowerShell更灵活的对应项:

    • -SimpleMatch
      指定将
      -Pattern
      参数解释为文本,而不是正则表达式。请注意它们的默认值有何不同:

      • findstr
        默认情况下需要文本(您可以使用
        /R
        切换到正则表达式)
      • Select String
        默认情况下需要正则表达式(您可以使用
        -SimpleMatch
        切换到literal)
    • -i
      ->(默认行为);与大多数PowerShell一样,不区分大小写是
      选择字符串的默认行为添加
      -区分大小写
      ,以更改该行为

    • “word1 word2 word3”
      ->
      -模式“word1”、“word2”、“word3”
      指定模式数组会查找每行上至少一个模式的匹配项(析取逻辑)

      • 也就是说,以下所有行都将匹配:
        。。。word1…
        。。。word2…
        。。。word2 word1…
        。。。word3 word1 word2…
  • /c
    ->
    (…).Count
    选择字符串
    输出表示匹配行的对象集合
    ,此表达式仅对匹配行进行计数。 输出的对象是实例,其中不仅包括匹配行,还包括关于输入的元数据和匹配内容的细节


解决方案,基于:

是仅析取
选择字符串
cmdlet
的仅合取包装函数,该cmdlet使用与后者完全相同的语法,但不支持
-AllMatches
开关

也就是说,
selectstringall
要求传递给它的所有模式-无论它们是正则表达式(默认情况下)还是文本(使用
-SimpleMatch
)-匹配一行。

^(?=.*\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
Get-ChildItem -Filter *.abc -Recurse |Select-String -Pattern '\b(?:word1|word2|word3)\b'
\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
$slsmeta = [System.Management.Automation.CommandMetadata]::new((Get-Command Select-String))
[System.Management.Automation.ProxyCommand]::Create($slsmeta)
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

    #>

}
Get-ChildItem -Filter *.abc -Recurse |Select-Match word1,word2,word3 -All
(Get-ChildItem -File -Filter *.abc -Recurse |
  Select-StringAll -SimpleMatch word1, word2, word3).Count
findstr /s /i "word1" *.abc | findstr /i "word2" | findstr /i "word3"
Get-ChildItem -Filter '*.abc' -Recurse | Get-Content | Where-Object {
  $_ -like '*word1*' -and
  $_ -like '*word2*' -and
  $_ -like '*word3*'
}
ls '*.abc' -r | cat | ? {
  $_ -like '*word1*' -and
  $_ -like '*word2*' -and
  $_ -like '*word3*'
}
findstr /i /r /c:"word[1-3].*word[1-3].*word[1-3]" *.abc