在 Windows Batch 中,为啥用括号括起来的 for 循环不会扩展它被告知要回显的变量?

Posted

技术标签:

【中文标题】在 Windows Batch 中,为啥用括号括起来的 for 循环不会扩展它被告知要回显的变量?【英文标题】:In Windows Batch, why a for loop surrounded by parenthesis won't expand a variable it's told to echo?在 Windows Batch 中,为什么用括号括起来的 for 循环不会扩展它被告知要回显的变量? 【发布时间】:2021-08-22 03:54:11 【问题描述】:

在我的项目中,我需要使用findstr 解析命令的多行输出。为此,我按照this answer 创建了一个行数组,并使用for 循环将它们逐个echo

(for /L %n in (1 1 %info_count%) do echo %info[%n]%)| findstr /R /C:"[0-9s]: [0-9]" /C:"bytes"

问题是这会导致echo 部分输出%info[1]%%info[2]% 等等。

但是,删除第一组括号会导致 for 循环的 do 部分同时解释 echo 和管道部分,这意味着通过一个管道而不是六个 echo 输出,我通过六个不同的管道获得六个(扩展的)echo 输出。

是什么导致了这个问题?

PS:上面的 sn-p 正是我在整个脚本运行后在结果提示中调查的内容。在主项目中,我自然会为我的变量和批处理文件循环内的适当变量使用延迟扩展(即setlocal enabledelayedexpansion!var!%%a


编辑:鉴于jeb's answer,我添加了更长的sn-p,并且我理解问题在于findstr是一个外部exe。

for /f %%d in ('^(for /L %%n in ^(1 1 !info_count!^) do echo !info[%%n]!^)^| ^(findstr /R /C:"[0-9s]: [0-9]" /C:"bytes"^)^| find /c /v ""') do (
    if [%%d]==[0] (
        ...
    ) else (
        ...

其目的是分析 mkvmerge -i 的输出,这是一个已放入数组(!info1!!info2 等)的多行文本,使用 findstr 检测两个所需匹配项之一,然后使用find 输出匹配的数量,这将决定下一行的行为。

我试图通过这一切实现的是避免多次启动 mkvmerge,但我已经有了一个可行的替代方案,它只是再次调用 mkvmerge 而不是 for /l 循环(这意味着双管道正在工作另一种情况)。

This answer,感谢jeb,建议在findstr 周围添加括号可以解决问题。对我来说不是这样。

【问题讨论】:

你必须在由 FOR 控制的变量中使用 double %:FOR /L %%n (....) 否则 %n 会被自己替换,...​​...什么都没有。 @user207421 命令是在命令行执行的,所以符号百分号是正确的 【参考方案1】:

在命令行上测试这个没有用,因为行为与批处理文件不同。

这里的主要问题是百分比扩展。它在解析块时被扩展!

下一个问题是百分比表达式的嵌套不能以这种方式完成。 %info[%n]% 分为%info[%n]%

在批处理文件中,您将使用延迟扩展来避免所有这些问题。

(for /L %%n in (1 1 !info_count!) do echo !info[%%n]!) | findstr /R /C:"[0-9s]: [0-9]" /C:"bytes"

但它仍然失败,因为您正在将一个延迟扩展的块输送到findstr。 管道使用默认配置启动两个子 cmd.exe 进程,然后再次禁用延迟扩展。 Why does delayed expansion fail when inside a piped block of code?

您可以将 findstr 移动到块中,然后将 !info[%%n]! 展开,然后将其传输到新的 cmd.exe 实例。

for /L %%n in (1 1 !info_count!) do (
  echo !info[%%n]! | findstr /R /C:"[0-9s]: [0-9]" /C:"bytes"
)

这会比较慢,因为它会启动 findstr 和每个循环的管道。

在您提供更多信息后:

包围 findstr 在这里没有帮助,因为那不是你的问题。 如果您直接启动 findstr 或在 cmd.exe 实例中启动并不会改变行为,那么只有在管道 findstr 部分中存在百分比扩展时才需要该技巧。

如果您只想计算匹配项,则可以不使用find

set /a match_cnt=0
for /L %%n in (1 1 %info_count%) do (
  echo !info[%%n]! | findstr /R /C:"[0-9s]: [0-9]" /C:"bytes" > nul
  if !errorlevel! == 0 set /a match_cnt+=1
)

echo matches: !match_cnt!

您还可以修改您的初始代码以使用:

( 
    for /L %%n in (1 1 %info_count%) do @(
        cmd /V:on /C echo !info[%%n]!
    )
) | findstr /R /C:"[0-9s]: [0-9]" /C:"bytes"

或者没有cmd /v:on的(缓慢)调用也一样

    call echo %%^^info[%%n]%%

【讨论】:

首先我不认为%info[%n]% is being split, as my testing shows %n%` 扩展了。其次,将findstr 移动到块中会遇到另一个问题,我将不得不编辑我的问题并使用更大的 sn-p 来说明原因。第三,您提出的调查路线使我相信findstr 是一个外部exe 是问题所在。然而,提供的解决方案,将其括起来,并没有帮助。所有这些都非常有帮助,我明天会继续调查,谢谢! @FabioFreitas 您可以在之前使用set info[=WRONG split 测试拆分行为

以上是关于在 Windows Batch 中,为啥用括号括起来的 for 循环不会扩展它被告知要回显的变量?的主要内容,如果未能解决你的问题,请参考以下文章

用括号括住 JavaScript 语句有啥意义?

单/双中括号与测试条件

sql 数据库使用注意事项

为啥将箭头函数体包装在括号中

为啥clang-format在getter大括号之前不中断?

(Spring Batch)为啥表'batch_job_instance'已经存在?