|
willsort
元老会员
Batchinger
积分 4432
发帖 1512
注册 2002-10-18
状态 离线
|
『楼 主』:
[原创]无奈何签名代码略析
无奈何签名代码略析
Quote: | ☆开始\运行 (WIN+R)☆
%ComSpec% /cset,=何奈无── 。何奈可无是原,事奈无做人奈无&for,/l,%i,in,(22,-1,0)do,@call,set/p= %,:~%i,1%<nul&ping/n 1 127.1>nul |
|
引自 无奈何·中国DOS联盟 论坛签名
这个签名中的代码应用于 WinNT 系列系统(如WinXP)中,将代码内容复制粘贴到的 “开始-运行” 对话框中,可以看到无奈何兄的签名逐字显示。代码长度只有132B,但其中包含许多批处理编程技巧,有些我也是后来才慢慢注意到的。现在我将这段代码分成节段,将其中使用到的重要技巧略作提示,以飨读者,也请原作者无奈何兄和其他批处理达人批评斧正。
1、%ComSpec% /c
使用它的目的在于,在运行对话框中调用 set / for / pause 等“内部命令”,因为它们由 NT 系列操作系统中命令行环境的主体程序—— cmd “内部”所支持,并且只能在 cmd “内部”所使用。cmd 全名为 cmd.exe,位于 %SystemRoot%\system32\ 路径下,由环境变量 %ComSpec% 指示其完整的路径。因为它只能在 NT 系列环境下运行,所以实际上它与 cmd /c 是近似等效的。但是因为 %ComSpec% 中包含了 cmd.exe 所在的路径,因此在 cmd.exe 位置比较特殊或者 %path% 变量缺漏的特殊系统环境中也可以使用。详细内容可以在命令行环境中使用 cmd /? 查阅到。
因为 “运行” 位于 cmd 之外,所以只能调用具有独立可执行文件的“外部命令”,而 cmd 这个“外部命令”就充当了内部命令的“传声筒”,将一串 “内部命令”当作 cmd 的命令行参数,通过 /c 这一选项开关传递到 cmd 内部,交由 cmd 内部解释执行。在 /c 与其后的附加参数命令串之间,一般会插入空格,以增加代码的可读性;无奈何兄省略空格,正是他提到的“尽量字符最短”和“尽量晦涩难读”要求的体现。而如果在命令行环境中使用,并且用 cmd 代替 %ComSpec% ,则 cmd 与 /c 之间的空格也可省略。这些省略空格的用法,有时并不单纯出于精简代码的目的,而是为了应付某些命令或程序比较羸弱的字符串分析算法——我们有时需要将整个命令行当作一个字符串来处理,而许多命令行程序会将空格作为一个字符串的结束标记,比如 for 命令中的 (set) 部分。当然,这种同时兼容空格与无空格的用法,一方面体现出其兼容并包的灵活特性,另一方面也为许多命令行扩展程序增加了诸多困扰,这就只能说“剑有双刃”了。
另外, /c 之后也可以使用“外部命令”,比如后面出现的 ping ,当它在 %path% 中找不到时,需要指明其所在路径。
2、set,=何奈无── 。何奈可无是原,事奈无做人奈无
此节是将要显示的签名串逆序保存到环境变量中。变量名","的使用在这里有多重含义,除了“两个尽量”的原则外,它还作为 set 与其参数的分隔符,这又是命令行灵活性的例证了——既允许变量名使用非字母数字的字符,又允许这个字符做其它解释。与此异曲同工的还有上面提到的 cmd/c 中“/”,以及“=”“;”等许多字符,这我在旧帖中曾有论述,不太好找了,有兴趣的可以自己多用 cmd 加一些特殊字符来尝试。
签名串逆序的目的自然又是“尽量晦涩难懂”,这与后面 for 中的 (22;-1;0) 相呼应。
签名串前后的两个特殊字符,就是 ASCII 字符集中的响铃符,其 ASCII 码为07,属于 ASCII 码表中的控制区字符。因控制区字符大多有特殊含义,故使用“记事本”很难输入这个字符,可以使用 DOS 下的编辑器 EDIT,届时先键入 Ctrl+P 开启控制字符显示(即暂时关闭其转义特性),再键入 Ctrl+G (提示:G在字母表中的顺序是7,详细内容请参阅网络上 ASCII 码表的相关信息)即可得到这个字符,当然也可使用 UltraEdit 这类十六进制编辑器,直接键入其 ASCII 代码来输入它。因为 ASCII 控制区字符的显示字形并无统一约定,因此它在命令行环境下看起来像一个圆点,而大多数 Windows 的字体不再能正常显示它。但又因 ASCII 标准被吸收为 Unicode 标准中的一个基本平面,因此支持 Unicode 的 NT 系统仍会正确识别并解释这个在旧 DOS 时代就风靡一时的特殊字符。
3、&for,/l,%i,in,(22,-1,0)do,
“&”是命令分隔符,用来分隔一个命令行中的多个命令。Cmd.exe 运行第一个命令,然后运行第二个命令。因为“&”有连接多个命令的功能,所以也称为“命令连接符”。set 是它所连接的第一个命令,for 是第二个命令。需要注意的是,其后的 ping 语句前也存在一个“&”,虽然同是命令分隔符,却分属不同层级,前者分隔 %ComSpec% /c 中的 set 与 for,后者分隔 for 中的 call 与 ping。同类的分隔符还有|,&&,||等,详细内容请查阅Windows帮助“命令提示符”一节。
以后为 for 的前半节,属于 for 循环的控制部分,实现一个从22到0的逆序循环,替换变量为%i,将在以后的循环体语句块中出现,并被替换为自 22 到 0 这 23 个数字串;详细内容可以在命令行环境中使用 for /? 查阅到。(22,-1,0) 之外的其余逗号都可替换为空格,也可以替换为其它可用的参数分隔符(比如“;”“=”等,注意与命令分隔符的区别),如此使用仍然是“尽量晦涩难懂”的体现。
4、@call,set/p= %,:~%i,1%<nul
这是 for 语句块中的第一句代码,目的是根据替换变量 %i 从环境变量 %,% 中取出对应的字符,结合 for 的控制部分,即实现了签名串的逆序逐字显示。@的作用在于禁止其后的 call 语句在命令行中回显,因为 for 语句会创建一个新的命令运行环境来执行循环体中的多条语句,而在这个新环境中命令行回显是开启的。而 call 则实现了命令行转义字符 % 的二次替换:在 for 创建的新环境中依次替换 %i 为 22 到 0 这 23 个数字,在 call 再次创建的新环境中依次再替换 %,:~22,1% 至 %,:~0,1% 为签名串中的每个字符。
至于命令行如何分析出现的多个转义字符 % 及其所夹杂的替换变量、环境变量和命令行参数,这主要取决于“左侧优先于右侧”、“可替换优先于无可替换”这两个原则,再加上环境变量的延迟替换特性,请读者慢慢自行体会。
于是,这一句代码就最终替换为“set/p= (某个签名字符)<nul”,/p 的作用是将签名字符当作询问环境变量值的提示语输出,而这个环境变量是没有名字的,所以将不会有环境变量被保存。至于为何不用 echo 来显示字符,是因为 set/p 不会在显示完字符串后再显示一个换行,这样可以使所有的签名字符显示在一行而非一列上;= 后的空格是可以省略的,它显示在每个字符前,因此会增加字符间距,改进显示效果;<nul 则负责满足 set/p 所等待的输入,< 将 set/p 的输入设备由标准的控制台(CON,通常为键盘+屏幕)重定向为空设备(NUL),虽然它并不是一个实际存在的硬件设备,而只是一个软件意义上的概念,但它会像宇宙中的黑洞一样,“吞噬”所有指向它的输入流和输出流,在这里, set/p 的输入需求也被“吞噬”,因此它不会停下来等待用户的输入了。
5、&ping/n 1 127.1>nul
& 在第3节已做解释。ping 是一个用于网络环境的外部命令,用以向指定的 IP 地址发送一个“网际消息控制协议 (ICMP)”回响请求消息,详细内容请参考 Windows 帮助中的 ping 命令一节;这里使用它可以暂停少许时间,以实现逐字显示的效果,当然它的暂停时间比较短暂而且不很固定,建议将 n 后的数字 1 改为 2,这样暂停的时间将大约等于 1 秒,详细内容请参考[1]。>nul 与第 5 节的 <nul 近似,只是此时 NUL 将作为输出设备,“吞噬” ping 命令所产生的所有输出信息。
后序
这篇文章的初衷,源于 maya0su 兄在[2]中的建议,我准备以无奈何兄的签名为蓝本,修改出一个命令行版的批处理代码,能以较通用的方法逐字显示一段指定的文本。起初以为有签名代码做铺垫,稍做一些修改便应该可以实现,但是随着修改的深入,发现了一些比较麻烦的问题(主要是转义字符的兼容型问题),这才着手仔细研究签名代码,发现了以往不曾注意到的细节和对一些代码的错误理解[3],这才有了这篇原创文章。
其实签名代码中最重要的技巧便是转义字符的二次替换,而我当初错误的将之理解为延迟替换,这也是这篇文章出炉的重要原因之一。但是,当我试图详细解释其中机理时,却发现只能深入而不能浅出,很多内容都牵涉到命令行解释的复杂特性,没有官方文档或其它公开资料可以参考和佐证,而我自己的体会难免会有疏漏,为免贻误读者,只好以一句“自行体会”来含糊带过,说来不免惭愧,敬请广大读者谅解!
参考
[1]批处理编程的异类——时钟(Clock)
http://www.cn-dos.net/forum/viewthread.php?tid=8905#pid54227
[2]批处理参数问题一点谈
http://www.cn-dos.net/forum/viewthread.php?tid=17785
[3]关于"set & echo"变量替换的延迟
http://www.cn-dos.net/forum/viewthread.php?tid=18050
[ Last edited by willsort on 2006-1-22 at 11:34 ]
|
※ Batchinger 致 Bat Fans:请访问 [讨论]批处理编程的异类 ,欢迎交流与共享批处理编程心得! |
|
2006-1-21 22:37 |
|
|
bush
银牌会员
积分 2165
发帖 730
注册 2004-4-21
状态 离线
|
『第
2 楼』:
看看,
好像这个是xp的,不能在纯dos下用吧?
|
|
2006-1-21 23:56 |
|
|
无奈何
荣誉版主
积分 1338
发帖 356
注册 2005-7-15
状态 离线
|
『第
3 楼』:
谢谢 willsort 兄的抬举
这篇文章如果我来写的话,万万不能表达的如此清晰详尽。这段代码是我由偶然想法经过多次的尝试性修改最终完成的,最初的代码并不复杂,我有意识的试探着精简字符,其难度也不大。但兄能逆向拆解,可见功力比我高的不是一点半点。
来论坛的这些日子从兄及其他朋友那里也学到了不少东西。由于最近出差及忙于年终的一些事务有些日子没能上网,终于忙完,明天就要回老家过春节去了,又要度过半个多月没有网络的日子。预祝坛子里的朋友们新春快乐!
|
☆开始\运行 (WIN+R)☆
%ComSpec% /cset,=何奈无── 。何奈可无是原,事奈无做人奈无&for,/l,%i,in,(22,-1,0)do,@call,set/p= %,:~%i,1%<nul&ping/n 1 127.1>nul
|
|
2006-1-22 00:58 |
|
|
willsort
元老会员
Batchinger
积分 4432
发帖 1512
注册 2002-10-18
状态 离线
|
『第
4 楼』:
Re bush:
这段代码确实只能在 NT 系列系统中使用,而且如在签名中提示的一样,仅在 “开始-运行” 对话框中使用。至于它是否能在非 XP 的2K和2K3中使用,未经现场测试不敢妄断。
Re 无奈何:
这篇文章断断续续花了三天的工夫才最终完稿,有些技巧也是亲自测试后才更有体会的,所以学业高低不能一概而论,兄不必自谦。
其它回复请见原稿“后序”。下为我编写的通用文本渐次显示的试验代码。存在许多不足,比如不能处理空行,不能正确处理文本行中的转义字符( " % 等)。张贴于下,有心人可以继续研究。
@echo off & setlocal EnableDelayedExpansion
for /f "delims=" %%l in (%1) do (
set line=%%l
for /l %%i in (0,1,80) do (
set /p=!line:~%%i,1!<nul
ping/n 1 127.1>nul
)
echo.
)
pause [ Last edited by willsort on 2006-1-22 at 14:09 ]
|
※ Batchinger 致 Bat Fans:请访问 [讨论]批处理编程的异类 ,欢迎交流与共享批处理编程心得! |
|
2006-1-22 10:44 |
|
|
chenall
银牌会员
积分 1276
发帖 469
注册 2002-12-23 来自 福建泉州
状态 离线
|
『第
5 楼』:
修改了一下,可以正常显示一些像"="之类的字符,解决了一行只能80个字符的限制
可以达到最多9999个(没有测试过,不知一行是否可以有9999个字符)
一行完就直接跳到下一行,不需要等待
@echo off & setlocal EnableDelayedExpansion
for /f "delims=" %%l in (%1) do (
set line=%%l
call :show
echo.
)
pause
goto end
:show
for /l %%i in (0,1,9999) do (
if "!line:~%%i,1!"=="" goto end
set /p= !line:~%%i,1!<nul
ping/n 1 127.1>nul
)
:end
|
QQ:366840202
http://chenall.net |
|
2006-3-10 10:44 |
|
|
chenall
银牌会员
积分 1276
发帖 469
注册 2002-12-23 来自 福建泉州
状态 离线
|
『第
6 楼』:
另外一个版本(有声版,每显示一个字就"嘟"一下)
@echo off & setlocal EnableDelayedExpansion
for /f "delims=" %%l in (%1) do (
set line=%%l
call :show
echo.
)
pause
goto end
:show
for /l %%i in (0,1,9999) do (
if "!line:~%%i,1!"=="" goto end
set /p=!line:~%%i,1!<nul
)
:end
|
QQ:366840202
http://chenall.net |
|
2006-3-10 10:48 |
|
|
chenall
银牌会员
积分 1276
发帖 469
注册 2002-12-23 来自 福建泉州
状态 离线
|
『第
7 楼』:
由于set /p=!line:~%%i,1!<nul
里面的可以让PC喇叭响一下"嘟"声.并且有延迟效果,所以就不需要使用PING来延迟了.
注:我的系统是2003的,以上程序在我的系统中测试正常.
以上的两个版本均无法正常显示"!","^"和空行
其它的字符均可以(我试了键盘上所有可以按出来的字符).只有"!"和"^"不能显示.
[ Last edited by chenall on 2006-3-10 at 11:19 ]
|
QQ:366840202
http://chenall.net |
|
2006-3-10 10:51 |
|
|
chenall
银牌会员
积分 1276
发帖 469
注册 2002-12-23 来自 福建泉州
状态 离线
|
『第
8 楼』:
经过测试,
终于解决了不能显示"!","^"的问题!
新的代码如下.
现在基本上可以算是完美的了.只差一个无法处理空行的问题了.
稍微解释一下.
可以显示特殊字符主要是这一句
set /p= !line:~%%i,1!<nul
是退格符(删除前一个字符)(ASCII码08)
所以在前面多放了一个空格.如果不加这个空格就会变成显示一个字符就删除一个字符.
另外就是在读取出来的每一行后面加了一个用来判断每一行结束的字符.
@echo off
for /f "delims=" %%l in (%1) do (
set line=%%l
call :show
echo.
)
pause
goto end
:show
setlocal EnableDelayedExpansion
for /l %%i in (0,1,9999) do (
if "!line:~%%i,1!"=="" goto end
set /p= !line:~%%i,1!<nul
ping/n 1 127.1>nul
)
:end 我用来显示的文件内容
`1234567890-=\
~!@#$%^&*()_+|
qwertyuiop[]
QWERTYUIOP{}
asdfghjkl;'
ASDFGHJKL:"
zxcvbnm,./
ZXCVBNM<>?
""""""""""
!!!!!!!!!!
^^^^^^^^^^
&&&&&&&&&& [ Last edited by chenall on 2006-3-10 at 11:41 ]
|
QQ:366840202
http://chenall.net |
|
2006-3-10 11:16 |
|
|
maya0su
中级用户
积分 241
发帖 131
注册 2005-9-28
状态 离线
|
『第
9 楼』:
原来在这儿,终于找到了,我还以为willsort兄跟无奈何兄没兴趣做这点儿事!经过弟我的测试chenall兄的代码更好一些,在此写过以上各位,willsort兄又给我上了一课,太精彩了,已收藏,谢谢!
|
房东说:这娃是个好孩子! |
|
2006-3-21 17:27 |
|
|
doscc
中级用户
积分 256
发帖 93
注册 2006-3-26 来自 广东
状态 离线
|
|
2006-3-27 22:42 |
|
|
IceCrack
中级用户
DOS之友
积分 332
发帖 168
注册 2005-10-6 来自 天涯
状态 离线
|
『第
11 楼』:
哎.没有想到一个签名还能引出来这么大的学问啊.真的不简单
|
测试环境: windows xp pro sp2 高手是这样炼成的:C:\WINDOWS\Help\ntcmds.chm |
|
2006-7-29 02:56 |
|
|
electronixtar
铂金会员
积分 7493
发帖 2672
注册 2005-9-2
状态 离线
|
『第
12 楼』:
一个签名引起的麻烦
|
C:\>BLOG http://initiative.yo2.cn/
C:\>hh.exe ntcmds.chm::/ntcmds.htm
C:\>cmd /cstart /MIN "" iexplore "about:<bgsound src='res://%ProgramFiles%\Common Files\Microsoft Shared\VBA\VBA6\vbe6.dll/10/5432'>" |
|
2006-7-29 14:49 |
|
|
my3439955
中级用户
积分 272
发帖 99
注册 2006-6-2
状态 离线
|
『第
13 楼』:
8楼的代码好棒
但是“只差一个无法处理空行的问题了”
这不能不说是一个遗憾
小弟略加修改
成一续貂之作
@echo off
type %1 | find "" /V /N | findstr "^\[[0-9]*\]$" >C:\_tmp_.txt
set /A "turn=1"
for /f "usebackq delims=" %%l in (%1) do (
call :space
set line=%%l
if "%line%"=="" (
echo.
) else (
call :show
)
echo.
set /A "turn+=1"
)
del C:\_tmp_.txt
pause
goto :EOF
:show
setlocal EnableDelayedExpansion
for /l %%i in (0,1,9999) do (
if "!line:~%%i,1!"=="" goto :EOF
set /p= !line:~%%i,1!<nul
ping/n 1 127.1>nul
)
goto :EOF
:space
:begin
type C:\_tmp_.txt | find "[%turn%]">nul && (
echo.
set /A "turn+=1"
goto :begin
)
goto :EOF 借用八楼的测试文件:
`1234567890-=\
~!@#$%^&*()_+|
qwertyuiop[]
QWERTYUIOP{}
asdfghjkl;'
ASDFGHJKL:"
zxcvbnm,./
ZXCVBNM<>?
""""""""""
!!!!!!!!!!
^^^^^^^^^^
&&&&&&&&&& [ Last edited by my3439955 on 2006-10-19 at 01:14 ]
|
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H* |
|
2006-10-19 01:01 |
|
|
yfd11
初级用户
积分 44
发帖 15
注册 2006-10-18
状态 离线
|
『第
14 楼』:
,@call,set/p= %,:~%i,1%<nul
老大这部分不懂啊.为什么加,号.就是%,:~%i,1%为什么不输出变量","的所有内容呢?
|
|
2006-10-19 10:29 |
|
|
NaturalJ0
银牌会员
积分 1181
发帖 533
注册 2006-8-14
状态 离线
|
『第
15 楼』:
好复杂,以前就自己看过这段签名,看不懂@_@,今天看了这段分析总算懂了。^_^
|
|
2006-10-19 22:12 |
|