前几天尝试解决命令行参数中含有特殊字符导致错误划分的问题时出了一点小问题,检查代码发现没有什么明显错误,就开始对关键部分断点纠错,奇怪事情发生了,在仅使用 echo 的情况下,变量的值居然前后不同。
(关于解决命令行参数中含有特殊字符的讨论帖原帖链接:http://bbs.bathome.net/thread-15349-1-1.html)
这个测试中出现的诡异的现象让我纳闷了许久,最后发现问题出在 cmdcmdline 身上,每当对 %cmdcmdline% 使用变量偏移或变量替换时(哪怕是在 echo 中),%cmdcmdline% 就会被改变,请参考代码示例:- @echo off
- setlocal enabledelayedexpansion
- echo 原命令行参数:%cmdcmdline%
- echo;
- echo 显示第三个字符之后的内容:%cmdcmdline:~3%
- echo;
- echo 再次显示命令行参数:%cmdcmdline%
- echo;
- echo 未作任何赋值操作,但是却改变了动态环境变量 %%cmdcmdline%%...
- pause>nul
复制代码 而且这个 cmd 进程的命令行参数还真的被改变了...- @echo off
- setlocal enabledelayedexpansion
- echo 原命令行参数:
- wmic process where name="cmd.exe" get commandline /value
- echo 按任意键查看 cmd 的实际命令行参数:
- pause>nul %cmdcmdline:~,1% %cmdcmdline:c=cmd /c 国家机密,禁止泄露%
- echo;
- echo 再次显示命令行参数:
- wmic process where name="cmd.exe" get commandline /value
- echo 可见这里是改变了当前 cmd 的命令行参数,进而使动态环境变量 cmdcmdline 也随之变更。
- pause>nul
复制代码 测试后发现仅 cmdcmdline 变量存在此现象,这是否说明,绝大多数变量的引用是通过传值来实现的,而 cmdcmdline 是传址调用的呢?
或者从更高的角度来思考,cmd 对于部分动态环境变量的引用是否都是通过传址实现的呢?
有意思的是,因为标签和注释也需要经历预处理,所以我们可以不执行任何语句,单纯靠预处理来完成对 cmdcmdline 的修改- @echo off&setlocal enabledelayedexpansion
- echo A:!cmdcmdline!
- :%cmdcmdline:*" =%
- :%cmdcmdline:""="%
- :%cmdcmdline:^=^^%
- :%cmdcmdline:&=^&%
- :%cmdcmdline:(=^(%
- :%cmdcmdline:)=^)%
- set "str=%cmdcmdline%"
- echo;
- echo B:!str!
- pause>nul
复制代码 更有趣的是,它居然还不受常规的预处理流程限制,同一行中的两个 %cmdcmdline% 的变量替换都生效了,
由于对它的修改,仅依赖于预处理中的变量替换和偏移,并不是一条语句,所以不需要连接符也能一次预处理中完成多次替换,不过也因为无法执行语句,所以无法进行变量嵌套- @echo off&setlocal enabledelayedexpansion
- echo A:!cmdcmdline!
- :%cmdcmdline:~,7% %cmdcmdline:cmd=cmd(批处理宿主)% %cmdcmdline:/c=/c(执行字符串指定的命令然后终止)%
- echo;
- echo B:!cmdcmdline!
- pause>nul
复制代码 真是有趣而又诡异的现象,其局限性比较大,但是也有一定的实用性,以下这个代码就对它的实用性稍作探索:- @echo off&setlocal enabledelayedexpansion
- rem %cmdcmdline:~,1% %cmdcmdline:c=#%
- set test=#
- rem 以上为初始化,%cmdcmdline% 与 %test% 分别用于两处对比测试。
-
- echo %time%
- for /l %%a in (1 1 5000) do (
- break !cmdcmdline:*#=%%a#!!cmdcmdline:0=零!!cmdcmdline:1=一!!cmdcmdline:2=二!!cmdcmdline:3=三!!cmdcmdline:4=四!!cmdcmdline:5=五!!cmdcmdline:6=六!!cmdcmdline:7=七!!cmdcmdline:8=八!!cmdcmdline:9=九!
- )
- echo 5000=!cmdcmdline!
- rem 利用 cmdcmdline 的特殊性在一条语句中完成十个替换
-
- echo %time%
- for /l %%a in (1 1 5000) do (
- set test=!test:*#=%%a#!
- for %%b in ("0=零" "1=一" "2=二" "3=三" "4=四" "5=五" "6=六" "7=七" "8=八" "9=九") do set test=!test:%%~b!
- )
- echo 5000=!test!
- rem 常规方案,用十条语句分别进行替换
-
- echo %time%
- rem 可以发现前者的用时要少得多
- pause
复制代码 当然除此以外,也可以用来为 cmd.exe 伪造身份,使命令行参数中的隐私信息不泄露。
另外,需要注意的是,因为动态环境变量独立于变量表,所以 setlocal 与 endlocal 无法对其进行“备份还原”的操作:- @echo off
- echo setlocal 前:【%cmdcmdline%】
- setlocal
- echo 改变 %cmdcmdline%
- :%cmdcmdline:~-10%
- echo setlocal 后:【%cmdcmdline%】
- endlocal
- echo endlocal 后:【%cmdcmdline%】
- pause
复制代码
|