如何在 for 循环中处理路径名中的右括号?

Posted

技术标签:

【中文标题】如何在 for 循环中处理路径名中的右括号?【英文标题】:How to handle a closing parenthesis in path names in a for loop? 【发布时间】:2012-01-01 15:18:48 【问题描述】:

我必须在 for /f 循环中运行的程序有一个长路径名,其中包括右括号“)”,我需要从中解析输出:

for /f "tokens=1" %%G in ('"C:\Documents and Settings\myaccount\Desktop\Test_release (x86)\program.exe" list') do (echo Will do something with %%G)

...其中 'list' 是传递给我的程序的参数。我收到错误“'C:\Documents' 未被识别为内部或外部命令、可运行程序或批处理文件。”

我知道问题是右括号实际上关闭了“for”块,所以结尾的双引号没有“看到”,所以长路径名没有包含在 double引用了。我不明白的是为什么会发生这种情况,因为我的路径用双引号括起来?我还尝试了 usebackq 选项:

for /f "usebackq tokens=1" %%G in (`"C:\Documents and Settings\myaccount\Desktop\Test_release (x86)\program.exe" list`) do (echo Will do something with %%G)

...没有更好的结果。我试图像这样“^)”或像这样“^^)”那样逃跑,无事可做。尝试加倍双引号:

for /f "tokens=1" %%G in ('""C:\Documents and Settings\myaccount\Desktop\Test_release (x86)\program.exe"" list') do (echo Will do something with %%G)

还是不行。

此外,我实际上使用了一个保存路径的变量,该变量事先不知道(从 %CD% 构建),并且激活了 EnableDelayedExpansion。我尝试了延迟扩展(在其他情况下确实解决了类似问题)以防止变量在读取时扩展并在执行时延迟它:

setlocal EnableDelayedExpansion
set _var=%CD%\program.exe
@REM _var now contains C:\Documents and Settings\myaccount\Desktop\Test_release (x86)\program.exe
for /f "tokens=1" %%G in ('"!_var!" list') do (echo %%G)
endlocal

还是不行,不明白为什么。

但是,在上面的代码中加倍双引号并延迟扩展:

for /f "tokens=1" %%G in ('""!_var!"" list') do (echo %%G)

确实有效!...为什么...为什么必须这样做???它有什么作用?我不明白。我也担心它可能会在某些特定情况下导致问题...

有什么想法吗?

【问题讨论】:

对不起。你写道你试图逃避括号。你确定你是这样用的吗?:for /f "tokens=1" %%G in ('"C:\Documents and Settings\myaccount\Desktop\Test_release ^(x86^)\program.exe" list') do (echo Will do something with %%G) 是的,我按照我的第二个示例下面提到的那样尝试了它,但没有任何成功。 【参考方案1】:

此问题的答案中的评论表明 XP 提供的行为与较新的 Windows 版本不同。

XP 中有一个已知的 FOR /F 错误:http://www.dostips.com/forum/viewtopic.php?p=9062#p9062。但是这个问题与那个bug无关。

实际问题源于 FOR /F 如何在 IN() 子句中执行命令。它使用CMD \C command(见How does the Windows Command Interpreter (CMD.EXE) parse scripts?)

您可以通过将此行添加到 Aacini 的 PROG.BAT 示例来观察此行为。

echo cmdcmdline=%cmdcmdline%

下一个问题涉及 CMD 如何处理 /C 命令中出现的引号,以及为什么 XP 的行为与最近的 Windows 版本不同。

此命令在 XP 中失败,但在 Vista 及更高版本中成功:

for /f "delims=" %a in ('"test (this)\prog" args') do @echo %a

FOR 尝试执行的命令 (%cmdcmdline%) 在两个版本中都是相同的(忽略 %COMSPEC% 的差异):

C:\Windows\system32\cmd.exe /c "test (this)\prog" args

XP 在处理引号方面存在 CMD 设计缺陷。该缺陷甚至被记录在案(但它不被视为缺陷)。 Vista 及更高版本部分修复了设计缺陷,但无需费心更正文档。

这是 HELP CMD 的摘录

