Windows命令解释器(CMD.EXE)如何解析脚本?
Windows命令解释器(CMD.EXE)如何解析脚本?
我发现了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=a
,echo %a %b% c%
→%a a c%
set a =b
,echo %a %b% c%
→bb% c%
- 如何确保一个变量作为单个参数传递给命令,如果这个变量包含双引号?
- 当使用
set
命令时,变量存储在哪里?例如,如果我执行set a=a\" b
,然后执行echo.%a%
,我会得到a\" b
。但如果我使用UnxUtils的echo.exe
,我会得到a b
。为什么%a%
以不同的方式展开?
从命令窗口调用命令时,命令行参数的分词并不是由cmd.exe
(也称为“shell”)完成的。大多数情况下,分词是由新形成的进程的C/C++运行时完成的,但这并不一定是这样的情况——例如,如果新进程不是用C/C++编写的,或者如果新进程选择忽略argv
并为自己处理原始命令行(例如使用GetCommandLine()),那么分词将不会被完成。在操作系统级别上,Windows将命令行作为未标记为单个字符串传递给新的进程。这与大多数*nix shell不同,在这些shell中,在将参数传递给新形成的进程之前,shell会以一致、可预测的方式对参数进行分词。所有这些意味着,您可能会在Windows上的不同程序之间经历完全不同的参数分词行为,因为个别程序通常会将参数分词交给自己处理。
如果听起来像混乱,它有点像。然而,由于大量的Windows程序确实利用了Microsoft C/C++运行时的argv
,因此了解MSVCRT如何分词参数可能是有用的。这里摘录了一部分:
- 参数由空格或制表符分隔。
- 由双引号括起来的字符串将被解释为单个参数,不考虑其中包含的空格。引用的字符串可以嵌入到参数中。请注意,插入符(^)不被认为是转义字符或分隔符。
- 由反斜杠后跟的双引号 (\") 解释为文字双引号 (")。
- 反斜杠按字面意义解释,除非它们紧接着一个双引号。
- 如果偶数的反斜杠后跟一个双引号,则在argv数组中为每一对反斜杠()放置一个反斜杠(\),并将双引号(")解释为字符串分隔符。
- 如果奇数的反斜杠后面跟着一个双引号,则在argv数组中为每一对反斜杠(\)放置一个反斜杠(\),并且剩余的反斜杠将解释为一个转义序列,从而导致将一个文字双引号(")放置在argv中。
Microsoft“批处理语言”(.bat
)是这种无政府状态的环境中的一个例外,它已经发展出自己独特的分词和转义规则。看起来cmd.exe的命令提示符确实对命令行参数进行了一些预处理(主要是变量替换和转义),然后将参数传递给新执行的进程。您可以在此页面的jeb和dbenham的优秀答案中了解有关批处理语言和cmd转义的低级详细信息。
让我们在C语言中构建一个简单的命令行实用程序,看看它对您的测试用例有何反应:
int main(int argc, char* argv[]) { int i; for (i = 0; i < argc; i++) { printf("argv[%d][%s]\n", i, argv[i]); } return 0; }
(注:argv[0]始终是可执行文件的名称,在下文中为简洁起见省略。在Windows XP SP3上测试。使用Visual Studio 2005编译。)
> 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]
以下是我自己的一些测试:
> test.exe a "b" c argv[1][a] argv[2][b] argv[3][c] > test.exe a "b c" "d e argv[1][a] argv[2][b c] argv[3][d e] > test.exe a \"b\" c argv[1][a] argv[2]["b"] argv[3][c]