Windows命令解释器(CMD.EXE)如何解析脚本?

时间:2010-11-04 07:38:57

标签: windows parsing batch-file cmd variable-expansion

我遇到了ss64.com,它为如何编写Windows命令解释器将运行的批处理脚本提供了很好的帮助。

但是,我无法找到批处理脚本的语法,扩展或不扩展的方法以及如何逃避事情的良好解释。

以下是我无法解决的示例问题:

  • 如何管理报价系统?我制作了一个TinyPerl脚本
    foreach $i (@ARGV) { print '*' . $i ; }),编译并以这种方式调用它:
    • my_script.exe "a ""b"" c"→输出为*a "b*c
    • my_script.exe """a b c"""→输出*"a*b*c"
  • 内部echo命令如何工作?在该命令中扩展了什么?
  • 为什么我必须在文件脚本中使用for [...] %%I,但在交互式会话中使用for [...] %I
  • 什么是转义字符,以及在什么情况下?如何逃脱百分号?例如,我如何从字面上回显%PROCESSOR_ARCHITECTURE%?我发现echo.exe %""PROCESSOR_ARCHITECTURE%有效,是否有更好的解决方案?
  • %对如何匹配?例:
    • set b=aecho %a %b% c%%a a c%
    • set a =becho %a %b% c%bb c%
  • 如果变量包含双引号,如何确保变量作为单个参数传递给命令?
  • 使用set命令时如何存储变量?例如,如果我set a=a" b然后echo.%a%,我会获得a" b。但是,如果我使用UnxUtils中的echo.exe,我会获得a b%a%如何以不同的方式扩展?

谢谢你的灯光。

8 个答案:

答案 0 :(得分:164)

答案 1 :(得分:59)

从命令窗口调用命令时,cmd.exe(a.k.a。“shell”)不会对命令行参数进行标记化。大多数情况下,标记化是由新形成的进程的C / C ++运行时完成的,但这不一定是这样 - 例如,如果新进程不是用C / C ++编写的,或者新进程选择忽略{{ 1}}并为自己处理原始命令行(例如使用GetCommandLine())。在操作系统级别,Windows将未命名的命令行作为单个字符串传递给新进程。这与大多数* nix shell形成对比,其中shell在将参数传递给新形成的进程之前以一致,可预测的方式对参数进行标记。所有这些意味着您可能会在Windows上的不同程序中遇到极为不同的参数标记化行为,因为单个程序通常会将参数标记化放在自己手中。

如果它听起来像无政府状态,那就是。但是,由于大量Windows程序 使用Microsoft C / C ++运行时的argv,因此理解how the MSVCRT tokenizes参数通常很有用。这是一段摘录:

  • 参数由空格分隔,可以是空格或制表符。
  • 由双引号括起的字符串被解释为单个参数,而不管其中包含的空格。带引号的字符串可以嵌入参数中。请注意,插入符号(^)不会被识别为转义字符或分隔符。
  • 以反斜杠开头的双引号“”被解释为文字双引号(“)。
  • 反斜杠按字面解释,除非它们紧跟在双引号之前。
  • 如果偶数个反斜杠后面跟一个双引号,那么每个反斜杠(\)对argv数组放一个反斜杠(),双引号(“)被解释为一个字符串分隔符。
  • 如果奇数个反斜杠后面跟一个双引号,那么每个反斜杠(\)都会在argv数组中放入一个反斜杠(),其余的双引号会被解释为转义序列反斜杠,导致文字双引号(“)放在argv中。

Microsoft“批处理语言”(argv)对于这种无政府环境也不例外,它已经为标记化和转义开发了自己独特的规则。在将参数传递给新执行的进程之前,它看起来像cmd.exe的命令提示符确实对命令行参数进行了一些预处理(主要用于变量替换和转义)。您可以在本页的jeb和dbenham的优秀答案中阅读有关批处理语言和cmd转义的低级详细信息的更多信息。


让我们在C中构建一个简单的命令行实用程序,并查看它对您的测试用例的描述:

.bat

(注意:argv [0]始终是可执行文件的名称,为简洁起见,在下面省略。在Windows XP SP3上测试。使用Visual Studio 2005编译。)

int main(int argc, char* argv[]) {
    int i;
    for (i = 0; i < argc; i++) {
        printf("argv[%d][%s]\n", i, argv[i]);
    }
    return 0;
}

我自己的一些测试:

> test.exe "a ""b"" c"
argv[1][a "b" c]

> test.exe """a b c"""
argv[1]["a b c"]

