【问题标题】:Strange behavior with special characters in arguments of batch functions?批处理函数的参数中带有特殊字符的奇怪行为?
【发布时间】:2012-10-01 00:59:27
【问题描述】:

假设你运行test.bat "blabla,blabla,^>blabla", "blaby"

test.bat 实现:

@SETLOCAL
@ECHO OFF
SET list=%~1
ECHO "LIST: %list%"
ECHO "ARG 1: %~1"
ECHO "ARG 2: %~2"
@ENDLOCAL   
@GOTO :EOF

输出如预期:

"LIST: blabla,blabla,>blabla"
"ARG 1: blabla,blabla,^>blabla"
"ARG 2: blaby"

但是,如果您在批处理文件中创建 test.bat 函数会怎样:

@SETLOCAL
CALL :TEST "blabla,blabla,^>blabla", "blaby"
@ENDLOCAL
@GOTO :EOF

:TEST
@SETLOCAL
@ECHO OFF
SET list=%~1
ECHO "LIST: %list%"
ECHO "ARG 1: %~1"
ECHO "ARG 2: %~2"
@ENDLOCAL   
@GOTO :EOF

运行后输出为:

"LIST: blabla,blabla,^"
"ARG 1: blabla,blabla,^^>blabla"
"ARG 2: blaby"

嗯?

  1. blabla 在 LIST 中的哪个位置?
  2. ARG 1 有^^?为什么?

有人能解释一下特殊字符在函数参数和命令行参数中的行为有何不同吗?

