我遇到了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%
可以工作,有更好的解决方案吗? %
对如何匹配?例如:- x一米十一纳米一x一米十二纳米一x → x一米十三纳米一x
- x一米十四纳一x x一米十五纳一x → x一米十六纳一x
- 如果一个变量包含双引号,我如何确保它作为一个参数传递给命令?
- 当使用
set
命令时变量是如何存储的?例如,如果我执行set a=a" b
,然后执行echo.%a%
,我会得到a" b
。但是如果我从UnxUtils使用echo.exe
,我会得到a b
。%a%
如何以不同的方式扩展?
8条答案
按热度按时间ylamdve61#
我们通过实验研究了批处理脚本的语法,也研究了批处理和命令行模式之间的差异。
批处理行解析器:
下面是批处理文件行分析器中各个阶段的简要概述:
@
开头,且ECHO在上一步骤开始时为ON时。%X
变量扩展:**仅当FOR命令处于活动状态且DO之后的命令正在处理时。以下是每个阶段的详细信息:
请注意,下面描述的阶段只是批处理分析器工作方式的一个模型。实际的cmd.exe内部可能不会反映这些阶段。但此模型在预测批处理脚本的行为方面非常有效。
<LF>
读取输入行。<Ctrl-Z>
(0x1A)被读取为<LF>
(LineFeed 0x0A)<Ctrl-Z>
被视为自身-它***不***转换为<LF>
%%
替换为单%
%*
、%1
、%2
等)%var%
,如果var不存在,则将其替换为空<LF>
处被截断,不在%var%
扩展范围内有些概念在整个阶段都很重要。
<space>
<tab>
;
,
=
<0x0B>
<0x0C>
和<0xFF>
连续的令牌分隔符被视为一个-令牌分隔符之间没有空令牌
根据上下文,以下字符在此阶段可能具有特殊含义:x一个米23纳米1 x一个米24纳米1 x一个米25纳米1 x一个米26纳米1 x一个米27纳米1 x一个米28纳米1 x一个米29纳米1 x一个米30纳米1 x一个米31纳米1 x一个米32纳米1 x一个米33纳米1 x一个米34纳米1 x一个米35纳米1 x一个米36纳米1 x一个米37纳米1 x一个米38纳米1 x一个米39纳米1 x
从左到右看每个字符:
如果是
<CR>
,则删除它,就好像它从未存在过一样(除了奇怪的redirection behavior)如果是插入符号(
^
),则对下一个字符进行转义,并删除转义插入符号。转义字符将失去所有特殊含义(<LF>
除外)。如果是引号(
"
),则切换引号标志。如果引号标志处于活动状态,则只有"
和<LF>
是特殊的。所有其他字符将失去其特殊含义,直到下一个引号将引号标志切换为关闭。右引号不能转义。所有带引号的字符始终位于同一标记内。<LF>
总是关闭引号标志。其他行为因上下文而异,但引号永远不会改变<LF>
的行为。逃逸
<LF>
<LF>
已剥离转义下一个字符。如果在行尾缓冲区,则下一行由阶段1和1.5读取和处理,并在转义下一个字符之前附加到当前行。如果下一个字符是
<LF>
,则将其视为文字,这意味着此过程不是递归的。未转义的
<LF>
不在括号内<LF>
被剥离并且终止对当前行的解析。行缓冲区中的任何剩余字符都将被忽略。
带FOR IN括号的块中的未转义
<LF>
将
<LF>
转换为<space>
如果在行缓冲区的末尾,则读取下一行并将其附加到当前行。
带括号的命令块中的未转义
<LF>
<LF>
被转换为<LF><space>
,并且<space>
被视为命令块的下一行的一部分。如果在行缓冲区的末尾,则读取下一行并将其附加到空间。
如果是特殊字符
&
|
<
或>
之一,请在此处拆分行,以便处理管道、命令串联和重定向。在管道(
|
)的情况下,每一端都是一个单独的命令(或命令块),在阶段5.3中进行特殊处理在
&
、&&
或||
命令串联的情况下,串联的每一侧都被视为单独的命令。在
<
、<<
、>
或>>
重定向的情况下,将分析重定向子句,暂时将其删除,然后将其附加到当前命令的末尾。重定向子句由可选的文件句柄数字、重定向运算符和重定向目标标记组成。如果重定向运算符前面的标记是单个未转义的数字,则该数字指定要重定向的文件句柄。如果找不到句柄标记,则输出重定向默认为1(stdout),输入重定向默认为0(stdin)。
如果此命令的第一个标记(在将重定向移到末尾之前)以
@
开头,则@
具有特殊含义。(@
在任何其他上下文中都没有特殊含义)特殊的
@
被删除。如果ECHO为ON,则此命令沿着该行中的任何后续连接命令将从阶段3回显中排除。如果
@
在开始(
之前,则整个括号中的块将从阶段3回显中排除。处理括号(为跨多行的复合语句提供):
如果解析器不寻找命令标记,那么
(
就不是特殊的。如果解析器正在查找命令标记并找到
(
,则启动一个新的复合语句并递增括号计数器如果括号计数器〉0,则
)
终止复合语句并递减括号计数器。如果到达行末并且括号计数器〉0,那么下一行将被附加到复合语句(从阶段0开始)
如果括号计数器为0,并且解析器正在查找命令,则
)
的功能类似于REM
语句,只要它后面紧跟标记分隔符、特殊字符、换行符或文件结束符即可除
^
外,所有特殊字符将失去其含义(行连接是可能的)一旦到达逻辑行的末尾,整个“命令”被丢弃。
每个命令都被解析成一系列的令牌,第一个令牌总是被当作命令令牌(在特殊的
@
被剥离并且重定向被移到末尾之后)。命令标记之前的前导标记分隔符将被删除
解析命令标记时,除了标准标记分隔符之外,
(
还用作命令标记分隔符后续标记的处理取决于命令。
大多数命令只是简单地将命令标记后面的所有参数连接成一个参数标记。所有参数标记分隔符都被保留。参数选项通常直到第7阶段才被解析。
有三个命令需要特殊处理- IF、FOR和REM
IF被分成两个或三个独立处理的不同部分。IF结构中的语法错误将导致致命的语法错误。
比较操作是一直流到阶段7的实际命令
所有IF选项都在阶段2中完全解析。
连续的标记分隔符折叠为一个空格。
根据比较运算符的不同,将标识一个或两个值标记。
True命令块是条件之后的命令集,其分析方式与任何其他命令块类似。如果要使用ELSE,则必须将True块括在括号中。
可选的False命令块是ELSE之后的命令集。同样,该命令块被正常解析。
True和False命令块不会自动进入后续阶段,它们的后续处理由阶段7控制。
FOR在DO之后被一分为二。FOR构造中的语法错误将导致严重的语法错误。
通过DO的部分是贯穿阶段7的实际FOR迭代命令
所有FOR选项都在阶段2中完全解析。
IN括号子句将
<LF>
视为<space>
。分析IN子句后,所有标记将连接在一起以形成单个标记。从FOR命令到DO,连续的未转义/未加引号的标记分隔符将折叠为一个空格。
DO之后的部分是正常解析的命令块,DO命令块的后续处理由阶段7的迭代控制。
在第2阶段检测到的REM的处理方式与所有其他命令的处理方式截然不同。
只解析一个参数标记-解析器忽略第一个参数标记之后的字符。
REM命令可能会出现在阶段3输出中,但该命令永远不会执行,并且原始参数文本会被回显-转义插入符号不会被删除,除了...
如果只有一个参数标记以一个未转义的
^
结尾,那么这个参数标记将被丢弃,随后的行将被解析并追加到REM中,直到有多个标记,或者最后一个字符不是^
。如果命令令牌以
:
开始,并且这是阶段2的第一轮(不是由于阶段6中的CALL而重新启动),则标记通常被视为 * 未执行标签 *。
行的其余部分被解析,但是
)
,<
,>
,&
和|
不再有特殊含义。行的整个其余部分被认为是标签“命令”的一部分。^
仍然是特殊的,这意味着可以使用行继续符将下一行附加到标签上。带括号的块中的 Unexecuted Label 将导致致命的语法错误,除非它后面紧跟命令或下一行的 Executed Label。
(
对于 Unexecuted Label 后面的第一个命令不再具有特殊含义。标签解析完成后,该命令将中止。不会对标签执行后续阶段
有三种例外情况会导致在阶段2中找到的标签被视为 Executed Label,并继续解析到阶段7。
标签标记之前有重定向,并且行上有
|
管道或&
、&&
或||
命令串联。标签标记之前有重定向,并且命令位于带括号的块中。
label标记是带括号的块中某行的第一个命令,上面的行以 Unexecuted Label 结尾。
在阶段2中发现 Executed Label 时,将发生以下情况
标签、其参数和重定向都被排除在阶段3的任何echo输出之外
该行中任何后续的连接命令都将被完全解析并执行。
有关 * 已执行标签 * 与 * 未执行标签 * 的详细信息,请参见https://www.dostips.com/forum/viewtopic.php?f=3&t=3803&p=55405#p55405
阶段3)回显解析的命令仅当命令块不以
@
开始,且ECHO在上一步骤开始时为ON时。**阶段4)FOR
%X
变量扩展:**仅当FOR命令处于活动状态且正在处理DO之后的命令时。此时,批处理的第1阶段已经将FOR变量(如
%%X
)转换为%X
。命令行在第1阶段具有不同的百分比展开规则。这就是命令行使用%X
而批处理文件使用%%X
作为FOR变量的原因。FOR变量名区分大小写,但
~modifiers
不区分大小写。~modifiers
优先于变量名。如果~
后面的字符既是修饰符又是有效的FOR变量名,并且存在活动FOR变量名的后续字符,则该字符将被解释为修饰符。FOR变量名是全局的,但仅在DO子句的上下文中。如果例程是从FOR DO子句中调用的,则FOR变量不会在调用的例程中展开。但是,如果例程有自己的FOR命令,则内部DO命令可以访问***所有当前定义的FOR变量。
FOR变量名可以在嵌套的FOR中重用。内部的FOR值优先,但一旦INNER FOR关闭,则外部的FOR值将恢复。
如果在此阶段开始时ECHO为ON,则重复阶段3),以显示FOR变量展开后解析的DO命令。
***----从这一点开始,阶段2中识别的每个命令都将单独处理。
----完成一个命令的阶段5到阶段7,然后再进入下一个命令。***
**阶段5)延迟扩展:**仅当延迟扩展打开,命令不在parenthesized block on either side of a pipe中,并且命令不是"naked" batch script(脚本名称不带括号、CALL、命令串联或管道)时。
命令的每个令牌被独立地解析以用于延迟扩展。
大多数命令分析两个或多个标记-命令标记、参数标记和每个重定向目标标记。
FOR命令仅分析IN子句标记。
IF命令只解析比较值-一个或两个,具体取决于比较运算符。
对于每个已解析的标记,首先检查它是否包含任何
!
。如果不包含,则不解析该标记-这对于^
字符很重要。如果标记确实包含!
,则从左到右扫描每个字符:如果是插入符号(
^
),则下一个字符没有特殊含义,插入符号本身将被删除如果是感叹号,则搜索下一个感叹号(不再观察到插入符号),展开到变量的值。
连续开口
!
折叠成单个!
删除任何剩余的未配对
!
在此阶段扩展VAR是"安全的",因为不再检测特殊字符(甚至
<CR>
或<LF>
)管道的每一端都是独立和异步处理的。
%comspec% /S /D /c" commandBlock"
在新的cmd.exe线程中执行,因此命令块获得阶段重新启动,但这次是在命令行模式下。<LF>
都将转换为<space>&
。其他<LF>
将被剥离。||
is used。/?
。如果在标记中找到任何地方,则中止阶段6并继续阶段7,在阶段7中将打印CALL的HELP。CALL
,以便可以堆叠多个CALL&
或|
(
开头@
开头IF
或FOR
未被识别为内部或外部命令。:
开始的标签,则在阶段2的第二轮中CALL不被中止。:
开头的标签,则对于已调用的脚本或:labels,不执行阶段7。
*7.1 -执行内部命令-如果命令标记用引号括起来,则跳过此步骤。否则,尝试解析出内部命令并执行。
+
/
[
]
<space>
<tab>
,
;
或=
之前中断命令标记如果前面的文本是内部命令,则记住该命令
.
\
或:
之前中断命令标记如果上述文本不是内部命令,则后藤7.2
否则前面的文字可能是一个内部命令。记住这个命令。
+
/
[
]
<space>
<tab>
,
;
或=
之前中断命令标记如果前面的文本是现有文件的路径,则后藤7.2
否则执行记忆的内部命令。
/?
,所有内部命令都将打印帮助而不是执行它们的功能。大多数命令在/?
出现在参数中的任何地方时都能识别它。但是少数命令,如ECHO和SET,仅在第一个参数标记以/?
开头时才打印帮助。set "name=content" ignored
**--〉**值=content
则将第一个等号和最后一个引号之间的文本用作内容(不包括第一个等号和最后一个引号)。忽略最后一个引号之后的文本。如果等号之后没有引号,则将该行的其余部分用作内容。
set name="content" not ignored
**--〉**值="content" not ignored
则等于之后的行的整个剩余部分被用作内容,包括可能存在的任何和所有引号。
评估IF比较,并且取决于条件是真还是假,从阶段5开始处理适当的已经解析的依赖命令块。
FOR命令的IN子句被适当地迭代。
如果这是一个迭代命令块输出的FOR /F,则:
IN子句通过CMD /C在新的cmd.exe进程中执行。
命令块必须再次经历整个解析过程,但这次是在命令行上下文中
ECHO将启动,延迟扩张通常将启动禁用(取决于注册表设置)
子cmd.exe进程终止后,IN子句命令块所做的所有环境更改都将丢失
对于每次迭代:
FOR变量值已定义
然后处理已经解析的DO命令块,从阶段4开始。
后藤使用以下逻辑定位:label
从第一个参数标记解析标签
扫描标签的下一个匹配项
从当前文件位置开始
如果到达文件末尾,则循环回到文件开头并继续到原始起始点。
扫描在找到的标签第一次出现时停止,并将文件指针设置为紧跟标签的行。脚本从该点继续执行。请注意,成功的真正后藤将立即中止任何已分析的代码块,包括FOR循环。
如果找不到标签,或者标签标记丢失,则后藤失败,打印错误消息,并弹出调用堆栈。这实际上起到EXIT /B的作用,除了GOTO之后的当前命令块中的任何已解析命令仍在执行,但在CALLER的上下文(EXIT /B之后存在的上下文)中执行
有关标签解析规则的更精确描述,请参见https://www.dostips.com/forum/viewtopic.php?t=3803;有关标签扫描规则,请参见https://www.dostips.com/forum/viewtopic.php?t=8988。
RENAME和COPY都接受源路径和目标路径的通配符。但是Microsoft在记录通配符如何工作方面做得很糟糕,尤其是对于目标路径。可以在How does the Windows RENAME command interpret wildcards?中找到一组有用的通配符规则
忽略所有参数标记
如果找不到第一个字符指定的卷,则中止并返回错误
除非使用SUBST为
::
定义卷,否则::
命令标记将始终导致错误如果使用SUBST为
::
定义卷,则卷将被更改,而不会被视为标签。,
、;
、然后,=
或+
在第一次出现<space>
,
;
或=
时中断命令标记,并将剩余部分添加到参数标记的前面。如果找不到该卷,则中止并返回错误。
:
开头,则转至7.4请注意,如果标签令牌以
::
开头,则无法达到此值,因为除非使用SUBST为::
定义卷,否则前面的步骤将因错误而中止。:
开头,则转至7.4请注意,很少会出现这种情况,因为除非命令令牌以
::
开头,SUBST用于定义::
的卷,并且整个命令令牌是外部命令的有效路径,否则上一步骤将因错误而中止。:
开头,则忽略命令及其所有参数。7.2和7.3中的规则可能会阻止标签达到这一点。
命令行解析器:
工作原理与BatchLine-Parser类似,不同之处在于:
%*
、%1
等参数扩展%var%
保持不变。%%
没有特殊处理。如果var = content,则%%var%%
扩展为%content%
。!var!
保持不变。::
开头时才会导致错误解析整数值
cmd.exe在许多不同的上下文中从字符串中解析整数值,并且规则不一致:
SET /A
%var:~n,m%
(可变子字符串扩展)FOR /F "SKIP=n"
有关这些规则的详细信息,请访问Rules for how CMD.EXE parses numbers
对于希望改进cmd.exe解析规则的任何人,可以在discussion topic on the DosTips forum中报告问题并提出建议。
Jan Erik(Jeb)-相的原作者和发现者
Dave Benham(dbenham)-更多附加内容和编辑
j5fpnvbx2#
从命令窗口调用命令时,命令行参数的标记化不是由
cmd.exe
完成的(又称为“ shell ”)。最经常地,标记化由新形成的进程的C/C运行时来完成,但这不是必须的--例如,如果新进程不是用C/C编写的,或者如果新进程选择忽略argv
并为自己处理原始命令行(例如GetCommandLine())。在操作系统级别,Windows将命令行作为单个字符串传递给新进程。这与大多数 *nix shell形成对比,在这里,shell在将参数传递给新形成的进程之前,会以一致的、可预测的方式对参数进行标记化。所有这一切意味着,在Windows上的不同程序中,您可能会遇到非常不同的参数标记化行为,因为各个程序通常会自行进行参数标记化。如果这听起来像是无政府状态,那么它确实是。然而,由于大量Windows程序 * 确实 * 使用Microsoft C/C++运行时的
argv
,因此理解how the MSVCRT tokenizes参数通常可能是有用的。以下是摘录:微软的“批处理语言”(
.bat
)对于这种无政府环境也不例外,并且它开发了自己独特的标记化和转义规则。看起来cmd.exe的命令提示符确实对命令行参数做了一些预处理(主要用于变量替换和转义),然后再将参数传递给新执行的进程。批处理语言和cmd转义的详细信息,请参阅本页上Jeb和dbenham的精彩回答。让我们用C语言构建一个简单的命令行实用程序,看看它对测试用例有什么影响:
(注意:argv[0]始终是可执行文件的名称,为了简洁起见,下面省略了它。在Windows XP SP3上测试。使用Visual Studio 2005编译。)
还有我自己的一些测试:
dgtucam13#
百分比展开规则
下面是jeb's answer中第1阶段的详细说明(对批处理模式和命令行模式都有效)。
%
或<LF>
。如果找到,则<LF>
处截断行)**<LF>
,则<LF>
开始的行的其余部分%
,因此请继续执行1.1%
)***如果是命令行模式则跳过 *%
,则将
%%
替换为单个%
并继续扫描*
并且启用了命令扩展,则将
%*
替换为所有命令行参数的文本(如果没有参数,则替换为nothing)并继续扫描。<digit>
,则将
%<digit>
替换为参数值(如果未定义,则无替换)并继续扫描。~
并且启用了命令扩展,则<digit>
,则将
%~[modifiers]<digit>
替换为修改后的参数值(如果未定义或指定了$PATH,则替换为nothing:修饰符未定义)并继续扫描。<digit>
* 之前的最后一个修饰符查看下一个字符串,在
%
或缓冲区结束之前中断,并将其命名为VAR(可能是空列表)%
,则用VAR值替换
%VAR%
并继续扫描删除
%VAR%
并继续扫描查看下一个字符串,在
%
:
或缓冲区结束之前中断,并将其称为VAR(可能是空列表)。如果VAR在:
之前中断,并且后续字符为%
,则将:
作为VAR中的最后一个字符,并在%
之前中断。%
,则将
%VAR%
替换为VAR值并继续扫描删除
%VAR%
并继续扫描:
,则删除
%VAR:
并继续扫描。~
,则[integer][,[integer]]%
模式匹配,则将
%VAR:~[integer][,[integer]]%
替换为VAR值的子字符串(可能导致空字符串)并继续扫描。=
或*=
,则无效的变量搜索和替换语法引发***致命错误:所有解析的命令都将中止,如果处于批处理模式,批处理也将中止!***
[*]search=[replace]%
的模式,其中搜索可以包括除=
之外的任何字符集,并且替换可以包括除%
之外的任何字符集,则执行搜索和替换后,将
%VAR:[*]search=[replace]%
替换为VAR值(可能导致空字符串)并继续扫描删除
%
并从%
后的下一个字符开始继续扫描%
并从保留的前导%
之后的下一个字符开始继续扫描以上有助于解释为什么这批
给出以下结果:
注1-阶段1发生在识别REM语句之前。这一点非常重要,因为这意味着如果注解具有无效的参数扩展语法或无效的变量搜索和替换语法,则即使注解也会生成致命错误!
注2- % parsing规则的另一个有趣的结果:变量包含:可以定义名称中的字符串,但除非禁用命令扩展,否则无法展开这些字符串。有一个例外-在启用命令扩展时,可以展开末尾包含单个冒号的变量名。但是,您无法对以冒号结尾的变量名执行子字符串或搜索和替换操作。下面的批处理文件(由jeb提供)演示了此行为
注3-jeb在他的帖子中列出的解析规则顺序的一个有趣的结果:执行延迟扩展的查找和替换时,查找和替换术语中的特殊字符都必须转义或用引号引起来。但百分比扩展的情况不同-查找术语不能转义(尽管可以用引号引起来)。百分比替换字符串可能需要转义或用引号引起来,也可能不需要,具体取决于您的意图。
延迟扩展规则
下面是对jeb's answer中第5阶段的扩展和更准确的解释(对批处理模式和命令行模式都有效)
如果满足以下任一条件,则跳过此阶段:
CALL
、带括号的块、任何形式的命令串联(&
、&&
或||
)或管道|
关联。延迟扩展过程独立地应用于令牌。一个命令可以具有多个令牌:
for ... in(TOKEN) do
if defined TOKEN
if exists TOKEN
if errorlevel TOKEN
if cmdextversion TOKEN
if TOKEN comparison TOKEN
,其中比较是==
、equ
、neq
、lss
、leq
、gtr
或geq
之一不对不包含
!
的令牌进行更改。对于每个至少包含一个
!
的标记,从左到右扫描每个字符以查找^
或!
,如果找到,则!
或^
文本需要^
,则^
!
,则查看下一个字符串,在
!
或<LF>
之前断开,并将其命名为VAR(可能是空列表)!
,则将
!VAR!
替换为VAR值并继续扫描删除
!VAR!
并继续扫描查看下一个字符串,在
!
、:
或<LF>
之前中断,并将其命名为VAR(可能是空列表)。如果VAR在:
之前中断,且后续字符为!
,则将:
作为VAR中的最后一个字符,并在!
之前中断!
,则将
!VAR!
替换为VAR值并继续扫描删除
!VAR!
并继续扫描:
,则删除
!VAR:
并继续扫描~
,则[integer][,[integer]]!
模式匹配,则用VAR值的子串替换!VAR:~[integer][,[integer]]!
(可能导致空字符串)并继续扫描。[*]search=[replace]!
的模式,其中搜索可以包括除=
之外的任何字符集,并且替换可以包括除!
之外的任何字符集,则在执行搜索和替换(可能导致空字符串)后,将
!VAR:[*]search=[replace]!
替换为VAR值并继续扫描!
否则保留前导
!
!
之后的下一个字符开始继续扫描cig3rfwq4#
如前所述,在μSoft land中,命令被传递了整个参数字符串,由命令自己将其解析为单独的参数以供使用。不同程序之间没有一致性,因此没有一套规则来描述这个过程。无论程序使用什么C库,您都需要检查每个角落的大小写。
至于系统
.bat
文件,测试如下:现在我们可以运行一些测试,看看你是否能弄清楚μSoft正在尝试做什么:
到目前为止都很好(从现在开始我将省略无趣的
%cmdcmdline%
和%0
)。无文件名扩展。
没有引号剥离,虽然引号可以防止参数拆分。
连续的双引号会使他们失去他们可能拥有的任何特殊的解析能力。@Beniot的例子:
测验:如何将任何环境变量的值作为一个 single 参数(即
%1
)传递给bat文件?理智的分析似乎永远被打破了。
为了方便您的娱乐,请尝试在这些示例中添加各种
^
、\
、'
、&
(等)字符。nnt7mjpx5#
你已经有一些很好的答案以上,但要回答你的问题的一部分:
这里发生的事情是,因为在=之前有一个空格,所以创建了一个名为
%a<space>%
的变量,所以当你echo %a %
时,它的计算结果正确地为b
。剩下的部分
b% c%
被计算为纯文本+一个未定义的变量% c%
,它应该作为类型返回,对于我来说echo %a %b% c%
返回bb% c%
我怀疑在变量名中包含空格的能力与其说是计划中的“特性”,不如说是一种疏忽
rggaifut6#
FOR
-循环元变量展开这是accepted answer中**阶段4)**的扩展说明(适用于批处理文件模式和命令行模式)。当然,
for
命令必须处于活动状态。下面介绍do
子句之后的命令行部分的处理。请注意,在批处理文件模式下,由于前面的立即%
-扩展阶段( 阶段1)),%%
已经被转换为%
。%
-符号,从左侧开始直至行末;如果找到一个,则:~
;如果是,则:fdpnxsatz
中,尽可能多地取定义for
变量引用或$
-符号的字符之前的下列字符(每个字符甚至取多次);如果遇到这样的$
符号,则::
1;如果找到,则::
后面有字符,则将其作为for
变量引用并按预期展开,除非未定义,否则不展开并继续在该字符位置扫描;:
是最后一个字符,cmd.exe
将崩溃!:
)不展开任何内容;$
符号)使用所有修饰符扩展for
变量,除非未定义,否则不扩展并继续在该字符位置扫描;~
或命令扩展名被禁用)请检查下一个字符:%
,则不扩展任何内容,并返回到该字符位置2处的扫描开始处;for
变量引用并扩展,除非未定义,否则不扩展;1)
$
和:
之间的字符串被认为是环境变量的名称,甚至可以为空;因为环境变量不能有空的名字,所以行为与未定义的环境变量是一样的。2)这意味着名为
%
的for
元变量不能在没有~
修饰符的情况下展开。原始来源:How to safely echo FOR variable %%~p followed by a string literal
t1rydlwq7#
编辑:请参阅已接受的答案,以下内容是错误的,仅解释了如何将命令行传递给TinyPerl。
关于报价,我的感觉是,行为如下:
"
时,字符串匹配开始"
的每个字符都是全局匹配的"
时:""
(因此是三重"
),则会在字符串中添加双引号"
(因此是双"
),则会在字符串中添加双引号,字符串匹配结束"
,则字符串匹配结束简而言之:
"a """ b "" c"""
由两个字符串组成:a " b "
和c"
如果
"a""
、"a"""
和"a""""
位于行尾,则它们都是相同的字符串ycggw6v28#
请注意,微软已经发布了终端的源代码。它的语法分析可能类似于命令行。也许有人有兴趣根据终端的解析规则测试反向工程的解析规则。
Link的源代码。