> test.exe "a"" b c
argv[1][a" b c]

答案 2 :(得分:43)

答案 3 :(得分:7)

正如所指出的那样,命令在μSoftland中传递整个参数字符串,由它们将它解析为单独的参数供自己使用。在不同的程序之间没有任何一致性,因此没有一套规则来描述这个过程。你真的需要检查你的程序使用的任何C库的每个角落案例。

就系统.bat文件而言,这是测试:

c> type args.cmd
@echo off
echo cmdcmdline:[%cmdcmdline%]
echo 0:[%0]
echo *:[%*]
set allargs=%*
if not defined allargs goto :eof
setlocal
@rem Wot about a nice for loop?
@rem Then we are in the land of delayedexpansion, !n!, call, etc.
@rem Plays havoc with args like %t%, a"b etc. ugh!
set n=1
:loop
    echo %n%:[%1]
    set /a n+=1
    shift
    set param=%1
    if defined param goto :loop
endlocal

现在我们可以进行一些测试了。看看你是否可以弄清楚μSoft正在尝试做什么:

C>args a b c
cmdcmdline:[cmd.exe ]
0:[args]
*:[a b c]
1:[a]
2:[b]
3:[c]

到目前为止很好。 (从现在开始,我将忽略无趣的%cmdcmdline%%0。)

C>args *.*
*:[*.*]
1:[*.*]

没有文件名扩展。

C>args "a b" c
*:["a b" c]
1:["a b"]
2:[c]

没有报价剥离,但报价确实阻止了参数拆分。

c>args ""a b" c
*:[""a b" c]
1:[""a]
2:[b" c]

连续双引号会导致它们失去任何特殊的解析能力。 @Beniot的例子:

C>args "a """ b "" c"""
*:["a """ b "" c"""]
1:["a """]
2:[b]
3:[""]
4:[c"""]

测验:如何将任何环境var的值作为单个参数(即%1)传递给bat文件?

c>set t=a "b c
c>set t
t=a "b c
c>args %t%
1:[a]
2:["b c]
c>args "%t%"
1:["a "b]
2:[c"]
c>Aaaaaargh!

Sane解析似乎永远破碎了。

为了您的娱乐,请尝试在这些示例中添加其他^\'&(&amp; c。)字符。

答案 4 :(得分:5)

上面你已经有了一些很好的答案,但要回答你问题的一部分:

set a =b, echo %a %b% c% → bb c%

正在发生的事情是因为你在=之前有一个空格,所以创建了一个名为%a<space>%的变量 因此,当echo %a %被正确评估为b

然后将剩余部分b% c%评估为纯文本+未定义变量% c%,应该按类型回显,对我来说echo %a %b% c%返回bb% c%

我怀疑在变量名称中包含空格的能力更多的是疏忽而不是计划的特征&#39;

答案 5 :(得分:1)

FOR-循环元变量扩展

这是accepted answer(适用于批处理文件模式和命令行模式)中 第4阶段 的扩展说明。当然,for命令必须处于活动状态。下面描述do子句之后的命令行部分的处理。请注意,在批处理文件模式下,由于上述%%的立即扩展阶段(阶段1)),%已转换为%

  • 从左至行末扫描%-符号;如果找到一个,则:
    • 如果启用了Command Extensions(默认),请检查下一个字符是否为~;如果是,则:
      • 在不区分大小写的集合fdpnxsatz中尽可能多地使用以下字符(每个字符甚至多次),这些字符位于定义for变量引用或{{1}的字符之前}-标志;如果遇到这样的$符号,则:
        • 扫描$ 1 ;如果找到,则:
          • 如果:之后有一个字符,请将其用作:变量引用,并按预期方式扩展,除非未定义,否则不要扩展并在该字符位置继续扫描;
          • 如果for是最后一个字符,则 :将崩溃!
        • 其他(未找到cmd.exe)不会展开任何内容;
      • 否则(如果未遇到:-符号)使用所有修饰符扩展$变量,除非未定义,否则不要扩展并在该字符位置继续扫描;
    • 否则(如果未找到for或命令扩展名被禁用),请检查下一个字符:
      • 如果没有更多可用字符,请勿展开任何内容;
      • 如果下一个字符是~,请不要展开任何内容,并在该字符位置 2 ;
      • 返回扫描的开始
      • 否则将下一个字符用作%变量引用并扩展,除非未定义,否则不要扩展;
  • 在下一个字符位置返回扫描的开始(只要仍有可用的字符);

1)for$之间的字符串被认为是环境变量的名称,它甚至可以为空。由于环境变量不能使用空名称,因此其行为与未定义的环境变量相同。
2)这意味着名为:的{​​{1}}元变量如果没有for修饰符就不能扩展。


原始来源:How to safely echo FOR variable %%~p followed by a string literal

答案 6 :(得分:0)

编辑:看到接受的答案,后面的内容是错误的,只解释了如何将命令行传递给TinyPerl。


关于引号,我觉得行为如下:

  • 当找到"时,字符串通配开始
  • 发生字符串通配时
    • 不是"的每个字符都是全球化的
    • 找到"时:
      • 如果后跟""(因此为三"),则会在字符串中添加双引号
      • 如果后跟"(因此是一个双"),则会在字符串中添加双引号并将字符串全局结束
      • 如果下一个字符不是",则字符串通道结束
    • 当行结束时,字符串globbing结束。

简而言之:

"a """ b "" c"""由两个字符串组成:a " b "c"

如果在行尾,

"a"""a""""a""""都是相同的字符串

答案 7 :(得分:0)

请注意,Microsoft已发布其终端的源代码。就语法分析而言,它可能类似于命令行。也许有人有兴趣根据终端的解析规则测试反向工程解析规则。

Link到源代码。