为什么在其搜索字符串 return 中包含变量扩展的 `findstr` 在涉及管道时会出现意外结果?

Why does `findstr` with variable expansion in its search string return unexpected results when involved in a pipe?

同时尝试提供 comprehensive answer to the question Why is FindStr returning not-found, I encountered strange behaviour of code that involves a pipe. This is some code based on the original question (to be executed in a ):

rem // Set variable `vData` to literally contain `...;%main%\Programs\Go\Bin`:
set "vData=...;%%main%%\Programs\Go\Bin"
set "main=C:\Main"

echo/%vData%| findstr /I /C:"%%main%%\Programs\Go\Bin"

return 匹配,因此没有回显并且 ErrorLevel 设置为 1

虽然当我逐步完成解析过程时,我得出了相反的结论,但我确实希望匹配和 0ErrorLevel,因为:

  1. 首先,整行被解析并立即(%)展开,因此 %vData% 被展开并且 %% 被 [= 取代20=],导致将要执行以下命令行:

    echo/...;%main%\Programs\Go\Bin| findstr /I /C:"%main%\Programs\Go\Bin"
    
  2. 管道|的每一侧都由cmd /S /D c在自己的新cmd实例中执行,两者运行都在cmd 上下文(影响 %-扩展的处理),导致这些部分:

    • 管道左侧:

      echo/...;C:\Main\Programs\Go\Bin
      
    • 管道右侧:

      findstr /I /C:"C:\Main\Programs\Go\Bin"
      

      findstr 最终使用的搜索字符串是 C:\Main\Programs\Go\Bin 因为 \ 即使在文字搜索模式下也被用作转义字符,/C/L)

如您所见,最终搜索字符串实际上出现在回显字符串中,因此我希望匹配,但命令行没有 return。那么这是怎么回事,我错过了什么?


当我在执行管道命令行之前清除变量 main 时,我得到了预期的结果,这使我得出结论,尽管我有上面的假设,但变量 main 没有扩展(请注意,在 cmd 上下文中, %main% 在变量为空时按字面意思保留)。我说得对吗?


它变得更加混乱:当我将管道的右侧放在括号之间时,一个匹配项是 returned,与变量 main 是否定义无关:

echo/%vData%| (findstr /I /C:"%%main%%\Programs\Go\Bin")

谁能解释一下?这是否与 findstr 是一个外部命令而不是 echo 这一事实有关? (我的意思是,break | echo/%vData% 扩展了 main 的值,如果定义...)

我将您的示例简化为:

@echo off

set "main=abc"
break | findstr /c:"111" %%main%%
break | echo findstr /c:"222" %%main%%

输出为:

FINDSTR: %main% kann nicht geöffnet werden.
findstr /c:"222" abc

这证明在管道中使用 exe 文件会导致与使用内部批处理命令不同的行为。
仅为内部命令创建一个新的 cmd.exe 实例。
这也是 findstr 不扩展百分号的原因。

这条令人困惑的行展开了,因为括号强制了一个新的 cmd.exe 实例。

break | (findstr /c:"111" %%main%%)

我将在 5.3 管道修改解释 - Windows 命令解释器 (CMD.EXE) 如何解析脚本?

在 CMD 脚本中更改内容。

我对原因的粗略理解:

批次的行为符合预期,即使在管道的另一侧也不需要将 main 上的百分比加倍,因为没有发生扩展,它是一个正常变量。

IE 你写的操作顺序是正确的,但也在每一行上单独发生,我认为你太接近你没有注意到的问题或者我不理解这个问题?

我不确定是否明确说明了这一点,所以我打算逐步回顾一下我认为正在发生的事情:

set "vData=...;%%main%%\Programs\Go\Bin"

内存中存储的结果:“...;%main%\Programs\Go\Bin

ECHOing %vData% 产生:“...;%main%\Programs\Go\Bin

在主目录中添加

set "main=C:\Main"

内存中存储的结果:“C:\Main

ECHOing %main% 产生:“C:\Main

ECHO'%%main%%' 产生:“%C:\Main%

既然定义了 Main,就可以再次回显 vData:

Result Stored in Memory is still: "...;%main%\Programs\Go\Bin"

