使用FINDSTR中的子模式

5

我需要检查存储在变量中的字符串的有效性,不能使用外部CLI工具(如grep、awk等),因此我选择了FINDSTR。 该字符串的格式为(正则表达式):

([1-9][0-9]*:".*"(|".*")*)

我不知道如何检查子模式(|。*)。

目前我的代码是:

((ECHO.) | (SET /P "=(11:"a"|"b"|"c")") | (FINDSTR /R /C:"^([1-9][0-9]*:".*")$"))

敬礼。


2
如果可能的话,最好使用VBScript或PowerShell。在Windows批处理文件中操作包含特殊字符的字符串非常困难。 - Harry Johnston
很抱歉,@Harry Johnston,我只能使用标准的内部或外部命令来操作cmd.exe。 - networkcode
1
VBScript和JScript是CMD.EXE可用的标准本地实用程序,具有良好的正则表达式支持。PowerShell从Vista开始就是本地的,并且也具有良好的正则表达式支持。 - dbenham
dbenham:Windows脚本宿主与cmd无关,cscript只是一个普通的控制台可执行文件(在企业环境中有时会被禁用)。PowerShell可以安装在XP和Vista上,但仅预装在Windows 7和8(以及相应的服务器版本)上。 - Joey
@Joey:我认为重点在于cscript(像任何其他控制台可执行文件一样)可以通过cmd.exe使用。虽然它确实可以被组策略禁用,但任何其他外部命令(包括findstr)和cmd.exe本身也可以被禁用。 - Harry Johnston
2个回答

6
Mat M的说法是正确的,FINDSTR的限制很大。FINDSTR的正则表达式支持非常简单和非标准化。在命令行上键入HELP FINDSTRFINDSTR /?可以获取支持的简要概述。有关详细说明,请参阅Windows FINDSTR命令的未记录功能和限制是什么? 我喜欢Harry Johnston的评论-使用VBScript或JavaScript可以很容易地创建解决方案。我认为这将是一个更好的选择。
但是,这里是本地批处理解决方案。我已经合并了OP在Mat M答案的评论中提到的关于子模式数量的额外规则。
解决方案非常棘手。特殊字符可能会导致问题,因为管道在执行时会分别在自己的CMD会话中执行。特殊字符必须引用、转义两次或仅通过延迟展开公开。我选择使用延迟展开,但是!字符必须转义两次,以确保延迟展开发生在正确的时间。
解析可变数量的子模式最简单的方法是将分隔符替换为换行符,并使用FOR / F迭代每个子模式。
我的代码的前半部分是一个脆弱的编码框架,方便地迭代和测试一组字符串。它将无法正确处理任何一个<space>;,=<tab>*?字符串。此外,每个字符串中的引号必须平衡。
但更重要的验证例程可以处理var变量中的任何字符串。
@echo off
setlocal
set LF=^


::Above 2 blank lines are critical for creating a linefeed variable. Do not remove

set test=a

for %%S in (
  "(3:"a"|"c"|"c")"
  "(11:"a"|"b"|"c"|"d"|"esdf"|"f"|"g"|"h"|"i"|"j"|"k")"
  "(4:"a"|"b"|"c")"
  "(10:"a"|"b"|"c"|"d"|"esdf"|"f"|"g"|"h"|"i"|"j"|"k")"
  "(3:"a"|"b"|"c""
  "(3:"a"|"b^|c")"
  "(3:"a"|"b"|c)"
  "(3:"a"|"b"||"c")"
  "(3:"a"|"b"|;|"c")"
) do (
  set "var=%%~S"
  call :validate
)
exit /b

:validate
setlocal enableDelayedExpansion
cmd /v:on /c echo ^^^!var^^^!|findstr /r /c:"^([1-9][0-9]*:.*)$" >nul || (call :invalid  FINDSTR fail& exit /b)
if "!var:||=!" neq "!var!" (call :invalid double pipe fail& exit /b)
for /f "delims=(:" %%N in ("!var!") do set "expectedCount=%%N"
set "str=!var:*:=!"
set "str=!str:~0,-1!"
set foundCount=0
for %%A in ("!LF!") do for /f eol^=^%LF%%LF%^ delims^=  %%B in ("!str:|=%%~A!") do (
  if %%B neq "%%~B" (call :invalid sub-pattern fail& exit /b)
  set /a foundCount+=1
)
if %foundCount% neq %expectedCount% (call :invalid count fail& exit /b)
echo Valid: !var!
exit /b
:invalid
echo Invalid - %*: !var!
exit /b

运行批处理脚本后,以下是结果:

Valid: (3:"a"|"c"|"c")
Valid: (11:"a"|"b"|"c"|"d"|"esdf"|"f"|"g"|"h"|"i"|"j"|"k")
Invalid - count fail: (4:"a"|"b"|"c")
Invalid - count fail: (10:"a"|"b"|"c"|"d"|"esdf"|"f"|"g"|"h"|"i"|"j"|"k")
Invalid - FINDSTR fail: (3:"a"|"b"|"c"
Invalid - sub-pattern fail: (3:"a"|"b|c")
Invalid - sub-pattern fail: (3:"a"|"b"|c)
Invalid - double pipe fail: (3:"a"|"b"||"c")
Invalid - sub-pattern fail: (3:"a"|"b"|;|"c")


更新

通过将延迟扩展的启用推迟到CMD /V:ON管道之后,可以简化:validate例程。这意味着我不再需要担心管道左侧的!被双重转义。

:validate
cmd /v:on /c echo !var!|findstr /r /c:"^([1-9][0-9]*:.*)$" >nul || (call :invalid  FINDSTR fail& exit /b)
setlocal enableDelayedExpansion
... remainder unchanged

不错,虽然我们不知道第六个测试用例是否真的是假的。 - Mat M
@MatM - 很好的观点。如果它应该是有效的,那么解决方案将会更加复杂。 - dbenham
@dbenham:解决方案是正确的,谢谢!我想告诉你一个我用批处理完成的项目,希望能听到你的意见,可能会有共同的兴趣! - networkcode

2
据我所知,findstr 无法对正则表达式进行分组,因此 (|".*")* 是不可行的。如果您知道有多少个块并且您可以像这样复制代码,则可以解决问题。
FINDSTR /R /C:"^([1-9][0-9]*:\"..*\"|\"..*\"|\"..*\")$"

这样,如果您确定块的数量是恒定的,并且在需要时使用空块"",那么您可以进行检查。
表达式中的双引号将被忽略,除非您在其前面加上\。
..*结构旨在替换.+:一个或多个字符。

字符串不能包含固定数量的子模式,变量数量由冒号后面的第一个数字传递。也许一个解决方案是立即使用 FINDSTR 验证字符串,然后使用 FOR /F 分析每个子模式的令牌,如果内容和数量正确,您认为呢? - networkcode
@user1125183 - 这应该可以工作,但有点棘手。请参见我的答案 - dbenham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接