|
qzwqzw
银牌会员
天的白色影子
积分 2342
发帖 635
注册 2004-3-6
状态 离线
|
『楼 主』:
[50条]不能说的秘密-CMD命令奇诡语法特性汇集
谨以此文献给我生命中最重要的三个女人,在本文更新期间,是她们给了我很大的物质支持和精神鼓励,支持我最终将此文完成。同时感谢论坛管理层,尤其是批处理之家的namejm,他不仅给我提供了很多线索和建议,同时也在论坛管理上提供了很大的便利和支持。当然,也感谢所有参与讨论并给出建议的朋友们。他们分别是(按发帖时间排序):
BATHOME:neorobin、hanyeguxing、yangfengoo、x9tiancmd、随风、x9tiancmd、Batcher、lxzzr
CN-DOS:qinchun36
声明:
- 本帖目前已完成初稿,现向全体会员开放阅读,大家可以选择在线阅读或者制作离线阅读版本;
- 我将不再主要负责本帖的更新,但仍然希望大家能继续跟帖讨论补充,同时麻烦管理层及时更新本帖;
- 本文代码的运行环境,凡是未明确声明的,均是在Windows XP SP2的CMD下;
- 此篇可以看做是《CMD/DOS下符号的作用参考》的姊妹篇,两者内容可能会有重叠;
- 本文作者不希望各位在此发表无内容的回复,如果只是支持可以选择评分,如果只是路过就请不要留下脚印;
- 本帖在CNDOS与BATHOME同步更新,互为备份,参考链接如下:
BATHOME:http://bbs.bathome.net/thread-7629-1-1.html
CNDOS:http://www.cn-dos.net/forum/viewthread.php?tid=50912
Quote: | 示例:
set varname=var1
set var1=value
call echo %varname%=%%%varname%%%
效果:输出var1=value,实现变量的二次扩展
注释:这种二次扩展的办法在纯DOS中比较常用,CMD下在不能使用变量延迟时也可以考虑 |
|
Quote: | 示例:
md Test Sample
cd Test Sample
rd Test Sample
效果:md和rd命令正常执行,cd命令报错“系统找不到指定的路径。”
注释:md和rd命令隐含支持多个目录的创建和删除,所以把Test Sample理解成了Test和Sample两个目录,
但cd命令只能是一个目录,而且隐含支持带空格不带引号的路径,所以将Test Sample理解成了一个目录,
三个命令在认识上的不统一,导致了这个问题的出现。
另外,不像command.com,cmd下的md和rd已经都支持多级目录的创建和删除了,这个在官方文档已提到。 |
|
Quote: | 示例:echo.%var%
效果:如果变量var 为空显示空行,如果不为空则显示其值
注释:echo. 官方文档解释为输出一个空行,用于文本的排版;
但更多的用于防止输出的变量为空时,echo语句出现语法错误;
. 也可以改为, + / : ; [ ] 等特殊字符 |
|
Quote: | 示例:
echo 文本1 ^
文本2 ^
文本3
效果:将三段文本合并到一行显示
注释:^ 将随后的行结束符取消转义,所以有其它语言中类似续行符的作用
其它命令中也可以这样使用,甚至可以用它截断命令名、文件名或者参数
只是别在引号对中这样使用 |
|
Quote: | 示例:
set/p=不含回车换行符的文本行<nul>test.txt
findstr "$" test.txt
type test.txt|findstr "$"
效果:第二句输出任何文本,第三句输出“不含回车换行符的文本行”
注释:findstr的正则与POSIX正则以及其他标准的正则都不太一样,是个简化版的正则。
$ 在这里做为正则表达式元字符,匹配行尾的回车和换行符号,而不是真正的行尾。
而因为测试文本没有回车符和换行符,所以第二句的findstr "$" 匹配失败。
第三句成功匹配是因为CMD 的“后处理”为文本添加了回车换行并通过管道传给了findstr |
|
Quote: | 示例:
echo 我 你 他>test.txt
findstr "我 你 他" test.txt
效果:没有输出任何文本
注释:finstr在匹配多个中文串时存在问题,使用开关/l或者/r可以得到正确的输出。 |
|
Quote: | 示例:
echo.>test.txt && attrib +h test.txt
echo.>test2.txt && attrib +s test2.txt
for %%i in (*.txt) do echo %%i
效果:显示结果不包含带有隐藏属性的test.txt,但包含带有系统属性的test2.txt
注释:不带/a开关的dir 是两者都不显示,for 显然对文件属性有自己的认识 |
|
Quote: | 示例:
md TestSample & cd TestSample & cd.>"test 1".txt
for %%f in (*.txt) do echo %%~sf
效果:在WindowsXP SP2环境下显示D:\TESTSA~1\TEST1~1.TXTt,多了一个字符t
注释:短名扩展是基于完整长名路径后的。当路径中既有长目录名,又有含空格的短文件名时,
CMD的短名扩展不对原长名路径的长度进行修剪,导致短名路径尾部残留原长名路径的字符。
Windows 2003以上系统无此问题。
链接:http://www.cn-dos.net/forum/viewthread.php?tid=27063 |
|
Quote: | 示例:
echo 1 > 1.txt & echo 2 > 2.txt
for %%f in (*.txt) do ren %%f f-%%f
效果:生成两个文件f-2.txt和f-f-1.txt ,1.txt 被改名后再次被改名
注释:在for 语句内使用ren 要谨慎,f-可以改为任意以字母开头的字符串 |
|
Quote: | 示例:for /f "delims=" %%i in (test.txt) do echo %%i
效果:显示test.txt的所有行,除了空行
注释:for/f 的文本遍历会遇到空行时,替代变量%%i被赋值为空,不会执行do的语句块。
显示空行文本的折中方案是,使用finstr或者find为文本的所有行包括空行添加一个前缀,
待通过for/f的“防空”保护后,再用set或者tokens把这个前缀去除。如下: |
|
Quote: | 示例:for /f "eol=" %%i in (test.txt) do echo %%i
效果:显示test.txt的文本内容,忽略以起始的行
注释:这个语法之所以称得上“奇诡”,主要是因为官方文档的中文化过程中,
将“an end of line comment character”翻译成了“行尾字符”(ntcmds.chm)
在命令行帮助中则是“行注释字符的结尾”,按字面意义理解都会理解成行尾的字符,
因此误人不浅,实际上应该是“代表行结束的注释字符”,而且这个字符必须在行首。
不指定eol时,for/f缺省使用分号;作为eol,所以会过滤掉文本中以分号起始的行,
而"eol="会将双引号指定为eol,使用"eol= delims="则会将空格指定为eol。
使用示例中的可以近似显示所有的行,因为是一个控制字符,不太可能出现在文本中。 |
|
Quote: | 示例:for /r %f in (.) do @echo %f
效果:分行显示当前目录下所有子目录名,并在行尾添加\.
注释:括号中的句点还可以改为其它不含文件通配符(*,?)的字符或字符串 |
|
Quote: | 示例:
if "e" leq "-" (echo "e <= -") else echo "e > -"
if "ef" leq "-f" (echo "ef <= -f") else echo "ef > -f"
效果:显示结果表明e大于-,而ef小于-f
注释:减号-也可以换成单引号',它们在if的数值比较语句中充当一种另类的转义字符
链接:http://bbs.bathome.net/thread-6853-1-1.html |
|
Quote: | 示例:if 3 gtr -2147483645 (echo 3>-2147483645) else (echo 3<=-2147483645)
效果:显示结果表明3<=-2147483645
注释:if数值比较的核心是减法,依靠对两个数值的减法差值的正负判定大小
而差值变量是32位(DWORD)整型变量,存在-2^31~2^31-1的限定范围
而3- (-2147483645)的差值即超出此限,结果有正转负,判定也由大变小
链接:http://bbs.bathome.net/thread-7659-1-1.html |
|
Quote: | 示例:if defined "test var" (echo pass) else (echo fail)
效果:不管之前有无定义变量test var,显示结果都为fail
注释:if defined判定的变量名包含空格时,不能直接使用 if defined test var echo pass
此时,test被识别为变量名,var被识别为if的执行体语句块,如果存在变量test,则出现语法错;
也不能如示例般使用引号对将变量名引起,否则if defined将引号看作变量名的一部分,执行else块
此时可以使用两个方法使变量名中的空格躲过CMD预处理的词法切分逻辑,详见链接:
1. for %%f in ("test var") do if defined %%~f (echo pass) else (echo fail)
2. set varname=test var&if defined !varname! (echo pass) else (echo fail)
方法2中的!varname!更改为%varname%无效,因为%的变量扩展逻辑在空格的词法切分逻辑之前
而且变量!varname!不能使用!varname:~1,2!的引用形式,因为逗号影响了CMD预处理的词法切分逻辑
链接:http://bbs.bathome.net/viewthread.php?tid=2050 |
|
Quote: | 示例:
md testdir&if exist testdir\nul (echo Pass) else (echo Fail)
md "test dir"&if exist "test dir"\nul (echo Pass) else (echo Fail)
效果:第一句显示Pass,第二句显示Fail
注释:if exist通常用于判断一个路径是否存在,它不能直接分辨出目标路径究竟是目录还是文件。
而如示例中所用的 if exist test\nul ...,则是从DOS 起广泛使用的判断目录存在的标准方案。
它借助系统中所存在的aux con nul prn com1 lpt1等标准字符设备名判断目标路径是否指向目录,
因为这些设备没有可以在任意目录下引用,所以“目录路径\nul”这样的引用被系统认为是有效的,
但在CMD下,这种方法不适用于判定带引号的目录,这点与if defined "变量名" 的缺陷类似。
此时,可以使用以下方案:if exist "test dir"\* echo Pass,通配符使 |
|
Quote: | 示例:
setlocal enabledelayedexpansion
for /f "delims=" %%a in ('ipconfig^|findstr /i "Address"') do set var=%%a
set CarriageReturn=!var:~-1!
效果:变量CarriageReturn被赋值为一个回车符(0x0d,\r,Cr)
注释:源于 ipconfig 的输出每行行尾都有两个回车符,在被for /f截掉一个后还能剩一个;
引用时需要用变量延迟的形式,否则就会在预处理中被当做行结束符而被过滤掉
链接:http://bbs.bathome.net/viewthread.php?tid=6692 |
|
Quote: | 示例:
reg add hkcu\test\"quote\"folder /f
reg query hkcu\test\"quote\"folder
reg delete hkcu\test\"quote\"folder
效果:reg添加、查询和删除的注册表键支都是Test,而不是我们设想的Test"quote"folder
注释:XP SP2的reg在注册表键值出现引号" 时可以使用\ 转义,
比如 reg add hkcu /v test /d "\"value\"" /f
但是这个转义字符不能用在注册表键支中,因为它被视作键支层级的分隔符
解决办法是升级reg.exe 为更高版本系统下的reg.exe 外
链接:http://www.bathome.net/viewthread.php?tid=7004 |
|
Quote: | 示例:ren test1.txt *t2*
效果:test1.txt将改名为test1.txt2,而非test2.txt
注释:在ren 的任何文件名参数中均可以使用通配符(* 和 ?)。
如果在 FileName2 中使用通配符,则通配符代表的字符与 FileName1 中的相应字符匹配。
类似于正则表达式中的最远匹配,但只匹配通配符后的一个字符,其余字符都属于替换字符。
另外,如果Filename1 含有通配符,那么在NTFS和FAT32下执行会略有差异,详见以下链接。
链接:http://www.cn-dos.net/forum/viewthread.php?tid=29538 |
|
Quote: | 示例:md test1 & ren test1 test2
效果:生成test2目录
注释:cmd的ren命令已经悄悄支持目录的改名了。当然,移动目录仍然需要使用move命令。 |
|
Quote: | 示例:set/=value
效果:生成变量/,值为value
注释:set将分隔符识别为变量名的一部分,/可以改为( + , . : ; [ \ ] 等特殊字符 |
|
Quote: | 示例:
@echo off
set var=
echo.%errorlevel%
set var=value
echo.%errorlevel%
效果:此段代码保存为test.bat执行后分行显示“1 1”,保存为test.cmd执行后显示“1 0”
注释:第一个数字1表示:set试图删除一个不存在的变量var时发生内部错误,产生errorlevel为1
第二个数字表示:set成功创建变量var后,.cmd文件会重置errorlevel为0,而.bat文件则不会
也就是说,在.cmd文件中,set成功执行后会重置errorlevel为0,而在.bat文件不管errorlevel
链接:http://www.cn-dos.net/forum/viewthread.php?tid=30968&page=3#pid217157 |
|
Quote: | 示例:
set test 1=value
set test 2
效果:test 1的名值对
注释:set 识别单纯的变量名时会丢掉最后一个空格的字符串 |
|
Quote: | 示例:set "
效果:显示所有的环境变量,其中有包括“=::=”、“=C:=”等变量名含有等号的隐含环境变量
注释:可以把双引号换成一个或多个逗号、双引号、分号或者其混合形式,双引号最好成对使用
“=::”、“=C:”等变量其代表当前CMD会话曾访问过的各个驱动盘下的当前目录。
因变量名中包含等号=,正常情况下,set无法对其显示和修改。
也可能会出现变量“=ExitCode=”,代表CMD所调用外部程序的错误返回码(%Errorlevel%)
链接:http://bbs.bathome.net/thread-7696-1-1.html |
|
Quote: | 示例:set/a n=1,n=!n!+7
效果:显示n=7,而不是8
注释:!!对的变量延迟语法是针对语句块内的多条语句的,
而set/a 的逗号表达式是一个语句,而不是语句块,自然不能实现延迟
解决的办法就是让set/a 自己处理: set/a n=1,n=n+7 |
|
Quote: | 示例:
set /a var=_abc
set /a var2=1a
效果:第一句会将变量var赋值为0,第二句会报“无效数字”的语法错,且不会对变量var2赋值
注释:第一句set/a将_abc识别为变量名,因找不到对应变量,所以赋值结果为0;
第二句set/a将1a识别为操作数,而它又不是一个合法数值,所以提示语法错误,并置errorlevel为9167。
set/a的判断变量名和操作数的标准类似高级语言:字符串以数字起始是操作数,否则为变量名。
此外,判断一个字符串是否是合法的十进制数值的方法如下:
set /a _var=%var% 2>nul
if "%_var%"=="%var%" echo %var%是合法的十进制数值 |
|
Quote: | 示例:set /p test=Text without CrLF(\r\n)<nul
效果:显示一串文本,不含回车换行符
注释:多次使用set /p可以将多串文本输出在一行中,也用在重定向输出过程中 |
|
Quote: | 示例:set /p test=<test.txt
效果:将变量test赋值为test.txt的第一行内容
注释:利用了set/p 的重定向输入特性,文本中的回车换行符被cmd理解为语句结束符
因此可以利用这点切分出文本首行,进而在循环中结合findstr切分出文本的每一行。
提示:set/p最多可以获取1024个字节作为变量,而环境变量长度限制为8192字节。 |
|
Quote: | 示例:for /l %%i in (1,1,33) do setlocal
效果:显示“已经达到最大的 setlocal 递归层。”
注释:“使用 setlocal 和 endlocal 命令,可以在 Cmd.exe 的实例中(或在脚本中)进一步将更改局部化。
Setlocal 创建局部作用范围,而 endlocal 终止局部作用范围。在 setlocal 和 endlocal 作用范围
内所做的更改将会被放弃,从而保持原始环境不变。这两个命令的嵌套最高可达到 32 级。”
此段描述引用自官方文档(ntcmds.chm::/ntcmds_shelloverview.htm)
setlocal意味着重新复制一份环境变量到新的内存地址,而系统只预留了32份地址空间,
所以环境变量复制份数不能超过32份,也就意味着setlocal的递归次数不能超过32级。
因此,适时使用endlocal回收不再使用的地址空间不仅是严谨的,也是必要的。 |
|
Quote: | 示例:subst ~: C:\Windows & ~:
效果:创建虚拟驱动器~,并跳转到该驱动器下
注释:subst 用于创建、显示和删除虚拟驱动器,该驱动器将指向其它真实或虚拟驱动器文件夹的路径,
虚拟驱动器的盘符可以是字母、数字以及一些特殊字符,但非字母的盘符不会出现在subst的显示列表中。 |
|
Quote: | 示例:type *.txt *.log
效果:显示当前目录下所有文本文件和日志文件的内容
注释:在COMMAND下type是不支持文件通配符的,在CMD下悄悄的支持了 |
|
Quote: | 示例:
>>test.txt echo 行 1
echo>>test.txt 行 2
echo 行>>test.txt 3
效果:将三行输出分别输出到test.txt中
注释:重定向符号可以出现语句中的很多地方,前提是不影响输入输出的正常语法;
在上面提到语句块的重定向特性出现之前,在纯DOS的环境中行1 的用法更多的被使用,
因为它使输出语句看起来更加整齐,另外在CMD下也可以避免在行尾出现单独的数字时,
这个数字可能与重定向符号被一起理解为某个句柄的重定向,而出现意想不到的输出。 |
|
Quote: | 示例:
echo.>time.com
time.com
效果:显示COMMAND中TIME命令的输出,并等待用户输入
注释:time可以换成其它COMMAND支持的内部命令名。
对于.COM和没有PE标记的.EXE,Windows都将其认为16位DOS程序,会自动调用其
16位MS-DOS子系统(NTVDM.EXE)解释执行该程序,如果它与COMMAND的内部命令同名,
则优先调用该命令。 |
|
Quote: | 示例:
cmd /u /c echo Unicode Text> test.txt & type test.txt
cmd /u /c echo Unicode Text|findstr ".*"
cmd /u /c echo Unicode Text|find /v ""
cmd /u /c echo Unicode Text|more
cmd /u /c echo Unicode Text|sort
效果:前两行命令输出“ n i c o d e T e x t”的文本,三四行命令将逐字分行输出
测试文本,并在尾部附五个空行,第五条命令将显示“Unicode”原文。
注释:cmd /u用于使命令输出或管道中的文本成为Unicode编码,对于纯英文文本,其Unicode
编码只使用一个高字节,另外一个字节以0x00填补。对于此编码各文本命令支持有很多差异。
type命令虽然支持显示Unicode文本,但需要文本前缀包含有Unicode的“FFFE”的BOM标记,
对于cmd /u /c输出的无BOM的Unicode文本将按照ANSI标准输出,字符间的00字节不做转换。
findstr的特性与type类似,但需要注意的是,它将无法处理重定向输入的Unicode文本。
而这段文本通过管道送到more和find命令之后,它们会将0x00理解为行结束而输出一个换行符。
文本后的五个空行,分别是从文本中的回车符、换行符以及它们之间的两个0x00字符转换而来,
最后一个则是CMD 的“后处理”为文本自动添加的。
而sort则会将Unicode文本转换为ANSI文本,所以会显示正确的文本,即使其中不包含BOM标记。 |
|
Quote: | 示例:mem
效果:首次在CMD窗口中执行会清空窗口,并显示十一个空行,提示符中的长名路径切换为短名;
第二次执行不会清空窗口,但仍会显示十一个空行;以后执行均只显示一个空行。
如果在CMD的全屏窗口中执行时,则会正常显示输出结果。
注释:mem.exe是16位DOS 程序,在CMD中调用之前会启用Windows的16位MS-DOS子系统(NTVDM/WOW),
模拟mem.exe程序类似MSDOS的运行环境,因此导致CMD窗口的输入或输出机制均发生较大的变化,
首先这个模拟子系统的键盘布局缺省只支持“美式英语的美式键盘”,而大多定制的中文系统已将
该键盘布局从“文字服务和输入语言”中删除,所以会报“Invalid keyboard code specified”,
并自动切换回该缺省方案。其次,模拟子系统缺省只支持美式英语的代码页(437),而中文系统下
的CMD是简体中文的代码页(936),所以会将CMD的活动代码页切换为437,并切换屏幕显示字体,
因此导致清屏动作,因为以上两个动作是快速发生,所以用户一般不会观察到键盘布局错误的显示。
如果使用mem>c:\mem.txt重定向命令的输出,则会在输出文本的第一行发现该错误信息。
在CONFIG.NT中使用COUNTRY命令可以更改模拟DOS环境的国家代码和字符集代码页。
在AUTOEXEC.NT中使用KB16命令可以更改模拟DOS环境的键盘方案,但不支持中文键盘方案的代码。
链接:http://www.cn-dos.net/forum/viewthread.php?tid=9452 |
|
[ Last edited by qzwqzw on 2010-4-23 at 20:10 ]
此帖被 +36 点积分 点击查看详情 评分人:【 plp626 】 | 分数: +5 | 时间:2010-4-16 19:53 | 评分人:【 yishanju 】 | 分数: +13 | 时间:2010-4-16 22:38 | 评分人:【 freeants001 】 | 分数: +4 | 时间:2010-4-20 17:59 | 评分人:【 radem 】 | 分数: +5 | 时间:2010-4-22 13:09 | 评分人:【 bc12060101 】 | 分数: +3 | 时间:2010-4-23 21:12 | 评分人:【 jarry0932 】 | 分数: +2 | 时间:2010-4-26 00:47 | 评分人:【 amao 】 | 分数: +4 | 时间:2010-4-26 01:54 |
|
|
|
2010-4-15 15:13 |
|
|
qinchun36
高级用户
据说是李先生
积分 609
发帖 400
注册 2008-4-23
状态 离线
|
|
2010-4-16 22:19 |
|
|
qzwqzw
银牌会员
天的白色影子
积分 2342
发帖 635
注册 2004-3-6
状态 离线
|
|
2010-4-17 23:33 |
|
|
qzwqzw
银牌会员
天的白色影子
积分 2342
发帖 635
注册 2004-3-6
状态 离线
|
『第
4 楼』:
还记得有一个关于for /f usebackq 的奇诡语法
不知道谁还能找到?
另外现在帖子是按核心命令行的字母顺序排序
现在的条数越来越多
索引方式就有点乱了
更新起来也比较麻烦
大家有什么其它的好办法没有
最好是多种索引方式并行
[ Last edited by qzwqzw on 2010-4-19 at 16:07 ]
|
|
2010-4-19 15:08 |
|
|
qzwqzw
银牌会员
天的白色影子
积分 2342
发帖 635
注册 2004-3-6
状态 离线
|
『第
5 楼』:
本文初稿已完成
更新将告一段落
请各位批评指教
|
|
2010-4-23 20:07 |
|
|
wode5130xm
新手上路
积分 10
发帖 10
注册 2010-4-22
状态 离线
|
『第
6 楼』:
好帖啊!看了一下午的论坛了,目前有些看不进去了,保存下来,楼主加油!!!
|
|
2010-4-23 20:52 |
|
|
jarry0932
初级用户
积分 128
发帖 122
注册 2009-9-21
状态 离线
|
『第
7 楼』:
在批处理中只输入:taskmgr,运行后会打开任务管理器,但cmd窗口不会关闭,而使用start taskmgr,则可以正常关闭,类似情况还有explorer,regedit,gpedit.msc等,不知道算不算?
|
|
2010-4-26 00:44 |
|
|
HAT
版主
积分 9023
发帖 5017
注册 2007-5-31
状态 离线
|
『第
8 楼』:
Re 7 楼
不算。
cmd只是做了你让它做的事情。
|
|
|
2010-4-26 13:49 |
|
|
gool123456
初级用户
积分 89
发帖 76
注册 2009-12-13
状态 离线
|
|
2010-4-26 19:28 |
|