FINDSTR的正则表达式功能非常有限,可以在打开命令提示符窗口并运行
findstr /?
获取输出帮助来了解。与
Perl兼容的正则表达式或
Boost.Regex或其他使用Perl语法的正则表达式实现相比,
FINDSTR的正则表达式功能远远不够。
没有选项可用于仅获取由正则表达式匹配的字符串的输出。有几个选项可以控制
FINDSTR的输出,但没有一个选项可以仅获取找到的字符串的输出。
因此,需要使用不同的方法从包含
+-
或
\-
的处理行中删除感兴趣的数据之前的所有内容,在文件
dependency-list.txt
中。Windows命令处理器仅提供两个用于字符串重新格式化任务的命令:
FOR和
SET。
下面的批处理文件演示了它们两个,考虑到需要在访问同一命令块中定义/修改的环境变量值时使用延迟扩展。
@echo off
setlocal EnableExtensions DisableDelayedExpansion
cls
set "DeleteExample="
if exist dependency-list.txt goto GetData
(
set "DeleteExample=1"
echo First line with no data of interest.
echo [INFO] +- org.owasp.esapi:esapi:jar:2.0.1:compile
echo [INFO] ^| +- commons-configuration:commons-configuration:jar:1.5:compile
echo [INFO] ^| ^| +- commons-lang:commons-lang:jar:2.3:compile
echo ;[INFO]^| \- commons-lang:commons-lang:jar:4.5:compile
echo Line trailing space at end +-
echo Line with no space at end +-
echo Line with just a hyphen character - somewhere on line.
echo [INFO] ^| ^| +- !commons-configuration!:commons-configuration!:jar:5.2:compile
echo Last line with no data of interest.
)>dependency-list.txt
:GetData
echo First solution
echo ==============
echo/
setlocal EnableDelayedExpansion
for /F "tokens=*" %%I in ('%SystemRoot%\System32\findstr.exe /R "+- \\-" dependency-list.txt 2^>nul') do (
echo Line read: "%%I"
set "Line=%%I"
set "Line=!Line:*- =!"
if defined Line echo Line work: "!Line!"
rem More commands working with environment variable Line referenced with
rem exclamation marks for delayed expansion on execution of the command line.
)
endlocal
echo/
echo Second solution
echo ===============
echo/
for /F "tokens=1* delims=-" %%I in ('%SystemRoot%\System32\findstr.exe /R "+- \\-" dependency-list.txt') do (
if not "%%J" == "" (
echo Line read: "%%J"
set "Line=%%J"
setlocal EnableDelayedExpansion
call set "Line=%%Line:~1%%"
if defined Line call echo Line work: "%%Line%%"
rem More commands working with environment variable Line referenced
rem with two percent signs on both side and with using command CALL.
endlocal
)
)
echo/
echo Third solution
echo ==============
echo/
for /F "tokens=1* delims=-" %%I in ('%SystemRoot%\System32\findstr.exe /R "+- \\-" dependency-list.txt 2^>nul') do (
if not "%%J" == "" (
echo Line read: "%%J"
for /F "tokens=*" %%L in ("%%J") do if not "%%L" == "" (
echo Line work: "%%L"
rem More commands working with loop variable L.
)
)
)
echo/
echo Fourth solution
echo ===============
echo/
for /F delims^=^ eol^= %%I in ('%SystemRoot%\System32\findstr.exe /R "+- \\-" dependency-list.txt 2^>nul') do (
echo Line read: "%%I"
set "Line=%%I"
setlocal EnableDelayedExpansion
set "Line=!Line:*- =!"
if defined Line echo Line work: "!Line!"
rem More commands working with environment variable Line referenced with
rem exclamation marks for delayed expansion on execution of the command line.
endlocal
)
echo/
echo Fifth solution
echo ==============
echo/
for /F delims^=^ eol^= %%I in ('%SystemRoot%\System32\findstr.exe /L /C:+- /C:"\\-" dependency-list.txt') do (
echo Line read: "%%I"
set "Line=%%I"
call :ProcessLine
)
goto EndDemo
:ProcessLine
set "Line=%Line:*- =%"
if defined Line echo Line work: "%Line%"
rem More commands working with environment variable Line referenced with
rem percent signs for expansion before execution of the command line.
goto :EOF
:EndDemo
if defined DeleteExample del dependency-list.txt
echo/
endlocal
pause
这个批处理文件的输出是:
First solution
==============
Line read: "[INFO] +- org.owasp.esapi:esapi:jar:2.0.1:compile"
Line work: "org.owasp.esapi:esapi:jar:2.0.1:compile"
Line read: "[INFO] | +- commons-configuration:commons-configuration:jar:1.5:compile"
Line work: "commons-configuration:commons-configuration:jar:1.5:compile"
Line read: "[INFO] | | +- commons-lang:commons-lang:jar:2.3:compile"
Line work: "commons-lang:commons-lang:jar:2.3:compile"
Line read: "Line trailing space at end +- "
Line read: "Line with no space at end +-"
Line work: "Line with no space at end +-"
Line read: "[INFO] | | +- :commons-configuration:jar:5.2:compile"
Line work: ":commons-configuration:jar:5.2:compile"
Second solution
===============
Line read: " org.owasp.esapi:esapi:jar:2.0.1:compile"
Line work: "org.owasp.esapi:esapi:jar:2.0.1:compile"
Line read: " commons-configuration:commons-configuration:jar:1.5:compile"
Line work: "commons-configuration:commons-configuration:jar:1.5:compile"
Line read: " commons-lang:commons-lang:jar:2.3:compile"
Line work: "commons-lang:commons-lang:jar:2.3:compile"
Line read: " "
Line read: " !commons-configuration!:commons-configuration!:jar:5.2:compile"
Line work: "!commons-configuration!:commons-configuration!:jar:5.2:compile"
Third solution
==============
Line read: " org.owasp.esapi:esapi:jar:2.0.1:compile"
Line work: "org.owasp.esapi:esapi:jar:2.0.1:compile"
Line read: " commons-configuration:commons-configuration:jar:1.5:compile"
Line work: "commons-configuration:commons-configuration:jar:1.5:compile"
Line read: " commons-lang:commons-lang:jar:2.3:compile"
Line work: "commons-lang:commons-lang:jar:2.3:compile"
Line read: " "
Line read: " !commons-configuration!:commons-configuration!:jar:5.2:compile"
Line work: "!commons-configuration!:commons-configuration!:jar:5.2:compile"
Fourth solution
===============
Line read: "[INFO] +- org.owasp.esapi:esapi:jar:2.0.1:compile"
Line work: "org.owasp.esapi:esapi:jar:2.0.1:compile"
Line read: "[INFO] | +- commons-configuration:commons-configuration:jar:1.5:compile"
Line work: "commons-configuration:commons-configuration:jar:1.5:compile"
Line read: "[INFO] | | +- commons-lang:commons-lang:jar:2.3:compile"
Line work: "commons-lang:commons-lang:jar:2.3:compile"
Line read: ";[INFO]| \- commons-lang:commons-lang:jar:4.5:compile"
Line work: "commons-lang:commons-lang:jar:4.5:compile"
Line read: "Line trailing space at end +- "
Line read: "Line with no space at end +-"
Line work: "Line with no space at end +-"
Line read: "[INFO] | | +- !commons-configuration!:commons-configuration!:jar:5.2:compile"
Line work: "!commons-configuration!:commons-configuration!:jar:5.2:compile"
Fifth solution
==============
Line read: "[INFO] +- org.owasp.esapi:esapi:jar:2.0.1:compile"
Line work: "org.owasp.esapi:esapi:jar:2.0.1:compile"
Line read: "[INFO] | +- commons-configuration:commons-configuration:jar:1.5:compile"
Line work: "commons-configuration:commons-configuration:jar:1.5:compile"
Line read: "[INFO] | | +- commons-lang:commons-lang:jar:2.3:compile"
Line work: "commons-lang:commons-lang:jar:2.3:compile"
Line read: ";[INFO]| \- commons-lang:commons-lang:jar:4.5:compile"
Line work: "commons-lang:commons-lang:jar:4.5:compile"
Line read: "Line trailing space at end +- "
Line read: "Line with no space at end +-"
Line work: "Line with no space at end +-"
Line read: "[INFO] | | +- !commons-configuration!:commons-configuration!:jar:5.2:compile"
Line work: "!commons-configuration!:commons-configuration!:jar:5.2:compile"
前四个解决方案在单独的命令进程中运行,使用cmd.exe /C
启动命令行并在后台运行:
C:\Windows\System32\findstr.exe /R "+- \\-" dependency-list.txt
这条命令行会在当前目录中搜索名为
dependency-list.txt
的文件,该目录可能与批处理文件所在的目录不同,并使用区分大小写的正则表达式查找包含
+-
或
\-
的行。
第五种解决方案在单独的命令进程中运行命令行:
C:\Windows\System32\findstr.exe /L /C:+- /C:"\\-" dependency-list.txt
这个命令行使用区分大小写的文字搜索,查找包含
+-
或
\-
的行。
FINDSTR不会向
STDOUT输出任何内容,如果没有找到包含
+-
或
\-
的行,也不会向
STDERR输出任何内容。
FOR捕获单独命令进程的
STDOUT输出,并逐行处理输出。
空行被
FOR忽略,默认情况下以分号开头的行也会被忽略,因为
eol=;
是默认的行结束字符选项。
FOR默认将行拆分为子字符串(标记),使用普通空格和水平制表符作为字符串定界符,并将第一个空格/制表符分隔的字符串分配给指定的循环变量。
第一种解决方案
第一种解决方案通过使用选项
tokens=*
修改
FOR的行处理行为,从而删除行中所有前导空格/制表符,如果还剩下什么,则将其余部分(包括空格/制表符)分配给指定的循环变量
I
。输出中以
Line read:
开头的行显示了由
FOR处理后分配给循环变量
I
的字符串。
这个字符串被赋值给环境变量
Line
。然后,使用命令
SET进行字符串替换,将行中第一个由连字符和空格组成的字符串之前的所有内容删除,并根据需要使用延迟的环境变量扩展来引用在以
(
开始并以匹配的
)
结束的命令块内定义/修改的环境变量的值。
如果在执行字符串替换后仍有剩余的内容,这部分行将带有
Line work:
输出,以查看字符串替换所做的工作。
这个解决方案很快,但是有缺点,以分号开头的行将被
FOR忽略,并且永久启用延迟环境变量扩展会导致未正确处理包含一个或多个感叹号的行。然而,在文件
dependency-list.txt
中不包含以
;
开头且包含一个或多个
!
的感兴趣行时,这是最好的解决方案。
第二种解决方案
第二种解决方案使用命令FOR将每个非空行(不以分号开头)拆分为两个子字符串。第一个子字符串直到出现一个或多个-
为止分配给指定的循环变量I
,剩余的行分配给下一个循环变量J
,根据ASCII表进行操作。
因此,对于示例行,J
包含带前导空格的感兴趣数据,但不是空字符串。使用命令SET再次获取分配给环境变量Line
的前导空格,最后只得到从第二个字符开始的字符串,即从字符索引1到字符串结尾的字符串。
但是,与其使用延迟扩展,不妨采用不同的技术,通过在命令块中引用刚刚设置的环境变量值,并在两侧使用两个百分号来使用CALL命令强制Windows命令处理器解析命令SET或ECHO执行前的两行。因此,在执行FOR命令之前解析整个命令块时,带有CALL的两行变为:
call set "Line=%Line:~1%"
if defined Line call echo Line work: %Line%
那两行代码在每次循环迭代中都会被解析两次,因为命令
CALL导致在执行
set
和
echo
之前分别用当前字符串替换
%Line:~1%
和
%Line%
而完全不含第一个字符。
由于避免使用延迟扩展,该解决方案可以正确处理包含感叹号的有趣行。
第三个解决方案
第三个解决方案与第二个解决方案类似。它使用第二个FOR从外部FOR分配给循环变量J
的字符串中删除所有前导空格/制表符,并将剩余的字符串分配给循环变量L
,甚至可以是空字符串。
这个解决方案肯定比第二个解决方案更好,因为速度更快。
第四个解决方案
第四个解决方案旨在始终将非空行分配给捕获输出的循环变量I
,独立于第一个字符是哪个字符。通过定义空分隔符列表和忽略以特定字符开头的行来禁用行拆分行为。
第一种方法中,删除连字符和空格左侧的字符串。与之不同的是,在将行分配给环境变量
Line
时禁用了延迟扩展,并且在处理行字符串时仅暂时启用了延迟扩展。
这种解决方案的缺点是循环内的命令
setlocal EnableDelayedExpansion
和
endlocal
做了比仅启用和禁用延迟扩展更多的工作。有关命令
SETLOCAL和
ENDLOCAL的详细信息,请参见
this answer。因此,该解决方案绝对不是所提供解决方案中最快的解决方案之一。
第五种解决方案类似于第四种解决方案,不同之处在于使用从
FOR循环内调用的子例程来避免在处理字符串值时使用延迟扩展的常见技术。
这种解决方案也非常慢,但具有更容易调试批处理文件的重大优势,可在批处理文件中顶部使用
@echo ON
来运行它。
为了理解所使用的命令及其工作原理,请打开命令提示符窗口,执行以下命令,并仔细阅读每个命令显示的所有帮助页面。
call /?
cls /?
del /?
echo /?
endlocal /?
findstr /?
for /?
goto /?
if /?
pause /?
rem /?
set /?
setlocal /?