【问题讨论】:

    标签: windows batch-file cmd


    【解决方案1】:

    您只需使用第一个批处理脚本即可获得相同的结果:

    call test.bat "blabla,blabla,^>blabla", "blaby"
    

    您的问题源于批处理如何解析 CALL 语句的一个不幸方面。它在第 6 阶段在How does the Windows Command Interpreter (CMD.EXE) parse scripts? 中进行了描述。

    哎呀-我以为我以前理解插入符号加倍,但显然不是。为了回应 jeb 的 cmets,我对以下讨论进行了大量编辑。

    CMD.EXE 的设计者想要像call echo ^^ 这样的语句来得到与echo ^^ 相同的结果。这两个语句在处理特殊字符的阶段 2 中将 ^^ 减少到 ^。但是 CALL 语句必须第二次经过阶段 1 和阶段 2。所以在幕后,当 CMD.EXE 在第 6 阶段识别 CALL 语句时,它将剩余的插入符号加倍回到^^,然后在第 2 阶段的第二轮将其减少回^。两个语句都在屏幕上回显一个插入符号。

    不幸的是,CMD.EXE 会盲目地将所有插入符号加倍,即使它们被引用。但是引用的插入符号不被视为转义,它是文字。插入符号不再被消耗。很不幸。

    在解析器的第 6 阶段运行 call test.bat "blabla,blabla,^>blabla", "blaby" 变为 call test.bat "blabla,blabla,^^>blabla" "blaby"

    这很容易解释为什么 ARG 1 在您的输出中看起来如此。

    至于blabla去哪里了?,这有点棘手。

    当您的脚本执行 SET list=%~1 时,引号被删除,^^ 被视为转义的插入符号,减少为 ^> 不再转义。因此,您的 SET 语句的输出被重定向到“blabla”文件。当然 SET 没有输出,所以你的硬盘上应该有一个零长度的“blabla”文件。

    编辑 - 如何使用“后期扩展”正确传递所需的参数

    在他的回答中,davor 试图扭转插入符在被调用例程中加倍的效果。但这并不可靠,因为您无法确定插入符号可能被加倍了多少次。最好让呼叫者调整呼叫以进行补偿。这很棘手 - 你必须使用 jeb 所谓的“后期扩展”

    在批处理脚本中,您可以定义一个包含所需参数字符串的变量,然后通过用另一个 % 转义 % 来延迟扩展直到插入符号加倍之后。您需要将语句中每个 CALL 的百分比加倍。

    @echo off
    setlocal
    set arg1="blabla,blabla,^>blabla"
    call :TEST %%arg1%% "blaby"
    echo(
    call call :TEST %%%%arg1%%%% "blaby"
    ::unquoted test
    exit /b
    
    :TEST
    setlocal
    set list=%~1
    echo "LIST: %list%"
    echo "ARG 1: %~1"
    echo "ARG 2: %~2"
    exit /b
    

    以上产生了预期的结果:

    "LIST: blabla,blabla,>blabla"
    "ARG 1: blabla,blabla,^>blabla"
    "ARG 2: blaby"
    
    "LIST: blabla,blabla,>blabla"
    "ARG 1: blabla,blabla,^>blabla"
    "ARG 2: blaby"
    

    从命令行运行时扩展规则不同。从命令行转义 % 是不可能的。相反,您必须在百分比内添加一个插入符号,以防止扩展阶段在第 1 遍识别名称,然后当插入符号在第 2 阶段被剥离时,第 2 遍扩展会正确扩展变量。

    以下使用davor的原TEST.BAT

    C:\test>test.bat "blabla,blabla,^>blabla" "blaby"
    "LIST: blabla,blabla,>blabla"
    "ARG 1: blabla,blabla,^>blabla"
    "ARG 2: blaby"
    
    C:\test>set arg1="blabla,blabla,^>blabla"
    
    C:\test>test.bat %arg1% "blaby"
    "LIST: blabla,blabla,>blabla"
    "ARG 1: blabla,blabla,^>blabla"
    "ARG 2: blaby"
    
    C:\test>call test.bat %^arg1% "blaby"
    "LIST: blabla,blabla,>blabla"
    "ARG 1: blabla,blabla,^>blabla"
    "ARG 2: blaby"
    
    C:\test>set arg2=%^arg1%
    
    C:\test>call call test.bat %^arg2% "blaby"
    "LIST: blabla,blabla,>blabla"
    "ARG 1: blabla,blabla,^>blabla"
    "ARG 2: blaby"
    

    转义的替代方法 - 通过引用传递值!

    总而言之,逃生规则非常复杂。这就是为什么高级批处理脚本通常通过引用而不是文字来传递字符串值。所需的字符串被放置在一个变量中,然后变量的名称作为参数传递。延迟扩展用于获取准确的字符串,而不必担心由于特殊字符或 CALL 插入符号加倍或百分比剥离而导致损坏。

    这里有一个简单的 test.bat 来演示这个概念

    @echo off
    setlocal enableDelayedExpansion
    set "var1=!%~1!"
    echo var1=!var1!
    
    call :test var1
    exit /b
    
    :test
    set "var2=!%~1!"
    echo var2=!var2!
    

    这是它如何工作的演示。

    C:\test>set complicatedString="This & that ^" ^& the other thing ^^ is 100% difficult to escape
    
    C:\test>set complicatedString
    complicatedString="This & that ^" & the other thing ^ is 100% difficult to escape
    
    C:\test>test.bat complicatedString
    var1="This & that ^" & the other thing ^ is 100% difficult to escape
    var2="This & that ^" & the other thing ^ is 100% difficult to escape
    
    C:\test>call test.bat complicatedString
    var1="This & that ^" & the other thing ^ is 100% difficult to escape
    var2="This & that ^" & the other thing ^ is 100% difficult to escape
    
    C:\test>call call test.bat complicatedString
    var1="This & that ^" & the other thing ^ is 100% difficult to escape
    var2="This & that ^" & the other thing ^ is 100% difficult to escape
    

    【讨论】:

    • +1 甚至 'call echo this ^& that' 的部分都是完全错误的 :-)
    • 它不回显任何内容,因为在阶段 2 中,插入符号被消耗。所以后来没有任何插入符号可以加倍,所以只有一个 nake & 和完整的“呼叫回声”失败。只有后期扩展才能在“呼叫回声”中逃避任何字面意思的内容。
    • @jeb - 谢谢 - 我想我已经解决了关于 CMD.EXE 将插入符号加倍的原因的讨论。另一个糟糕的 MS 设计。如果 MS 允许我们在使用 CALL 时根据需要显式地进行双重转义,生活会轻松得多。
    • 感谢您提供链接和解释 dbenham。我觉得这很复杂,可能是因为我还没有掌握批处理的基础知识。难怪过去几天我在这些争论中遇到了这么多麻烦。
    • @dbenham 您提出了一些有用的提示。可惜我只能做+1。谢谢。
    【解决方案2】:

    经过一些测试和 dbenham 的回答后,似乎需要预测双插入符号并将其替换为单插入符号:

    @SETLOCAL
    CALL :TEST "blabla,blabla,^>blabla", "blaby"
    @ENDLOCAL
    @GOTO :EOF
    
    :TEST
    @SETLOCAL
    @ECHO OFF
    SET "list=%~1" & REM CHANGED
    SET "list=%list:^^=^%" & REM ADDED
    ECHO "LIST: %list%"
    ECHO "ARG 1: %~1"
    ECHO "ARG 2: %~2"
    @ENDLOCAL   
    @GOTO :EOF
    

    那么输出是:

    "LIST: blabla,blabla,^>blabla"
    "ARG 1: blabla,blabla,^^>blabla"
    "ARG 2: blaby"
    

    还要注意奇怪的事情: 在SET "list=%list:^^=^%" 中,%% 之间的^^ 被视为两个字符,而不是转义的^

    【讨论】:

    • 只要你知道你的例程将如何被调用,它就可以工作。如果使用call call :test ... 调用它将中断。不太可能,但在极少数情况下需要双重通话。更有问题的是你原来的“TEST.BAT”。它可以使用TEST ...CALL TEST ... 执行。您的脚本无法判断插入符号是否需要修复。我将更新我的答案,以显示如何通过使用 jeb 隐秘引用的“后期扩展”技术修改 CALL 语句来获得所需的结果。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-10
    • 1970-01-01
    • 2014-05-24
    • 2012-04-08
    相关资源
    最近更新 更多