首页 > 解决方案 > 为什么 cmd findstr 中的这个正则表达式有效?

问题描述

我需要创建一个 cmd 脚本(不知何故我做到了),它从一系列文件中提取一些文本行并将它们放入一个新的 txt 文件中。

源文件是这样的:

%
!
! AAA
!
! ------------------------ SOME TEXT ABCDEFGHIJKLMN --------------------------
!
! BBB
! ----------------------------------------------------------------------------
! T5 PUNTA ø 6.5/9.5~  $ 63~
! ----------------------------------------------------------------------------
! T12 PUNTA ø 2.5~  $ 39~
! ----------------------------------------------------------------------------
! 
! SOME OTHER TEXT
! 
!  1]  ABC
!  2]  DEF
!  3]  ...

OTHER LINE 1
OTHER LINE 2
ETC

%

我需要提取的行是两者之间的行"! ----------------------------------------------------------------------------",在这种情况下,T5 PUNTA ø 6.5/9.5~ $ 63~T12 PUNTA ø 2.5~ $ 39~.

我正在尝试一些正则表达式findstr来匹配!仅在相关行之后的行,这表明搜索结束,直到我(纯属偶然)找到一条与我需要的所有行匹配的指令(运气, 我猜)。

片段是这样的:

@echo off
setlocal enabledelayedexpansion
if exist output.txt ( break > output.txt )
for /r <path> %%g in (<filename>) do (
    ...
    for /f "tokens=* delims= " %%a in (%%g) do (
        echo %%a | findstr /r /c:^\!$ >nul
        if errorlevel 1 (...)
        ) else ( echo %%a >> srcoutput.txt
            ...
        )
    )
)

请专注于指令echo %%a | findstr /r /c:^\!$ >nul。由于我不知道的原因,这仅匹配行T5 PUNTA ø 6.5/9.5~ $ 63~T12 PUNTA ø 2.5~ $ 39~. 这正是我想要的,但我不知道它为什么有效!

有人可以帮我理解为什么这个简单的表达方式^\!$有效吗?在我(错误的)理解中,它应该只在开头和结尾匹配一行!(我已经逃脱,因为否则它不起作用)。

先感谢您

标签: regexbatch-filecmdfindstr

解决方案


实际上是命令行:

echo %%a | findstr /r /c:^\!$ >nul

只返回包含$-character 的行。

这就是一步一步发生的事情:

  • 命令行被解析为(假设%%a保持<expanded text>):

      echo <expanded text> | findstr /r /c:\!$ >nul
    

    因此(未引用的)插入符号 ( ^) 消失了,因为它是cmd;的转义字符 因为\没有特殊含义,所以你可以省略它^

  • 由于启用了延迟扩展(实际上是不必要的),-!符号消失了,因为只有一个,所以命令行变为:

      echo <expanded text> | findstr /r /c:\$ >nul
    
  • \- 符号充当转义字符(尽管特别是对于findstr!),因此-符号$在正则表达式 ( /R) 模式下失去其特殊含义(即将匹配锚定到行尾),因此被视为文字字符;

  • 管道的左侧传递文本<expanded text> (带有尾随SPACE,因为在 之前有一个|),右侧最终$在该文本中搜索文字 - 字符;

您将使用以下命令行获得完全相同的结果:

echo %%a | findstr /C:$ > nul

虽然我宁愿把它写成:

echo(%%a| findstr /C:"$" > nul

避免尾随SPACE并安全地回显任何文本。


对于这项任务,我可能会采用另一种方法(请参阅所有解释性rem说明):

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // Define constants here:
set "_ROOT=D:\Target\Path"        & rem // (path to root directory)
set "_MASK=*.txt"                 & rem // (name or mask of files to process)
set "_SAVE=D:\Path\To\output.txt" & rem // (location of output file)
rem // Gather line-feed character:
(set ^"_LF=^
%= blank line =%
^")
rem // Gather carriage-return character:
for /F %%C in ('copy /Z "%~f0" nul') do set "_CR=%%C"

rem // Open output file only once and write to it:
> "%_SAVE%" (
    rem // Find matching files and loop through them:
    for /R "%_ROOT%" %%F in ("%_MASK%") do (
        rem // Check for file existence (only necessary when a dedicated name is given):
        if exist "%%~F" (
            rem // Store path of current file:
            set "FILE=%%~F"
            rem // Toggle delayed expansion to avoid troubles with `!`:
            setlocal EnableDelayedExpansion
            rem // Remove remaining quotes (only necessary when a dedicated name is given):
            set "FILE=!FILE:"=!
            rem /* Do a multi-line search by `findstr`, which only returns the first line;
            rem    the searched string is:
            rem     # anchored to the beginning of a line,
            rem     # an `!`, a space and a `T`, then
            rem     # some arbitrary text (without line-breaks), then
            rem     # a line-break, then another `!` and a space, then
            rem     # a sequence of one or more `-`,
            rem     # anchored to the end of a line;
            rem    only the portion before the explicit line-break is then returned: */
            findstr /R /C:"^^^! T.*~!_CR!!_LF!^! --*$" "!FILE!"
            endlocal
        )
    )
)

endlocal
exit /B

这并不完全搜索! ---etc. 之间的行,而是搜索两个相邻的行,其中第一行以!++开头并以 结尾,第二行由SPACE++一个或多个 的序列组成。T~!SPACE-

如果输入文件包含 Unix/Linux 样式的换行符而不是 DOS/Windows 样式的换行符,请在脚本!_CR!!_LF!中的findstr搜索字符串中替换为!_LF!.


推荐阅读