ECHOing "%vData%" Yields: "...;%main%\Programs\Go\Bin"

CALL ECHOing "%vData%" Yields: "...;C:\Main\Programs\Go\Bin"

所以这边依赖于扩展,但 FindStr 边不是,因为 %main% 已经定义,所以它在第一次通过时被扩展。

当被CMD解释器解析时,我相信顺序如下:

Given your original Find String:

echo/%vData%| findstr /I /C:"%%main%%\Programs\Go\Bin"

变为:

echo/...;%main%\Programs\Go\Bin| findstr /I /C:"%C:\Main%\Programs\Go\Bin"

变为:

echo/...;C:\Main\Programs\Go\Bin | (findstr /I /C:"%C:\Main%\Programs\Go\Bin")

或者,当被CMD解释器解析时,我相信顺序如下:

Given the modified Regex String:

echo/%vData%| findstr /I /C:"%main%\Programs\Go\Bin"

Becomes:

echo/...;%main%\Programs\Go\Bin| findstr /I /C:"C:\main\Programs\Go\Bin"

Becomes:

echo/...;C:\Main\Programs\Go\Bin | (findstr /I /C:"C:\Main\Programs\Go\Bin")

如果您担心的是您实际上将 %%Main%% 存储在变量中,请说 %_Regex% 变量:

然后根据我的经验,通常您必须将管道另一侧的语句用括号括起来或调用 findStr。

IE:

@(
  SETLOCAL
  ECHO OFF
)
rem // Set variable `vData` to literally contain `...;%main%\Programs\Go\Bin`:
set "vData=...;%%main%%\Programs\Go\Bin"
rem // Set variable `_Regex` to literally contain `%main%\Programs\Go\Bin`:
SET "_Regex=%%main%%\Programs\Go\Bin"
rem // Set variable `_Regex` to literally contain `C:\Main`:
set "main=C:\Main"

ECHO(&ECHO(Variable Contents After Main Set:&ECHO(
     echo(Normal:   vData = "%vData%"
     echo(Normal:  _Regex = "%_Regex%"
     echo(Normal:    main = "%main%"
CALL echo(CALLed:   vData = "%vData%"
CALL echo(CALLed:  _Regex = "%_Regex%"


ECHO(&ECHO(Testing the Results of The FindString Methods:
ECHO(==============================================&ECHO(
ECHO( Original ^%%^%%main^%%^%% Method:
echo/%vData%| findstr /I /C:"%%main%%\Programs\Go\Bin"
ECHO(==============================================&ECHO(
ECHO( Using the ^%%_Regex^%% Stored Variable:
echo/%vData%| findstr /I /C:"%_Regex%"
ECHO(==============================================&ECHO(
ECHO( Using just ^%%main^%%:
echo/%vData%| findstr /I /C:"%main%\Programs\Go\Bin"
ECHO(==============================================&ECHO(
ECHO( Using the CALL ^%%_Regex^%% Stored Variable:
echo/%vData%| CALL findstr /I /C:"%_Regex%"
ECHO(==============================================&ECHO(
ECHO( Using the (^%%_Regex^%%) Stored Variable:
echo/%vData%| ( findstr /I /C:"%_Regex%" )
ECHO(==============================================&ECHO(

结果:

C:\Admin>C:\Admin\TestFindStr.cmd

Variable Contents After Main Set:

Normal:   vData = "...;%main%\Programs\Go\Bin"
Normal:  _Regex = "%main%\Programs\Go\Bin"
Normal:    main = "C:\Main"
CALLed:   vData = "...;C:\Main\Programs\Go\Bin"
CALLed:  _Regex = "C:\Main\Programs\Go\Bin"

Testing the Results of The FindString Methods:
==============================================

 Original %%main%% Method:
==============================================

 Using the %_Regex% Stored Variable:
==============================================

 Using just %main%:
...;C:\Main\Programs\Go\Bin
==============================================

 Using the CALL %_Regex% Stored Variable:
...;C:\Main\Programs\Go\Bin
==============================================

 Using the (%_Regex%) Stored Variable:
...;C:\Main\Programs\Go\Bin
==============================================

鉴于我对 CMD 的经验,这也是我通常期望的。