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

时间:2017-04-17 11:49:31

标签: powershell boolean-logic findstr

以下findstr.exe命令几乎做了我想要的,但并不完全:

findstr /s /i /c:"word1 word2 word3" *.abc

我用过:

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

    将指定文本用作文字搜索字符串

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

以上内容将word1 word2 word3视为文字,因此只会在中找到确切顺序的字词。

相比之下,我希望所有字匹配单独任何顺序(AND逻辑,连接)

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

可以在PowerShell中完成吗?

4 个答案:

答案 0 :(得分:5)

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

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

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

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

Get-ChildItem -Filter *.abc -Recurse
     

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

由于每个前瞻组只是为了正确而被断言,并且字符串中的搜索位置永远不会改变,所以顺序并不重要。

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

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

这些当然可以在a simple proxy function中抽象出来。

我生成了param块以及Select-Match函数定义的大部分主体,其中包含:

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

然后删除了不必要的参数(包括-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

    #>

}

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

Get-ChildItem -Filter *.abc -Recurse |Select-Match word1,word2,word3 -All

答案 1 :(得分:4)

另一种(公认的不太复杂)的方法是简单地用菊花链过滤器,因为单词的顺序无关紧要。首先过滤文件中的一个单词,然后过滤输出中包含第二个单词的行,然后过滤那个输出的行也包含第三个单词。

findstr /s /i "word1" *.abc | findstr /i "word2" | findstr /i "word3"

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

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*'
}

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

答案 2 :(得分:2)

注意:

  • 本答案的第一部分解决了OP的问题 - 对于解决方案,请参阅Mathias R. Jessen's helpful answerAnsgar Wiecher's helpful answer;或者,请参阅本答案的底部,该答案提供了一个改编自Mathias代码的通用解决方案。

    • (由于对问题的初步误读),答案的这一部分使用析取逻辑 - 匹配至少一行的行匹配搜索字词 - findstr.exe和PowerShell Select-String(直接)支持的唯一逻辑。

    • 相比之下, OP要求联合逻辑,这需要额外的工作。

  • 在使用findstr.exeSelect-String命令转换为PowerShell方面,这部分答案可能仍然有用。

问题但没有findstr PowerShell等同于/c:命令 -
FINDSTR /s /i "word1 word2 word3" *.abc
  - 是:

(Get-ChildItem -File -Filter *.abc -Recurse |
  Select-String -SimpleMatch -Pattern 'word1', 'word2', 'word3').Count
  • /s - &gt; Get-ChildItem -File -Filter *.abc -Recurse输出当前目录子树中匹配*.abc

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

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

      • findstr默认情况下需要文字(您可以切换到/R的正则表达式。)
      • Select-String默认情况下需要 regexes (您可以使用-SimpleMatch切换到文字。)
    • -i - &gt; (默认行为);与大多数PowerShell一样, case- 敏感度为Select-String的默认行为 - 添加-CaseSensitive以更改该内容。

    • "word1 word2 word3" - &gt; -Pattern 'word1', 'word2', 'word3'; 指定模式的数组会查找每行至少一个模式的匹配 disjunctive 逻辑)。

      • 也就是说,以下所有行都匹配:... word1 ...... word2 ...... word2 word1 ...... word3 word1 word2 ...
  • /c - &gt; (...).CountSelect-String输出对象集合,表示匹配的行,此表达式只计算。 对象输出为 [Microsoft.PowerShell.Commands.MatchInfo]个实例,不仅包含匹配的行,而且有关输入的元数据以及匹配的具体内容

基于Mathias R. Jessen's elegant wrapper function构建的解决方案:

Select-AllStrings是仅用于析取的Select-String cmdlet 的仅限连接的伴随函数,它使用与后者完全相同的语法,但不支持-AllMatches切换。

也就是说,Select-AllStrings要求所有模式传递给它 - 无论它们是正则表达式(默认情况下)还是文字(带-SimpleMatch) - 匹配该行。

应用于OP的问题,我们得到:

(Get-ChildItem -File -Filter *.abc -Recurse |
  Select-AllStrings -SimpleMatch word1, word2, word3).Count

注意与顶部命令相比的变化:
* -Pattern参数由参数位置隐式绑定 *为方便起见,模式被指定为裸字(未引用),尽管引用通常更安全,因为要记住需要引用的内容并不容易。

答案 3 :(得分:0)

如果您在同一行中没有重复任何单词,则以下内容将起作用: word1你好word1再见word1

findstr /i /r /c:"word[1-3].*word[1-3].*word[1-3]" *.abc

如果不存在重复的word1 / word2 / word3,或者您确实希望结果中出现这些重复出现,则可以使用它。