If /C or /K is specified, then the remainder of the command line after
the switch is processed as a command line, where the following logic is
used to process quote (") characters:

    1.  If all of the following conditions are met, then quote characters
        on the command line are preserved:

        - no /S switch
        - exactly two quote characters
        - no special characters between the two quote characters,
          where special is one of: &<>()@^|
        - there are one or more whitespace characters between the
          two quote characters
        - the string between the two quote characters is the name
          of an executable file.

    2.  Otherwise, old behavior is to see if the first character is
        a quote character and if so, strip the leading character and
        remove the last quote character on the command line, preserving
        any text after the last quote character.

我们希望 CMD 遵循规则 1 以便保留引号,但 () 违反了 XP 上的特殊字符约束,因此遵循规则 2 并且 CMD 尝试执行

test (this)\prog args

失败的原因应该很明显了!

我想不出规则 1 中存在特殊字符约束的任何原因。它违背了 MS 试图做的全部目的。

显然,设计缺陷在 Vista 及更高版本中已部分修复,但他们尚未更新 HELP 文档。 Vista 会忽略特殊字符 () 并使用规则 1 处理命令,保留引号,一切正常。

2015-05-17 更新: 不幸的是,Vista 及更高版本仍将 @^&amp; 视为特殊字符,即使它们是文件名中的有效字符。当然&lt;&gt;| 被视为特殊字符,但无论如何它们在文件名中都无效。因此,对于 Vista 及更高版本,规则 1 的文档应为 where special is one of: &amp;&lt;&gt;@^|

我追踪了大家记录的行为,和上面的都是一致的。

有一种方法可以在 XP 上执行命令而无需使用延迟扩展变量,并且它与 Vista 及更高版本兼容。

for /f "delims=" %a in ('^""test (this)\prog" args^"') do @echo %a

开始和结束引号被转义,以便) 不会干扰 FOR 解析器。为 IN() 子句执行的命令是

C:\Windows\system32\cmd.exe /c ""test (this)\prog" args"

XP和Vista都遵循规则2,因为有两个以上的引号,所以CMD执行

"test (this)\prog" args

一切正常!

此答案的其余部分已过时,但保留下来以提供现有 cmets 的上下文。


您的第一个代码示例应该可以工作;它不能(不应该)给出你描述的错误信息。错误消息在第一个空格处断开了路径,这意味着该路径没有被引用或转义。但你“确定”它被引用了。

问题的关键在于帖子末尾附近的三条信息:

    实际上使用了延迟扩展的变量

    这不起作用:for /f "tokens=1" %%G in ('"!_var!" list') do (echo %%G)

    这可行:for /f "tokens=1" %%G in ('""!_var!"" list') do (echo %%G)

如果 var 的值已经被引用,你会得到你所描述的行为。

您的 var 的值必须是 "C:\Documents and Settings\myaccount\Desktop\Test_release (x86)\program.exe",包括引号。

为了使这个解释更易读,我将缩短到"test (this)\prog.exe"的路径

"!var!" 失败,因为它扩展为""test (this)\prog.exe"",这实际上取消了路径的引用。字符串有三个区域,两个被引用,一个在中间没有:

"空引用区域"未引用路径"空引用区域"

""!var!"" 有效,因为它扩展为 """test (this)\prog.exe""" 并且现在再次引用路径。现在字符串中有五个区域:

"空引用区域"空未引用区域"引用路径"空未引用区域"空引用区域"

关于您应该如何进行的简单答案:

如果 var 的值已被引用,则只需使用 !var! Edit- 这在 XP 上不起作用:“!var!”适用于两者

如果没有引用 var 的值,则使用 "!var!" Edit- 这在 XP 上不起作用:""!var!"" 对两者都有效

【讨论】:

谢谢dbenham。正确引用是我所做的基本验证的一部分。如果我在这里发帖,那是因为我也认为它应该可以工作,但事实并非如此。如果你说这很有效: for /f "tokens=*" %%G in ('"D:\Test folder (this)\bin\program.exe"') do (echo Hi there) 那我为什么有这个错误:D:\Test 文件夹 (this)>for /F "tokens=*" %G in ('"D:\Test folder (this)\bin\program.exe"') do (echo Hi there)' D:\Test' 不是内部或外部命令、可运行程序或批处理文件。再次感谢您的帮助! @philz ???该声明对我有用。您使用的是什么操作系统?我一直在 Vista 上进行测试。 @philz 根据您在 Aacini 的回答下的 cmets,听起来您同意我的回答。如果是这样,请将此答案作为解决方案。太糟糕了,你的评论在那里而不是在这里。我认为我在放置一些 cmets 时一开始就走错了路。 复制我在 Aacini 下的评论: 哇!你是我的男人!对了!我已经设法检测到操作系统版本并在 XP 上执行一种语法(使用双引号和延迟扩展),在 Vista 及更高版本上执行另一种语法(“标准”语法),但我会使用你的,因为它更优雅并且适用到任何操作系统版本。两个竖起大拇指!再次感谢!【参考方案2】:

我进行了一些测试,以找出您最初使用的直接方法不起作用的原因。我首先创建了一个名为test (this) 的目录,并在其中创建了一个名为prog.bat 的批处理文件:

>dir /b
test (this)
>dir /b "test (this)"
prog.bat
>type "test (this)\prog.bat"
@echo off
echo/One - Line one of prog
echo/Two - Line two of prog
echo/Three - Line three of prog

然后我首先尝试使用for /f 访问此类文件的内容。请注意,useback 选项是必需的,因为文件名包含空格并且必须用引号引起来:

>for /f "usebackq tokens=1" %G in ("test (this)\prog.bat") do @echo %G
@echo
echo/One
echo/Two
echo/Three

之前的结果证明路径名是正确的。现在,执行批处理文件而不是读取它,只需将其名称用反引号括起来,对吗?

>for /f "usebackq tokens=1" %G in (`"test (this)\prog.bat"`) do @echo %G
'test' is not recognized as an internal or external command,
operable program or batch file.

然后,因为消息说test是找不到命令的名称,所以我创建了一个名为test.bat的批处理文件:

>type test.bat
@echo off
echo Test.batFileLine1
echo Test.batFileLine2
echo Test.batFileLine2

>for /f "usebackq tokens=1" %G in (`"test (this)\prog.bat"`) do @echo %G
Test.batFileLine1
Test.batFileLine2
Test.batFileLine2

啊哈!之前的结果表明,在这种情况下,引号被忽略,只执行test (this)\prog.bat,对吧?

>for /f "usebackq tokens=1" %G in (`test (this)\prog.bat`) do @echo %G
\prog.bat`) was unexpected at this time.

现在呢?好吧,现在的问题与括号有关:

>for /f "usebackq tokens=1" %G in (`test ^(this^)\prog.bat`) do @echo %G
Test.batFileLine1
Test.batFileLine2
Test.batFileLine2

我的结论是,当反引号和引号与“usebackq”选项结合使用并且文件名有空格时,for /f 命令会出错,并且如果使用延迟的变量扩展,则会忽略此错误.

【讨论】:

Aacini,请注意,您最后的结果显示了您的第二个批处理文件的内容,名为“test.bat”。实际上,您使用括号转义的最后一个示例不起作用。只需删除你的“test.bat”,你就会看到。 我真的很困惑。我测试了使用和不使用 USEBACKQ 选项的批处理文件的执行情况,并且在我的 Vista 机器上一切正常。你在什么操作系统上测试? 我正在开发 32 位 XP SP3。我刚刚在 Win 7 64bits 上测试过,它确实有效!!完全相同的脚本!我不需要使用 usebackq 选项,只需使用单引号内的直接双引号字符串/路径,也不需要转义。它是一个 [未记录] XP 错误吗?我能够使其在 XP 上运行的唯一方法是使用延迟扩展,使用双引号(带单引号),就像我原来的帖子一样。 @philz 我更新了我的答案,引用了一个已知的 XP FOR /F 错误。我希望今晚(美国东海岸)在 XP 上运行一些测试 @philz 我进行了一些测试,我想我知道发生了什么,以及如何解决它。查看我编辑的答案。

以上是关于如何在 for 循环中处理路径名中的右括号?的主要内容,如果未能解决你的问题,请参考以下文章

批处理中如何在for循环中使用 find命令 ,尤其是引用相对路径

批处理实现批量创建快捷方式

如何在批处理文件中的for循环中使用可变长度的子字符串?

如何打破批处理文件中的 FOR 循环?

如何在从列表更新路径的for循环中读取多个json? [复制]

批处理:如何在 FOR 循环中附加字符串