Board logo

标题: [转贴] 无奈何签名代码略析 [打印本页]

作者: namejm    时间: 2008-11-2 11:56     标题: 无奈何签名代码略析

原帖地址:http://www.cn-dos.net/forum/viewthread.php?tid=27430
作者:willsort
发表日期:2006-1-21

无奈何签名代码略析


  ☆开始\运行 (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
作者: batman    时间: 2008-11-2 13:05

  还是在cn-dos做新手时就拜读了此贴,当时willsort博大精深的解释真的令我受益匪
浅(虽然当时还有很多地方不是很明白),也极大地鼓舞了我学习批处理的士气。今天
再次通读全贴,自是更有了一番不同的感受,真可谓是:

  好贴恒久远,精髓永流传。

  前辈意犹尽,后人亦思全。

作者: batman    时间: 2011-5-28 19:37

再阅经典,感慨万分。。。willsort老大你在哪里?




欢迎光临 批处理之家 (http://bbs.bathome.net/) Powered by Discuz! 7.2