以下findstr.exe
命令几乎做了我想要的,但并不完全:
findstr /s /i /c:"word1 word2 word3" *.abc
我用过:
/s
用于搜索所有子文件夹。/c:
将指定文本用作文字搜索字符串
/i
指定搜索不区分大小写。*.abc
文件类型为abc。以上内容将word1 word2 word3
视为文字,因此只会在中找到确切顺序的字词。
相比之下,我希望所有字匹配单独,任何顺序(AND逻辑,连接)
如果我从上面的命令中删除/c:
,则返回匹配任何字的行(OR逻辑,析取),这不是我想要的。
可以在PowerShell中完成吗?
答案 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 answer和Ansgar Wiecher's helpful answer;或者,请参阅本答案的底部,该答案提供了一个改编自Mathias代码的通用解决方案。
(由于对问题的初步误读),答案的这一部分使用析取逻辑 - 匹配至少一行的行匹配搜索字词 - findstr.exe
和PowerShell Select-String
(直接)支持的唯一逻辑。
相比之下, OP要求联合逻辑,这需要额外的工作。
在使用findstr.exe
将Select-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
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; (...).Count
:Select-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,或者您确实希望结果中出现这些重复出现,则可以使用它。