Windows 批处理脚本学习教程

Windows 批处理脚本学习教程

十一月 24, 2020

Collected by Clove

第一章:前言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
1.1 什么是批处理?

从内容上看,批处理文件包含了大量的基本DOS命令,是一种可执行文件。该文件运行时能按照其规则将其中的命令逐一执行。使用批处理文件进行的批量的命令处理的过程,称之为批处理。
批处理文件(Batch File,简称BAT文件)是一种在DOS下最常用的可执行文件。它具有灵活的操纵性,可适应各种复杂的计算机操作。所谓的批处理,就是按规定的顺序自动执行若干个指定的DOS命令或程序。即是把原来一个一个执行的命令汇总起来,成批的执行,而程序文件可以移植到其它电脑中运行,因此可以大大节省命令反复输入的繁琐。同时批处理文件还有一些编程的特点,可以通过扩展参数来灵活的控制程序的执行,所以在日常工作中非常实用。[1]

批处理文件的后缀名为.bat(Batch的缩写)。可以用绝大多数的文本编辑工具进行编辑。由于批处理文件的实质就是一个命令的集合,所以批处理文件的工作平台是由这些命令所工作的平台来决定,如今Microsoft环境下的批处理文件(.bat和.cmd文件)的平台,当然也就是Microsoft的DOS和Windows系统。[1]

1.2 了解与使用基本DOS命令

说到批处理,我们不得不先讨论一些基本的DOS命令。对于“命令”("Command")一词,实际上就体现了我们与计算机的交流过程。比如,您可以通过使用鼠标点击“开始”,选择关机来命令计算机执行关闭计算机的行动;您也可以通过只是简单地双击一下桌面上的《星际争霸》快捷方式来让计算机运行《星际争霸》这款经典的即时战略游戏。也许您认为这些命令就如同吃饭睡觉一样简单,这是因为图形化界面已经为我们把复杂难以理解的命令操作封装成人人都能非常容易上手的操作方式,使得我们只需要点点鼠标或是敲几下键盘就能轻而易举地完成。

而DOS命令,对于不少新接触计算机的朋友来说可能是一个高深而神秘的词。不过,如果您多少还懂点英语的话,这些问题可能就变得是很容易被理解的。假如我们打算把电脑D盘里的一个叫做"a.txt"的文本文件复制到E盘里,您也许能够提出使用Ctrl+C复制该文件,然后再在E盘中Ctrl+V将其粘贴到此处;或是建议按住Ctrl键然后直接拖动到E盘,又或是用右键拖动后再选择,等等。这些都是图形界面里的操作,如果我们想用DOS命令来实现这一操作该如何去做呢?copy D:\a.txt E:\a.txt 这句英文看起来更像是在尝试与电脑进行聊天。不过您必须得保证您的发言有着严格正确的格式,电脑才会按照您的意图去执行。以下为常用文件操作DOS命令:

dir    列文件名
cd    改变当前目录
ren    改变文件名
copy   拷贝文件
del    删除文件
md    建立子目录
rd    删除目录
deltree  删除目录树
format  格式化磁盘
edit   文本编辑
type   显示文件内容
mem    查看内存状况
以下是新增加的命令
help   显示帮助提示
cls    清屏
move   移动文件,改目录名
more   分屏显示
xcopy   拷贝目录和文件
[2]

仅仅是告诉您有哪些命令可以使用,此时您仍不会明白DOS命令的操作用法。我们不妨从例子中学习并理解。

可以在 开始->所有程序->附件->命令提示符 找到并打开MS-DOS命令提示符。或是按WIN键(键盘上有微软标示的那个按键)再按R键,然后输入 cmd 并按回车,也能打开命令提示符。无论您对这个黑色而单调的界面感到是多么的新奇、或是陌生、甚至是厌恶,此时您都不会再计较了,重要的是您得尝试一下所谓的DOS命令。如果您正在用Windows XP操作系统(XP自带的DOS5使用方便,便于理解,以下均以XP操作系统为例进行说明),您将可能看到有类似 C:\Documents and Settings\Administrator> 的这么一行文字。这说明,您此时的工作位置就在 C:\Documents and Settings\Administrator 里。当然 Administrator 也可能是别的词,这取决于您当前登录的用户名以及该用户所设定文件夹的名称。而使用 Windows9X 操作系统的命令提示符将会看到经典的 C:\WIDNOWS> 。

此时我们来尝试一下使用一些基本的DOS命令,
比如,键入 dir (不区分大小写)并按回车后,我们会得到当前文件夹下所有的子文件夹和文件的相关信息。输入 dir c:\windows ,可以查看指定的路径文件夹(这里假定我们指定的文件夹是C盘的WINDOWS文件夹)中的信息。在输入该命令后,只见刷地一下啥也没看清,N多文件或文件夹就已一闪而过,因为一个屏幕无法显示这么多的文件或文件夹。好在在DOS5中我们可以用鼠标滚轮向前滚动查看更多的内容。然而,早期的DOS版本并不具有保存多页信息的功能,我们不妨使用 dir 的一些参数(适当的参数加在相应的命令后面可以实现更多的扩展功能),比如 /p ,即输入 dir c:\windows /p 。这样就能在每显示一个屏幕的内容后暂停一下以便查看。

关于 dir 命令的用法和参数还有更多。除了 dir 以外,每一种命令的用法和参数都不少。看到这里您也许会觉得很沮丧,认为自己没有天赋、也没有工夫来死记硬背这些该死的命令。事实上我也有同感。命令的具体用法在帮助里、在网上都能轻而易举地找到。只要输入 dir /? 就能得到关于dir命令完整的使用介绍,而且是中文的。同理,任何命令后面跟上 /? 的参数,都能得到该命令的完整说明。当然,也可以在网上搜索"DOS命令"等关键字来查找具体的DOS用法(推荐一个洪恩在线)。我们所需要的只是知道执行什么操作时用什么命令就行了,即使是连命令的名称都记不得了也没关系,都是现学现用的嘛。

很多情况下,我们只需要记住一条命令 help ,就能掌握整个DOS命令。比如直接输入 help 可以得到命令的帮助信息。输入 help dir 就能得到命令 dir 的具体参数及其用法(等同与 dir /? )。不仅仅是DOS命令,很多带有命令提示的工具都有help提供帮助和提示,例如MATLAD中的命令行,又如BattleNet中的以斜杠 / 开头的命令等等。

下面以一段例子来介绍DOS命令的具体用法(灰色背景的文字为DOS命令,可以尝试在命令提示符中输入测试查看效果)。

d:
转到驱动器D盘下,此时我们能看到 D:\> 的提示符(前提是您的电脑硬盘必须至少划分出D盘)。如果看到的不是就再输入下一行命令。
cd\
这一条命令 cd 的作用是改变当前的工作目录,后面加上 \ 表示返回到该驱动器的最顶级目录。另外,一个句点 . 表示当前文件夹,两个连续的句点 .. 则表示上一级文件夹, cd.. 即目录向上一级。
md test
正如前文所说,命令 md 的作用是创建子目录,此时打开D盘看一看,是不是多了一个名叫test的文件夹(如果不是事先早已存在的话)。事实上该命令的完整写法为 md d:\test ,由于当前目录已经在D盘了,所以我们把具体的路径给简化了。
md "test my folder"
同理,在D盘创建一个名为test my folder的文件夹。加双引号的目的是告诉计算机我们要创建一个名字中含有空格的文件夹,而不是分别创建名字分别为test、my和folder的3个不同的文件夹。注:除了空格以外,在路径或文件名中含有 &()[]{}^=;!'+,`~ 特殊字符时也需要用双引号引用起来,以便机器能够正确地识别。
cd test
改变当前工作目录,此时不再是D盘了,而是在 D:\test 的目录下。
echo Hello world>a.txt
遇到了一个新命令 echo ,它可以将某某内容显示出来。只考虑 echo Hello world 就是把字符串 Hello world 显示出来而已。后面使用了符号 > 表示将这句 Hello world 写入到某文件中。结果为:在 D:\test 文件夹里多了一个叫 a.txt 的文件,其内容为 Hello world 。
copy a.txt "d:\test my folder"
这就是将当前文件夹里的那个 a.txt 文件复制到 D:\test my folder 文件夹中
copy a.txt "..\test my folder\b.txt"
还是复制 a.txt ,但这次的路径与上一条命令的写法不同。 .. 表示先向上一级,再挪到 test my folder 文件夹里。其实还是复制到 D:\test my folder 这个文件夹里了。这次复制过去的文件名也不一样,因为这次我们指定了要复制过去的目标文件名为 b.txt 。
copy a.txt "..\test my folder\c.bmp"
再次复制 a.txt 到同样的文件夹里。不过这次不光改文件的标题名了,连文件的后缀名也改了。打开 D:\test my folder 文件夹检查一下,是不是多了名字分别叫 a.txt b.txt c.bmp 的3个文件。
cd..
向上一级
cd "test my folder"
进入 D:\test my folder 文件夹
ren c.bmp d.bin
重新命名文件 c.bmp 为 d.bin 。完整地写法为 ren "d:\test my folder\c.bmp" d.bin 。
del *.txt
删除当前工作目录中所有文件后缀名为 .txt 的文件。 * 表示通配符。例如:a.* 表示所有文件标题为 a 的文件,不论后缀名。 *.* 则表示任何标题名和任何后缀名的文件,即所有文件。此时结果应为:该目录里的 a.txt b.txt 这两个文件已经不存在了,即使是在回收站里也找不到了。
cd..
向上一级
rd test "test my folder"
这一条命令是想同时移除 test 和 test my folder 这两个文件夹。不过结果并没有成功,因为它会提示:目录不是空的。test 文件夹中有 a.txt ,test my folder 文件夹中还有 d.bin ,因此文件夹删不掉。
rd test "test my folder" /s
如果加上 /s 这个参数后就能删除这两个文件夹以及其文件夹中所包含的所有内容了。注意:删除前请确认这些文件夹是否是您以前就有的文件夹,或者说里面是否存有任何有用文件,不要因为这个测试而丢掉了重要文件。

1.3 我们的第一个批处理

在阅读本文之前您可能还对此一无所知,而现在您却已经略知一二了。如果您还无法理解DOS命令的工作原理或方式,在继续阅读下文之前,强烈建议具体操作实践一下。

不过说了那么多,看起来似乎还没进入正题,您可能会不耐烦地说:目前批处理连个影子都没见到呢。如果我们只是简单地将上述例子中的命令集合起来,那么这就形成了一个批处理。做法是:先打开记事本,把下面这些您已经了如指掌(或者说只是略知一二)的命令复制进去。然后保存到某处(比如桌面),并命名为 MyFirstBatch.bat (文件标题可自拟,但后缀名必须是.bat)。

::::::::::::::::::::::::::::::::
md "d:\test" "d:\test my folder"
echo Hello world>"d:\test\a.txt"
copy "d:\test\a.txt" "d:\test my folder"
copy "d:\test\a.txt" "d:\test my folder\b.txt"
copy "d:\test\a.txt" "d:\test my folder\c.bmp"
ren "d:\test my folder\c.bmp" d.bin
::::::::::::::::::::::::::::::::

看到了吧!惊喜吧!疯狂吧!一个自己写的,或者说至少是自己已经能够完全理解的批处理文件 MyFirstBatch.bat 就这样诞生了。或许此时您对这东西能否正常工作还持有怀疑态度,但是在看到对该批处理文件双击运行完的结果以后,先前或多或少的怀疑也就荡然无存了……

双击后,显示的只是一闪就关闭了。正确的结果是在D盘多了名字分别为 test 和 test my folder 的两个文件夹。文件夹 test 里有一个叫 a.txt 的文件。而文件夹 test my folder 里有名字分别为 a.txt b.txt 和 d.bin 的3个文件。

第二章:显示篇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
有了前文作铺垫和基础,后面的便容易理解多了。这里主要讨论的是批处理过程中与显示相关的命令用法。先看一下 echo、@、pause、>、>>、title 、rem 这几个命令或符号的用法。

2.1 echo @ 和 pause

在DOS命令提示符中使用 echo /? 可以获得对 echo 用法的解释。echo on 用于打开命令的回显;echo off 用于关闭命令的回显(默认情况下,ehco 是处于打开状态的)。只输入 echo 可以获得当前的回显状态(是否处于打开状态)。输入 echo 再加一段文字,例如 echo Hello world! 可以显示出 Hello world! 这句信息。

@ ,如果在某一条命令最前面加上 @ ,那么这一行命令就不会显示出来。与 echo off 有着相似之处。 echo off 以后的所有命令本身都不再显示出来;而 @ 只是将当前那一行的命令不显示出来。然而,至于命令所产生的输出结果,仍然会显示出来。这看起来似乎有些拗口,但我们会通过例子来很容易地理解它们。

pause ,从字面上看就是暂停的意思,效果等同于将程序挂起,在按下任意键后才继续。

::::::::测试显示状态.bat::::::::
echo
pause

echo 例句一 此时回显为打开状态,因此前一句显示了命令行
@echo 例句二 此时回显虽然为打开状态,但命令前使用了@,因此未显示命令行本身
pause

echo off
echo
echo 例句三 此时回显为关闭状态,因此未显示命令行本身
@echo 例句四 此时回显为关闭状态且使用了@,因此未显示命令行本身
pause
::::::::::::::::::::::::::::::::

上面的这一段批处理测试,有效地展示了在使用 echo on 和 echo off ,以及在命令前加上 @ 符号后,命令行本身的显示效果。

2.2 > 和 >>

> 表示将输出结果打印到某处。比如:echo Hello world!>d:\a.txt 表示将 Hello world! 这句话写入到 D:\a.txt 文件中。如果以前该文件中已经存在,并且有自己的内容,那么以前的内容就被覆盖掉了。比如我们再输入:echo yo, whats up>d:\a.txt ,那么文件 a.txt 中以前的 Hello world! 就变成了现在的新例句。

>> 与 > 类似,也可以将输出结果打印到某处。比如我们用 echo nothin much, and u?>>d:\a.txt 将例句写到 a.txt 里时,该例句并不会覆盖原有的 yo, whats up 这句话,而是加在了原句的后面。

如果一条命令后面跟上 >nul ,比如 pause>nul 表示将 pause 这条命令的输出显示到空设备里, nul 表示为空。用了 pause>nul 这条命令后,"按任意键继续..."的提示就不再出现了。

对比 echo off、@ 和 >nul 。echo off 表示这以后的所有命令的本身不再显示了,直到后面有 echo on 的出现。而加在命令行前面的 @ 只是让当前这一行命令不显示。加在命令行后面的 >nul 却可以让该命令的输出不显示。

2.3 title 和 rem

title 后面跟字符串可以改变当前命令提示符的标题名称。输入 title 这是新标题 后,该命令提示符左上角的标题名称已经变为"这是新标题"了。输入中文可以通过 Ctrl+空格 切换出中文输入法;也可以通过复制粘贴的方式输入。

rem 的用法就很简单了,rem 后面跟上一段文字,在批处理中可以作为注释用。rem 和它后面跟的文字在实际运行时并不会起任何作用,只是为了方便人们阅读该批处理时更容易理解而已(如果您用过C的话,一定会联想到C语言里的 // 或 /* */ 的用法)。除了 rem 外,两个连续的冒号 :: 也起同样的作用。提示:rem 与 :: 的区别在于,rem 也是一种命令,在 echo on 的情况下会被显示出来,而 :: 却不会。

:::::::测试标题和注释.bat:::::::
@echo off
rem 上条命令表示以后所有的命令行不再显示自身,@表示连echo off这一句都不显示,当前这一行只是注释而已,不参与程序的运行。

echo 欢迎!
pause

title 现在标题已经换成这句了
echo 标题已更改

echo 现在使用了暂停,按任意键后该批处理结束~
pause>nul
rem 不显示pause的输出提示,而是使用我们自己定义的暂停提示。
::::::::::::::::::::::::::::::::

2.4 其他命令

prompt ,这就是命令提示符中所谓的"提示符"了。在命令提示符中输入 prompt 加一段文字能够使得提示符不再是以传统的路径名和大于号组成的,而是以我们刚才输入的那段文字开头的。这也许不是很好理解,或者您对 prompt 的含义还不清楚或只知道其字面含义。这并不要紧,如果您只要简单地输入 prompt 提示符 就能很快地明白 prompt 的含义了。此外,要想恢复以前的路径名和大于号为开头的提示符,只需要再输入 prompt $p$g 即可。这里$p 表示当前驱动器和路径, $g 表示大于号。因为一些特殊的格式或符号需要用 $ 加特定的字母来表示。具体的说明可以用 help prompt 来查询。

第三章:赋值 调用 参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
3.1 赋值

3.1.1 给变量赋予一个文字字符串的值

说到赋值,就得先弄懂 set 这条命令。set 这条命令比较复杂,在命令提示符中键入 set /? 后得到的帮助信息也很多。不过,简单地说,使用 set 跟上变量,再用等号 = 跟上字符串就能简单地给该变量赋值了。例如 set var=Hello world! 。为了确认一下变量 var 的值是否是 Hello world! ,可以用 set var 来查看变量 var 的值。用 set v 可以查看所有以字母 v 开头变量的值。直接输入 set 可以查看所有变量的值。另外,变量两侧加上百分符号 % 用来表示该变量的值(内容)。这样做可以将该变量的值赋给其他变量或是用做计算显示等处理。

3.1.2 给变量赋予一个数值型的值

set 后面加上 /a 的参数可以给变量赋予一个数值型的值,例如 set /a var=48 表示将数字48赋给变量var。该数值型的变量是一个32位的整数型数值,即占用4个字节,能表示的数值个数为2的32次方,含正负号,范围为:-2147483648~2147483647。

3.1.3 从外部获得输入的赋值方式

set 后面加上 /p 的参数,可以将变量设成用户输入的一行输入。读取输入行之前,显示指定的 提示文字。当然,提示文字也可以是空的。比如 set /p var=请输入一些文字: ,可以显示出一段提示文字"请输入一些文字:"并能将用户输入的信息存到变量var里。/p 的参数还有很多诸如对字符串的替代、提取、增减等功能,具体可以参考 set 的相关帮助信息。

3.1.4 变量的值的赋予、显示、变换、计算等功能

可能此时有些朋友对百分号 % 的理解还处于迷茫状态,对此我们可以做一些实验。首先,就像前文所说的那样,给一个叫做 var 的变量赋值Hello world! (在命令行里输入 set var=Hello world! )。然后我们的打算把变量 var 里的值赋给另一个变量 var1 ,做法是:set var1=%var% ,此时 var1 里的值也是 Hello world! 了。假如不使用百分号 % 仅仅是 set var1=var 的话,那么此时变量 var1 所得到的值仅仅是 var 这3个字母而已。再回顾一下 echo 的用法,分别尝试输入 echo var1 和 echo %var1% ,所得到的返回输出分别为:var1 和 Hello world! 。

此外,百分号可以对变量中的字符串有效地进行编辑或变换。
值得一提的是替换功能,其格式为:原始变量的名称后面跟上冒号 : ,再加上想要被代替的内容,紧接着一个等号 = ,然后再加上用来代替的新内容,最后用两个百分号把以上这些包括起来即可。虽然此时原始变量的值并没有改变,但百分号里的内容可以赋给一个变量,这个变量可以是原始变量。例:echo %var:o=z% ,效果为把 Hello world! 里所有的字母 o 都用字母 z 代替,并显示出来,然而变量 var 的值却没有变化。
当然,我们并不会满足于仅仅是代替一个字母——有时候我们需要代替两个。set var2=%var:ld=ms and bugs% ,这条命令可以在把 Hello world! 里的 ld 替换成 ms and bugs 并将新的结果赋给变量 var2 ,变量 var 仍然不会变化。输入 echo %var2% 确认一下结果是否为我们所期待的 Hello worms and bugs! 。

对变量中的字符串在特定位置上的删减将用到这样的格式:%var:~m% 和 %var:~m,n% 。m 和 n 为整数参数。数字 m 为正数表示取变量 var 中从左侧数第 m 个字符(单字节字符)以后的内容;m 为负数则表示取变量 var 从右侧数第 -m 个字符以及其右侧的所有的字符,这就是第一条命令所产生的新字符串。如果数字 n 为正数,表示在上述新字符串中,从其左侧取 n 个字符的内容;若 n 为负数,则从其左侧取字符直到还剩下 -n 个字符为止的内容。

如果您坚持认为这种抽象的表达方式是根本无法解释清楚这该死的 m 和无耻的 n 究竟是怎么回事的话,不如实验一下下面的例子。为了方便查看效果,我们假定变量 var 中的内容为 1234567890 (set var=1234567890),然后依次输入以下命令并查看相应的结果。
输入的命令    结果    效果                   
echo %var%    1234567890 显示所有                 
echo %var:~4%   567890   从第4个字符以后开始显示         
echo %var:~4,3%  567     从第4个字符以后开始显示,并只显示前3个  
echo %var:~-4%  7890    从倒数第4个字符开始显示         
echo %var:~-4,3% 789     从倒数第4个字符开始显示,并只显示前3个  
echo %var:~4,-2% 5678    从第4个字符以后开始显示,显示到还剩2个为止
echo %var:~0,3%  123     从头开始显示,并只显示前3个字符     
echo %var:~0,-3% 1234567   从头开始显示,显示到还剩3个字符为止   

此外,set 也可以对数值型的变量进行常见的运算操作。
set /a num=48
set /a result=%num%+12
echo %result%
上面的命令表示先给数值 48 赋给变量 num ,然后再把变量 num 的数值与数值 12 相加后的结果赋给变量 result 。第3行可以显示变量 result 的值,结果很明显,是 60 。

3.2 调用

与很多编程类的东西一样,批处理并不一定非得按照文本中命令的排列顺序一行一行地执行。如果遇到了 goto、call、start 这样的跳转、调用、启动等语句,程序通常会变得多层化,执行起来会更加有效。

3.2.1 跳转

goto 对于多少有点编程基础的朋友来说,想必不是一件难以理解的东西。goto 跟上标签就能直接让程序从该标签处开始继续执行随后的命令,不论标签的位置是在该 goto 命令的前面还是后面。标签必须以单个冒号 : 开头,但不区分大小写。有个特殊的标签 :EOF 或 :eof 能将控制转移到当前批脚本文件的结尾处,它是不需要事先定义的。
::::::::::::跳转.bat::::::::::::
@echo off
goto :FirstLable

:SecondLable
echo 然后显示这句
pause
goto :EOF

:FirstLable
echo 首先显示这句
pause
goto :SecondLable
::::::::::::::::::::::::::::::::

3.2.2 调用

call 主要体现在两个方面:一是调用该批处理以外的另一个批处理(事实上调用该批处理本身也可以,只是可能会带来不必要的死循环);另一方面是有着与 goto 类似的向特定标签处跳转的功能。然而,call 的独特之处在于:在调用的批处理或标签后的内容处理完成以后,控制会继续执行 call 后面的语句。我们先来看一下用 call 进行跳转的效果。为了方便对比,我们将上面的批处理作如下修改(浅色文字为修改部分)。
::::::::::::跳转.bat::::::::::::
@echo off
call :FirstLable

:SecondLable
echo 然后显示这句
pause
goto :EOF

:FirstLable
echo 首先显示这句
pause
::goto :SecondLable
::::::::::::::::::::::::::::::::
在用 call 跳转到 :FirstLabel 处执行到程序结尾后(此时 call 的任务才刚刚完成),会继续回到 call 语句后的 :SecondLabel 处。假如 goto :SecondLabel 这一句没有被注释掉的话,那么控制会跳转到 :SecondLabel 处直到 goto :EOF 处 call 的使命才真正完成。而且,call 在完成任务后,下面的 :SecondLabel 处内容会再次执行一遍。

当 call 作为调用其他新的批处理的用途时,当前批处理就会暂停,直到新的批处理结束后,之前的批处理才会继续执行。例如:直接调用当前路径里的一个批处理 call test.bat ,或是要调用的批处理在当前路径向上一级的abc文件夹里 call ..\abc\test.bat ,也可以使用绝对路径找到目标批处理 call D:\abc\test.bat (路径的写法请参阅[前言]里命令 cd 的用法介绍)。当然,一个最直观的例子往往更能说明问题。建立两个批处理(假设他们在同一路径下)"调用.bat""被调用.bat",它们的内容分别为:
::::::::::::调用.bat::::::::::::
@echo off
echo 这里是 调用.bat
pause

call 被调用.bat

echo 现在又回到了 调用.bat
pause
::::::::::::::::::::::::::::::::

:::::::::::被调用.bat:::::::::::
echo 这里是 被调用.bat
pause
::::::::::::::::::::::::::::::::
编辑并保存好这两个批处理,然后运行批处理"调用.bat",所得到的结果跟我们预期的完全一致。

3.2.3 启动

start 虽然也不是一个简单的命令,但用法绝对不难理解。来几个例子:start msconfig 用来打开"系统配置应用程序";start notepad 则可以打开一个记事本;start "这就是所谓的标题" cmd 用来打开一个新的命令提示符;start "随便写个标题" http://www.baidu.com 便打开百度的首页;而 start "开玩了" E:\game\starcraft\starcraft.exe 却是开始星际争霸(如果您的电脑里安装了星际且路径与上述一致的话)等等。虽然 start 的参数很多(具体用法在输入 help start 后可以得到),但通常情况下我们只需要知道 start 后面加上标题,再跟上想要执行程序、命令或网址即可。值得注意的是:标题要用双引号引用起来,否则会被作为可执行的文件来处理;所要执行的东西如果不是系统内部程序或命令的话,则需要我们给出具体的路径,比如绝对路径。
当然,start 也可以打开另外一个批处理。这看起来似乎与 call 相仿,却有一些区别。为了对比 call 和 start 之间的效果差异,可以在上一个例子中修改"调用.bat"中的 call 被调用.bat 为 start 被调用.bat 。从"调用.bat"开始执行后,"被调用.bat"也的确能够被调用。但之前的批处理也同时继续执行着。事实上,此时这两个程序已经完全独立开了,是两个独立的进程。

3.3 参数

上文提到了调用,这里就不得不补充一下参数。何谓参数?假如您要使用命令 dir ,那后面所跟的路径文件名也好、/p (分页显示)也好、/w (宽列表显示)也好、又或者是 /s (所有目录及子目录)也好,这些都能被称作为参数。对于一条命令来说,它除了能返回(输出)给我们一些信息以外,我们还经常给它输入一些信息,被输入的信息就是参数;然而,对于一个批处理来说,它除了能为我们工作(输出结果)以外,有时也需要外界给他一些信息。

3.3.1 参数的传递

为了很好的认识到这一点,我们先稍微修改一下上文所使用的"被调用.bat"这个批处理文件,使之能够接受处理外界给它的输入参数。
:::::::::::被调用.bat:::::::::::
echo 这里是 被调用.bat
echo 您输入的第1条参数为 %1
echo 您输入的第2条参数为 %2
pause
::::::::::::::::::::::::::::::::
其中,%1 和 %2 分别代表运行"被调用.bat"批处理时所跟的两个参数。那么它该如何获得所谓的参数1和参数2呢。双击运行"被调用.bat"当然是不行的了。可以在命令提示符里输入"被调用.bat"的全名,并在后面加上两个参数即可(如果"被调用.bat"不在当前工作路径,需要输入"被调用.bat"的路径,比如绝对路径,以便让计算机找到它)。就像:D:\批处理\test\被调用.bat Tom and Jerry 。运行时我们会发现 %1 和 %2 分别显示为 Tom 和 and ,Jerry 为作为第3个参数来处理,但该批处理中却未用到 %3 。提示:在XP等操作系统中,对于汉字的输入可用 Ctrl + 空格 切换出中文输入法;也可以按 Tab 键让其自动切换并补充完成您所想要输入的路径。

为了进一步验证这一点,我们再改一下批处理"调用.bat",让它在调用其他批处理时加上参数
::::::::::::调用.bat::::::::::::
@echo off
echo 这里是 调用.bat
pause

call 被调用.bat Hello world!

echo 现在又回到了 调用.bat
pause
::::::::::::::::::::::::::::::::

3.3.2 参数的输入与输出

参数不仅可以是值,也可以是变量。如果是变量的话,它既可以作为输入,也可以作为输出。为此,我们需要分别再改一次"调用.bat""被调用.bat"

::::::::::::调用.bat::::::::::::
@echo off
echo 这里是 调用.bat
pause

set /p Num=请输入一个整数:
set Square=

call 被调用.bat Num Square

echo 现在又回到了 调用.bat ,而且,%Num% 的平方是 %Square% 。
pause
::::::::::::::::::::::::::::::::

:::::::::::被调用.bat:::::::::::
echo 这里是 被调用.bat
echo 您输入的第1条参数为 %1
echo 您输入的第2条参数为 %2

set /a %2 = %1 * %1

echo 经过计算后,您输入的第1条参数为 %1
echo 经过计算后,您输入的第2条参数为 %2
pause
::::::::::::::::::::::::::::::::

可以看出,在批处理"被调用.bat"中的 call 被调用.bat Num Square 里,变量 Num 和 Square 被传递过去的只是变量名而已,而不是变量中的数值。另外,尝试将"调用.bat"中的 call 被调用.bat Num Square 换为 call 被调用.bat %Num% Square ,然后再对比一下结果,相信您一定会有所收获。

3.3.3 函数

在一个批处理的内部调用时,使用参数会怎么样呢?那么这就是函数的雏形。把之前的"跳转.bat"稍微也改一下
::::::::::::跳转.bat::::::::::::
@echo off
call :FirstLable 很好很强大

:SecondLable
echo 然后显示这句
pause
goto :EOF

:FirstLable
echo 首先显示这句,后面跟的参数为 %1
pause
::::::::::::::::::::::::::::::::

对于 goto 所跟的标签或 start 所跟的程序,它们后面能否加参数呢?答案是:前者是否定的;后者是肯定的,试试看。

3.4 后记

写了那么多天,也许我们应该来点更实际更有用的东西。前几天无意中发现了一个163邮箱登录器,在此作为例子介绍一下。

:::::::163邮箱登录器.bat::::::::
@echo off
::不显示命令行
mode con cols=32 lines=8
::设置窗口的尺寸
title 163邮箱登录器
::设置该批处理的标题
color 3a
::设置背景(暗靛色)和文字(亮绿色)的颜色。
::另外,在命令提示符中输入 color/? 可获得详细的颜色信息

set /p username=帐号:
set /p password=密码:
::给出输入的提示,并将字符串赋给相应的变量

start "正在登录邮箱" "https://reg.163.com/logins.jsp?username=%username%&password=%password%&url=http://fm163.163.com/coremail/fcg/ntesdoor2"
::事实上,那个网址链接才是整个程序的核心部分,注意其中的 %username% 和 %password% 分别是指变量 username 和 password 里的字符串内容。
::::::::::::::::::::::::::::::::

第四章:条件 循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
4.1 条件 if

4.1.1 if 是一种极其普遍却又非常重要的语句,说得严重点这就是一种能够体现程序灵魂的东西之一。在大多数的编程语言(例如 C VB JScript Java 等)中都能看到 if 的身影。if 语句的功能正如它的字面含义一样——如果。批处理程序的语言格式相比较我们常见的 C 语言来说,并不是那么的严谨,至少看上去是更自由一些。比如 if 在批处理中的具体用法及格式就有很多,使用和发挥的余地也很大,但随之带来的问题就是我们不得不多花一些时间来记忆其各种用法和格式并分辨它们之间的差异。为了简化该问题使之更容易理解,在此我们并不打算过早地接触 if 的全面用法或格式,而是从最基本的用途开始。

4.1.2 在有前几篇文章的学习作基础的情况下,如果您能理解以下几行语句,那么您已经对 if 有了深刻的认识了(其中两个连续的等号 == 表示:是否等于)。
set var=Tom
if %var%==Tom echo It works
if %var%==Jerry echo We will never see this
如果变量 var 的值为 Tom Hanks ,即中间含有空格之类的特殊符号,那么我们在使用 if 时,就得为字符串加上双引号,就像 if "%var%"=="Tom Hanks" echo It works (注意,给字符串加上双引号后,在进行判断的时候会连双引号一起考虑进去。所以,为了使两边的对比均衡,所以一定要在 == 两边的两个字符串上同时都加双引号)。这里也体现了批处理程序语言格式的多样性(如果您熟悉 C 语言格式的话,就知道一串字符总是要被双引号引起来)。不过为了方便记忆,我们在使用 if 的时候,不妨总是在字符串上使用双引号,这样既好阅读,又不容易引起歧异。

::::::::::改变颜色.bat::::::::::
@echo off
echo 您希望字体的颜色是红色还是绿色?

:RETRY
set /p choice=请输入您的选择,R 或者 G :

if "%choice%"=="R" goto R
if "%choice%"=="r" goto R
if "%choice%"=="G" goto G
if "%choice%"=="g" goto G
goto RETRY

:R
color c
echo 您选择了红色字体
pause
exit

:G
color a
echo 您选择了绿色字体
pause
exit
::::::::::::::::::::::::::::::::
上面的例子能很好地说明了 if 与 goto 的结合使用带来的实用效果。其中,在使用 if 进行判断变量 choice 的值是否为字母 R 或 G 时,分别对其大小写都进行了判断。其实您还可以使用 if 的参数 /i ,就像 if /i "%choice%"=="r" ,这样在进行比较的时候就不区分大小写了。

4.1.3 说到了 if 就不得不说一下 elseelse 无法单独使用,必须与 if 配合连用。
:::::::::else的用法.bat:::::::::
@echo off

if "%TIME:~0,2%" lss "12" (
echo 现在是上午
) else (
echo 现在是下午
)

pause
::::::::::::::::::::::::::::::::
其中,变量 TIME 是动态环境变量之一,表示当前时间(在 set/? 中有介绍)。%TIME:~0,2% 的含义还没忘记吧,意思是取变量 TIME 的前两个字符(忘记的朋友请参阅上一篇[赋值 调用 参数])。lss 是 if 命令扩展用法,表示 小于 的意思。此外,还有等于、不等于、大于、大于等于、小于等于 的缩写,详细信息可以在 if/? 中获得。因此,对于上述批处理的理解就是:如果当前时间(前两位表示小时)小于12(点)的话,那么将显示输出“现在是上午”,否则就显示为“现在是下午”。另外:这里的大小比较判断只是对其ASCII符的大小比较,并不是真正的数值型变量的比较,稍后下文会有关于数值型变量比较的介绍。

对于 ifelse 的编写格式有较严格的要求,尤其是在两个圆括号上,若以不正确的格式使用可能会导致 ifelse 等命令无效。虽然上面的编写格式并不是唯一的,但使用统一、固定的格式编写代码会大大提高代码的可读性。为了加深对 if else 的理解,我们可以把上面的批处理扩展一下。
:::::::::else的用法.bat:::::::::
@echo off

if "%TIME:~0,2%" lss "12" (
if "%TIME:~0,2%" lss " 6" (
echo 现在是凌晨
) else (
echo 现在是上午
)
) else (
if "%TIME:~0,2%" lss "18" (
echo 现在是下午
) else (
echo 现在是晚上
)
)

pause
::::::::::::::::::::::::::::::::

4.1.4 刚才提到的数值型变量的比较,其实很简单,就如下面的例子中描述的一样。
::::::::::::::::::::::::::::::::
@echo off
set /a num=5

if %num% == 5 (
echo 变量 num 等于 5
)

if not %num% == 4 (
echo 变量 num 不等于 4
)

set /a num = ( %num% + 3 ) * 2
:: 变量 num 加3并乘2后再赋给变量 num 自身

if %num% == 16 (
echo 经过运算后,现在变量 num 等于16
)

if not %num% == 16 (
echo 此时的变量 num 不会不等于 16 ,因此这一句不会显示了
)

pause
::::::::::::::::::::::::::::::::

4.1.5 延迟变量扩充。考虑到读取一行文本时所遇到的目前扩充的限制时,延迟变量扩充是很有用的,而不是在执行的时候。下面的例子可以很好的说明直接变量扩充与延迟变量扩充的区别。
::::::::延迟变量扩充.bat::::::::
@echo off
setlocal EnableDelayedExpansion

set /a num=5

if %num% == 5 (
set /a num*=3

echoif 语句之前,变量 num 等于 %num%
echo 但变量 num 在经过运算后,且由于延迟变量扩充被启用,变量 num 等于 !num!
)

echo 但最终变量 num 还是等于 %num%

pause
::::::::::::::::::::::::::::::::
if 条件下的两行 echo 在输出变量值的时候用到的符号不一样,一个是用百分号 % 包括起来的,另一个用的却是惊叹号 ! 。虽然在显示 %num% 之前已经使变量 num 的数值乘了3倍,但是由于没有延迟变量的扩充,使得 %num% 的结果仍然是 5 。但用 !num! 显示出的值已经变为 15 了。注意到批处理中的 setlocal EnableDelayedExpansion (setlocal/? 查看相关信息),这表示开启延迟变量扩充。此时的 !num! 才有意义。不然 !num! 将无法被识别,因为在默认情况下,延迟变量扩充是被停用的。

4.1.6 此外,if 还有其他的用法—— if exist 和 if defined 。if exist 可判断文件是否存在,就像这样:
if exist "D:\test my folder\a.txt" (
del "D:\test my folder\a.txt"
) else (
echo 您所要删除的文件不存在
)
在对文件进行操作之前进行判断其是否存在很有意义,这使得代码更加健壮。

而对于 if defined 来说,与 if exist 类似,只不过 if defined 的判断对象不是文件,而是变量,它用于判断环境变量是否被定义。

4.2 循环 for

4.2.1 如果批处理不具备批量处理的功能,那么它就徒有虚名了。而命令 for ,在某种意义上彻底体现出了批处理的强大快捷省事批量的作用。在看过 for/? 后,可以归纳出 for 大致可以分三种常用的类型(或者叫使用方法)。从针对的循环目标来看,它们分别是针对于文件、数字、以及文字。

4.2.2 for %i in (*.*) do @echo %i 。这就是 for 的一般使用格式。注意到其中的浅靛色文字 forindo ,是 for 的固定用法。其内容可以理解为:在某一范围内(in),对于其中的某一文件来说(for),做如下的处理(do)。而 for %i in (*.*) do @echo %i 就是在当前工作目录的所有文件中(in (*.*)),对于其中的某一文件(for %i),做出显示其名称的处理(do @echo %i)。变量 i 仅在当前循环语句 for 里起作用,%i 表示其值。
注意:以上是直接在命令提示符里以命令的形式表达出来的写法;在批处理文件中应使用双百分号 %% 代替单百分比号 % ,就像:%%i。关于它们之间的区别我研究了好半天才分清楚 orz [具体请参阅后文第4.2.5节]。

批量修改文件名是其比较有用的典例之一。看看下面的批处理
:::::::批量修改文件名.bat:::::::
@echo off
setlocal EnableDelayedExpansion
set /a num=1
for %%i in (D:\test\*.txt) do (
ren "%%i" !num!.txt
set /a num+=1
)
::::::::::::::::::::::::::::::::
这个批处理并不难理解。就像第4.1.5节所说的:使用了 setlocal EnableDelayedExpansion 后,可以让 forif 后面的执行语句中变量的值随其变化而不断更新(所以后面使用了 !num! 而不是 %num%)。整个批处理的处理过程就是对 D:\test\*.txt 中的所有文本文件进行批量改名,文件名从 1.txt 开始依次为 2.txt 、3.txt ……。
注意:请确保循环语句 in 路径中的文件不是重要的文件,因为改名后将无法使用撤消,如果像我一样不小心把重要文件误改名的话就又要 orz 了一次。

以上批处理是固定了文件的路径以及文件后缀名。为了增加该批处理的功能,我们可以让用户自己选择要进行改名的文件所在路径,以及选择所进行文件修改的后缀名。当然,有些朋友还希望有给文件批量加上前缀(比如:前缀1.txt 前缀2.txt 等等)。(关于 批量改文件名.bat 在第六章中还有进一步的修改)
::::::::批量改文件名.bat::::::::
@echo off
setlocal EnableDelayedExpansion

set /p zpath=请输入目标文件所在的路径:
set /p prefix=请输入文件名前缀(不能包含以下字符\/:*?"<>|):
set /p ext=请输入文件的扩展名(例如 .txt):
set /a num=1

for %%i in (%zpath%\*%ext%) do (
ren "%%i" "%prefix%!num!.%ext%"
set /a num+=1
)
::::::::::::::::::::::::::::::::

4.2.3 也许大家注意到了,上面 for 的用法仅仅是针对多个文件来进行循环重复操作的。如果想对一系列有规律的数字进行循环,或是在一定的次数内对某个操作进行循环重复的执行,使用 for 也能够实现。/l 是可以跟在 for 后面的重要参数之一。比如:for /l %i in (5,3,16) do echo %i ,可以让数值型的变量 i 依次成为:5、8、11、14 。正如 in 里所描述的规律 (5,3,16) 一样,从 5 开始,每次增加 3 ,直到 16为止。同样,我们还可以试一下 for /l %i in (19,-4,3) do echo %i ,这次 i 是递减的规律。很明显,结果将依次显示为:19、15、11、7、3 。

这样的用法很自然的能让我们想到,重复执行N遍完全一样的事情不再是麻烦而又无聊的了。在下面的例子里,您一定会找到惊喜的。
::::::::::圆圈方阵.bat::::::::::
@echo off
setlocal EnableDelayedExpansion

set var=○
for /l %%i in (1,1,7) do set var=%var%!var!
:: 此时变量 var 已经变成一行连续的8个圆圈了

for /l %%i in (1,1,8) do (
echo 这是第 %%i 份>输出结果%%i.txt
for /l %%j in (1,1,8) do echo %var%>>输出结果%%i.txt
)

echo 8 X 8 的 ○ 矩阵已经画好,并保存到8份文本文件里了
pause
::::::::::::::::::::::::::::::::
注意:%%i ,上一节中提到过,在批处理文件中需要用连续的两个百分号 %% 来描述循环变量 i ,而不是一个。
注意:%var% 与 !var! ,它们的用法与区别,在第4.1.5节中有解释。
注意:i 与 j ,在循环里面再套循环时,前一个循环变量 i 在没有释放之前,不应该让第二个循环变量的名称与 i 重复。
注意:> 与 >> ,同样是向某设备里输出,但却有区别,请参阅第2.2节。

4.2.4 for 也可以对指定范围内的文字进行循。for 后面跟参数 /f ,/f 后面跟选项,所指定的范围 in 里可以是一个文件里的文字,可以是一个字符串,也可以是一条命令的输出结果。我们首先以一个文件里的文字作为循环对象,循环时,每一行将被循环一次。
::::::::::文字筛选.txt::::::::::
@echo off
echo 测试 文字筛选.txt 里每一行的首单词
for /f %%i in (文字筛选.txt) do echo %%i
pause

echo.
echo skip=2 表示前两行被跳过
for /f "skip=2" %%i in (文字筛选.txt) do echo %%i
pause

echo.
echo tokens=2,4-6 表示提取每行的第2个、以及第4到6个单词
for /f "skip=2 tokens=2,4-6" %%i in (文字筛选.txt) do echo %%i, %%j, %%k, %%l.
pause

echo.
echo eol=N 表示当此行的首字母为 N 时,就忽略该行
for /f "eol=N skip=2 tokens=2,4-6" %%i in (文字筛选.txt) do echo %%i, %%j, %%k, %%l.
pause

echo.
echo delims=e 表示不再以空格区分每个词,而是以字母 e 作为间隔
for /f "eol=N skip=2 tokens=2,4-6 delims=e" %%i in (文字筛选.txt) do echo %%i, %%j, %%k, %%l.
pause

echo.
echo usebackq 表示双引号里的东西是文件名而不是字符串
for /f "usebackq eol=N skip=2 tokens=2,4-6 delims=e" %%i in ("文字筛选.txt") do echo %%i, %%j, %%k, %%l.
pause
::::::::::::::::::::::::::::::::
作为测试,可以在上述批处理文件的同一路径下创建一个用于测试的文本文件 文字筛选.txt ,其内容为:
Hello there!
This text is an example of test for the batch file 文字筛选.bat.
Notice the first letter in this line, N.
If the eol charactor was set to be letter N.
The third line will not be considered by the batch.

4.2.5 ESCAPE字符 % ,通常被译为转义字符,但也有更形象的译名脱逸字符、逃逸字符等。也就是说 % 不仅仅将与其相关的特定字符串转义并替换为特定字符串,而且自身也会被“脱逸”。而且类似于C语言中的转义字符 \ ,双%会转义并脱逸为单百分号 % ,四%则脱为双百分号 %% 。[3]

注意下面这个批处理中的双百分号 %% 的用法
::::::::::::::::::::::::::::::::
@echo off
set Text=Hello world!
for /l %%i IN (0,1,11) do call echo %%Text:~%%i,1%%
pause
::::::::::::::::::::::::::::::::

第五章:组合命令 管道命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
5.1 组合命令

组合命令 & 、&& 和 || 是一类用于两个或多个命令语句之间起衔接作用的符号。这对于我们想一次性执行两条或多条命令,以及前面命令执行结果的成功与否作为后面命令是否被执行的衡量标准,起着决定性的作用。

5.1.1 &

通过紧随的例子,echo Checking what executable files we have in WINDOWS... & dir C:\WINDOWS\*.exe & echo And we got lots of stuff here. 我们不难理解 & 在多个命令之间所起的连接作用。事实上,我们完全可以将这三者分成3行来独立执行,因为它们之间是相互独立的关系。不论三者中每一条命令的结果如何,后面的一条命令总能被得到执行(这是与下文 && 和 || 的不同之处)。

5.1.2 &&

&& 作为组合命令之一,与 & 类似,也有着并列多条命令并将其按顺序执行的功能。与 & 的不同之处,也许此时您已经猜到了,没错,如果多命令中的某一条命令执行出错时,后面的所有命令将不会再被执行;如果一直没有出错则会一直执行完所有的并列命令。为了很好的对比它们之间的区别,我们分别尝试一下下面的两个例子。
echo Checking if we have the following directory... & dir "E:\starcraft II" & echo Seems it does not exist.
echo Checking if we have the following directory... && dir "E:\starcraft II" && echo Seems it does not exist.
它们的区别是一目了然的,前者会有 Seems its not exist. 的输出,而后者却没有。

5.1.3 ||

|| 的用途与 && 的功能恰好相反。当遇到执行正确的命令后将不再执行后面的命令,如果没有出现正确的命令则一直执行完所有命令。例如 dir D:\test || md D:\test ,如果 D:\test 存在,即第一条命令执行正确的话,后面的创建 D:\test 就不会再执行了;相反,如果第一条命令执行出错,那么后面的命令就起作用了。

在混合使用的时候需要注意它们的优先级。分析下面几个例子有助于我们理解它们的执行效果(如果我们的机器上并没有 Z: 盘的话)。注:浅蓝色的命令表示会被执行到,而深蓝色的命令将不会被执行到。
dir Z: & dir C: || echo Howdy
dir C: & dir Z: || echo Howdy
dir Z: && dir C: || echo Howdy
dir C: && dir Z: || echo Howdy
dir C: && dir C: || echo Howdy

dir C: || echo Howdy & echo Hi there
dir C: || echo Howdy && echo Hi there
dir Z: || echo Howdy & echo Hi there
dir Z: || echo Howdy && echo Hi there
实验一下,看看结果是不是跟预期的一样。

5.2 管道命令

5.2.1 > 、>>

它们是输出重定向命令,在第2.2节中已有详细的介绍。其主要功能就是将一条命令或某个程序输出结果的重定向到特定文件中。> 与 >> 的区别在于,> 会清除调原有文件中的内容后写入指定文件,而 >> 只会追加内容到指定文件中,而不会改动其中的内容。下面将会是一个很有用的例子。

众所周知,System32 文件夹是多数木马潜伏和发作的好地方。当我们刚装好机器的时候,可以给此时机器里还没有病毒、木马的 System32 文件夹里的所有可执行文件(.exe)和动态链接库文件(.dll)作个记录。等以后发觉 System32 里多了可疑的东西的时候再作个记录,然后跟之前的记录对比一下,就很容易发现问题了。
为了方便作记录,我们可以执行类似下面的一条命令:dir %windir%\system32\*.exe>D:\%DATE:~0,10%的exe文件.txt 。其中,%windir% 是当前启动系统所在的目录,默认情况下通常是 C:\WINDOWS ;%DATE:~0,10% 是指当天的日期,比如2007-11-30,如果您没有忘记在3.1.4节里曾描述过的话。而整条命令的结果就是把 System32 里的所有可执行文件名称及信息记录到一个指定的文本文件里了。同理:我们可以记录 System32 里的所有 dll 文件:dir %windir%\system32\*.dll>D:\%DATE:~0,10%的dll文件.txt 。
在经过一段时间后,我们可以再次使用上面两条命令,从而得到两个新的记录文件。然后对比一下两个文件看看有什么差异。DOS 命令里提供了这样一条命令 fc ,它允许我们对两个文件之间的差异进行比较。使用时就像:fc d:\2007-11-30的exe文件.txt d:\2007-12-01的exe文件.txt 。[1]

5.2.2 |

没错,只是一条竖线而已。它可以将它左边命令的输出结果放到它右边的命令里作为输入参数。这种用法据说在 Unix 里很常见。

有个能简单检测机器是否中冰河木马的例子:netstat /a /n | find "7626" && echo 已被冰河感染 || echo 未被冰河感染 。其中:
netstat 用于显示当前的网络连接情况,参数 /a 显示所有连接和监听端口;/n 以数字形式显示地址和端口号。仅仅执行 netstat /a /n 后,您将会看到输出的结果为:各种协议下的本地和目标的地址及端口,以及它们的连接状态。
命令 find 则可以搜索指定的字符串,在指定的文件中。netstat /a /n 的输出结果将通过 | 成为命令 find 的第二个参数。因此整个的一条命令可以理解为:在网络连接状态的输出结果中,查找字符串 7626 ,如果查找成功的话,将输出被感染的提示,否则便提示未感染。&& 和 || 请参考第5.1.3节的例子。(端口 7626 是冰河所使用的默认端口。事实上,这并不是一个严谨的检测冰河木马存在的方法,但它却是说明组合命令和管道命令一个很好的例子)

5.2.3 管道命令还有 < 、<& 和 >& ,它们并不常见,在此暂时不讨论了。

第六章:常用实例 上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
6.1 批量修改文件名

在第4.2.2节中,我们已经会使用循环命令对大量文件改名进行批量处理。但总结一下,该批处理并不是很健壮。判断一个程序的好坏,往往不是站在程序员的角度,而从用户的角度出发。比如:在用户使用它的时候,如果输入了不正确的路径格式怎么办?如果输入了含有非法符号的前缀怎么办?输入的扩展名也有问题怎么办?改完名后看不到是否执行成功的反馈信息,等等。带着这些想法,我们将原程序再次修改一下。

:::::::批量修改文件名.bat:::::::
@echo off
title 批量修改文件名
setlocal EnableDelayedExpansion
:: 启用延迟变量扩充

:GetPath
set zpath=%CD%
:: 对变量进行初始化,防止用户不输入而直接跳过。其中%CD%表示当前路径
set /p zpath=请输入目标文件所在的路径:
if %zpath:~0,1%%zpath:~-1%=="" set zpath=%zpath:~1,-1%
:: 检查变量 zpath 的第一个和最后一个字符是否为 "" ,是的话就去掉
if not exist "%zpath%" goto :GetPath
:: 如果 zpath 值的路径不存在,就得跳转回去,要求重新输入

:GetPrefix
set prefix=未命名
set /p prefix=请输入文件名前缀(不能包含以下字符\/:*?"<>|):
for /f "delims=\/:*?<>| tokens=2" %%i in ("z%prefix%z") do goto :GetPrefix
:: 这里对变量 perfix 进行检查,发现有非法符号便跳转到 :GetPrefix
:: 事实上,这里并没有对双引号 " 进行检测,因为双引号无法在此被转义为可用的分隔符
:: 即使是在这个程序里,不正确地使用双引号也会引起程序异常而退出。
:: 因此,想把它做的非常人性化并不是一件容易的事情

:GetExt
set ext=.*
set /p ext=请输入文件的扩展名(不输入则表示所有类型):
if not "%ext:~0,1%"=="." set ext=.%ext%
:: 检查变量 ext 的第一个是否为句点 . ,不是的话就加上
:: 建议这里对变量 ext 也检查一下,发现有除*外的非法符号便跳转到 :GetExt

set answer=N
echo.
echo 您试图将 %zpath%\ 里的所有 %ext% 类型的文件以 %prefix% 为前缀名进行批量改名,是否继续?
set /p answer=继续请输入 Y ,输入其它键放弃...
if "%answer%"=="Y" goto :ReadyToRename
if "%answer%"=="y" goto :ReadyToRename

echo 放弃文件改名,按任意键退出... & goto :PauseThenQuit

:ReadyToRename

set /a num=0
echo.

if "%ext%"==".*" (
for %%i in ("%zpath%\*%ext%") do (
set /a num+=1
ren "%%i" "%prefix%!num!%%~xi" || echo 文件 %%i 改名失败 && set /a num-=1
)
) else (
for %%i in ("%zpath%\*%ext%") do (
set /a num+=1
ren "%%i" "%prefix%!num!%ext%" || echo 文件 %%i 改名失败 && set /a num-=1
)
)

if %num%==0 echo %zpath%\ 里未发现任何文件。按任意键退出... & goto :PauseThenQuit

echo 文件改名完成,按任意键退出...

:PauseThenQuit
pause>nul
::::::::::::::::::::::::::::::::


相对第4.2.2节里的批量修改文件的批处理来说,已经全面多了。不过这仍然有许多地方需要进一步完善,比如,输出的文件名编号可以用001、002、003这样的方式来表达,以便浏览器在以文件名排列文件时能按我们或需要的顺序进行排列。

========================================朴实的分割线========================================

6.2 批量备份进程映像列表以及注册表自启动项

什么是备份;为什么要备份;怎么去备份;备份些什么,说起来我们可能会更关心这些问题。
什么是备份:将某事物复制出额外一份完全一样的事物,并妥善保存起来的过程;
为啥要备份:当原事物出现异常或无法使用的时候,取出复制品并代替原来无法使用的事物;
怎么去备份:复制,与原事物完全一样地去复制,然后保存到安全的地方;
备份些什么:系统、文档、数据库、工程、记录、进度等等。
不过,这些都不是本文的主旨。

本节的写作起因是从《利用Windows系统自带命令手工搞定病毒》这篇文章开始的。这篇文章的主旨是:用 tasklist 备份好进程列表→通过 fc 比较文件找出病毒→用 netstat 判断进程→用 ntsd 终止进程→搜索找出病毒以及同伙文件并删除→用 reg 命令修复注册表。
不过,这些也不是本文的主旨。

在前面提到的《手工杀毒》一文中,第二步提到:如果感觉机器异常,可以将现有的进程与以前机器正常时的进程加以对比,看看是否多出了可疑的进程。这就意味着您必须得记忆或保留住以前正常时的进程映像名,即备份。备份时正如《手工杀毒》中所说的,在系统正常且刚进入Windows的时候就做一个进程映像名列表的备份。查看机器当前的进程可以通过组合键 Ctrl+Alt+Del 召唤出"Windows 任务管理器",然后切换到"进程"页即可。对于如何记录下当前的这些进程,您可以用 PrintScreen 键抓图(Alt+PrintScreen 可以仅抓取当前窗口的图)并以图片的形式保存,当然,您甚至可以简单地将这些映像名抄到一张纸上。本文所提供的方法要比抓图或是手抄的方法更先进一些,至少是更严肃了一些。

除了 Windows 任务管理器 以外,使用命令 tasklist 也可以看到(详细信息请参阅 tasklist/?)当前进程。tasklist>进程.txt 即可将进程以表格的形式输出到一个文本文件中。在输出结果中除了进程映像名以外,还有 PID 、会话、内存使用等信息,但后面的这些信息并不总是固定的,因此我们也并不需要这些。我们所关心的只是一个按照字母顺序排列的进程映像名清单,以便今后进行比较。

::::::备份进程和自启动.bat::::::
@echo off

set TempFolder=临时文件夹
:: 设置临时文件夹的名称
if not exist %TempFolder% md %TempFolder%
:: 创建临时文件夹
echo.>%TempFolder%\temp.tmp
:: 在临时文件夹里创建一个临时文件 temp.tmp ,并清空其内容

set ExportImageName=%cd%\进程映像列表%date:~0,10%.txt
:: 设置默认导出文件的路径以及文件名
set /p ExportImageName=所要导出的进程映像名称列表备份文件名:
:: 用户自定义输入的路径文件名

for /f "delims=," %%i in ('tasklist /nh /fo csv') do echo %%~i>>%TempFolder%\temp.tmp
:: 将 tasklist 中的进程映像名输出到临时文件 temp.tmp 中 [注1]
sort %TempFolder%\temp.tmp>"%ExportImageName%"
:: 将进程映像名按字母顺序排列
del %TempFolder%\temp.tmp
:: 删除临时文件夹里的文件 temp.tmp
echo 当前进程中的所有映像名已导出到以下文件:%ExportImageName%
pause
:::::::: 以上完成了进程备份,下面开始备份自启动的注册表信息

set ExportRegRunName=%cd%\注册表自启动%date:~0,10%.txt
:: 设置默认导出文件的路径以及文件名
set /p ExportRegRunName=所要导出的注册表自启动备份文件名:
:: 用户自定义输入的路径文件名

reg export HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run %TempFolder%\HKLMrun.reg
reg export HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run %TempFolder%\HKLMexp.reg
reg export HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run %TempFolder%\HKCUrun.reg
reg export HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run %TempFolder%\HKCUexp.reg
:: 将相关的注册表信息导出到各个临时的注册表文件中 [注2]

copy %TempFolder%\*.reg "%ExportRegRunName%">nul
:: 将临时文件夹里所有的注册表文件合并复制到一个文件中
del %TempFolder%\*.reg
:: 删除临时文件夹里所有的临时注册表文件
if exist %TempFolder% rd %TempFolder%
:: 删除临时文件夹
echo 当前自启动的注册表信息已导出到以下文件:%ExportRegRunName%
pause
::::::::::::::::::::::::::::::::

注1. tasklist /nh /fo csv 中参数 /nh 表示不输出栏标头,参数 /fo csv 表示以双引号包含字符串,并用逗号分隔各个字符串。for /f "delims=," %%i in ('tasklist /nh /fo csv') do echo %%~i>>a.txt 表示在无栏标头且以 csv 格式输出的 tasklist 结果中,以逗号为分隔符,将每行的首字符串依次输出到文本文件 a.txt 中。

注2. reg export 能将注册表指定项的所有子项和值导出到指定的文件中(请参阅 reg/? 与 reg export /?)。当然,以上4个只是常见的自启动键值,病毒或木马所在注册表中添加或修改的项不仅此而已,更多需要备份的项可用类似的方法自行添加。

对于查找可以进程的方法,与前文 5.2.1 小节中所提到的对比系统文件夹找差异的过程类似,使用命令 fc 对比两个不同时期的进程映像名列表,找出多余的可疑进程(如果您无法确定某个进程是干什么用的或者是否存在安全风险,可以先到网上搜索并了解一下,比如:进程知识库)。至于是用 taskkill 还是 ntsd 来终止可以的进程,怎样用属性加时间来查找并锁定可以的文件,以及 reg import 注册表导入恢复的用法,这些仍然不是本文的重点。[创作日期:2008-01-08]

========================================朴实的分割线========================================

6.3 批量查看同一子网络下的所有IP在线情况[编辑中]

本小节的批处理可以让您知道自己所在局域网的同一网段下都有哪些IP被使用了。不得不承认,我在创造这个批处理的时候想法很奇怪,甚至有些愚蠢。

:::::::查看所有子网IP.bat:::::::
@echo off
title 查看所有子网IP

set /a Online=0
set /a Offline=0
set /a Total=256
set ExportFile=子网IP在线统计.txt
:: 初始化在线IP与不在线IP的个数为零,共扫描256个IP,结果输出的文件名

set StartTime=%time%
:: 记录程序的开始时间

for /f "delims=: tokens=2" %%i in ('ipconfig /all ^| find /i "IP Address"') do set IP=%%i
:: 获得本机IP [注1]

if "%IP%"=="" echo 未连接到网络 & pause & goto :EOF
if "%IP%"==" 0.0.0.0" echo 未连接到网络 & pause & goto :EOF
:: 当IP为空或 0.0.0.0 时,提示未连接并退出该程序

for /f "delims=. tokens=1,2,3,4" %%i in ("%IP%") do (
set /a IP1=%%i
set /a IP2=%%j
set /a IP3=%%k
set /a IP4=%%l
)
:: 以句点为分隔符,分别将IP的四个十进制数赋给四个变量

set /a IP4=0
echo 在线的IP:>%ExportFile%
:: 初始化IP的第四个数值为零,并创建结果输出文件

:RETRY
ping %IP1%.%IP2%.%IP3%.%IP4% -n 1 -w 200 -l 16>nul && set /a Online+=1 && echo %IP1%.%IP2%.%IP3%.%IP4%>>%ExportFile% || set /a Offline+=1
:: ping 目标IP [注2]

set /p =[将本文底部评论4中的退格符替换到此处]<nul
set /a Scanned=%Online%+%Offline%
set /a Progress=(%Online%+%Offline%)*100/%Total%
set /p =正在扫描:%Scanned%/%Total% 扫描进度:%Progress%%%<nul
:: 删除当前行的内容,并重新显示进度信息 [注3]

set /a IP4+=1
if %IP4% lss %Total% goto :RETRY
:: 当IP的第四个数值小于总数时,跳转回 :RETRY 处,重复执行直到全部 ping 完为止

echo.
echo.

set EndTime=%time%
:: 记录程序的结束时间

set /a Seconds = %EndTime:~6,2% - %StartTime:~6,2%
set /a Minutes = %EndTime:~3,2% - %StartTime:~3,2%
if %Seconds% lss 0 set /a Seconds += 60 & set /a Minutes -= 1
if %Minutes% lss 0 set /a Minutes += 60
:: 计算时间差

set /a Percent=%Online%*100/(%Online%+%Offline%)
:: 计算在线百分比

echo 在线IP个数: %Online%
echo 不在线IP个数: %Offline%
echo 在线百分比: %Percent%%%
echo 统计耗时: %Minutes%分%Seconds%秒
echo 统计日期: %date% %time:~0,-3%
echo.>>%ExportFile%
echo 在线IP个数: %Online%>>%ExportFile%
echo 不在线IP个数: %Offline%>>%ExportFile%
echo 在线百分比: %Percent%%%>>%ExportFile%
echo 统计耗时: %Minutes%分%Seconds%秒>>%ExportFile%
echo 统计日期: %date% %time:~0,-3%>>%ExportFile%
echo 记录已保存到文件"%ExportFile%"
::显示结果并将结果保存到文件中
pause
::::::::::::::::::::::::::::::::

注1. ipconfig 是内置于 Windows 的 TCP/IP 应用程序,用于显示本地计算机网络适配器的物理地址和IP地址等配制信息,这些信息一般用来检验手动配置的 TCP/IP 设置是否正确。当在网络中使用 DHCP 服务时, ipconfig 可以检测到计算机中分配到了什么IP地址,是否配置正确,并且可以释放,重新获取IP地址。这些信息对于网络测试和故障排除都有重要的作用。[3]
更详细的说明请参阅 ipconfig/? 。ipconfig /all ,参数 /all 表示查看详细的网络配置。命令 ipconfig /all ^| find /i "IP Address" 表示在 'ipconfig /all 的结果中,以 "IP Address" 为查找对象,进行搜索(其结果类似于:IP Address. . . . . . . . . . . . : 10.30.11.51 )。
而整条命令中的 for 语句,则表示在上述结果中,以冒号为间隔(delims=:),查找第2个字串(tokens=2)。很明显,所找到的结果就是自己电脑当前的IP地址了(如果您只有一快网卡或是只启用了一个网卡的话。显然,对于多个网卡会显示出多个IP的情况,我并没有考虑的太全面)。[关于 for 更详细请参阅 4.2.4 小节]
另外,注意到在 ipconfig /all ^| find /i "IP Address" 中有一个转义字符 ^ ,它的作用是让后面的管道命令 | 生效,而不是让程序把 | 误解为 for 语句里参数的一部分。

注2. ping 其实才是本批处理的核心部分。命令 ping 的主要作用是通过发送数据包并接收应答信息来检测两台计算机之间的网络是否连通。比如我可以输入 ping 10.30.11.35 以便查看我是否能与我所在的局域网中IP为 10.30.11.35 的机器连通。如果我不懂批处理的话,也许我就得从 IP 10.30.11.1 开始,挨个地 ping 到 IP 10.30.11.255 ,才能达到我在本小节的最初目的。
在批处理中 ping 的3个参数 -n 1 -w 200 -l 16 分别表示:仅 ping 一遍[-n 1],等待200毫秒后按超时考虑[-w 200],发送16字节的数据[-l 16]。
另外,此命令行中同时用到了两个 && 和一个 || 的组合命令,我不得不承认这种复杂的逻辑关系会给您带来阅读上的困难。

注3. 这里使用了 set /p =显示内容<nul 来显示一些内容,是因为这中方式显示出内容后并不换行,而 echo 却不行。还有向上数4行的那一堆奇怪的符号,表示的是退格符号,能删除掉当前行中以显示的内容。


图6-3 查看所有子网IP.bat 的运行结果
本小节的使用程度并不大,却很有趣,至少并没有想象中的那么愚蠢。[创作日期:2008-01-10]

========================================朴实的分割线========================================

6.4 令人大囧的WGA,华丽地卸载之

WGA 全称 Windows Genuine Advantage ,是微软制造的反盗版软件,因其自动潜入系统而备受争议。当用户访问 Windows Update 服务以及微软网站的下载页面时,该计划的附属检测软件就会被请求安装。很多用户都是在不知情的情况下被安装上了WGA。事实上,安装了WGA后,正版的用户并没见得有什么优势,但盗版用户的劣势绝对会被发挥的淋漓尽致。如果您是盗版的受害者(尽管您更坚持认为自己是WGA的受害者),那么建议您仔细阅读一下本小节。首先,卸载方法在微软的帮助与支持中有详细英文说明,中文版的在百度知道里也有介绍。不过,这些依然不是本文的主旨,我们更关心的还是批处理。

::::::::::WGA卸载器.bat:::::::::
@echo off
title WGA卸载器
setlocal EnableDelayedExpansion

set /a step=1
if exist WGA卸载记录.txt (
for /f "tokens=2" %%i in (WGA卸载记录.txt) do set /a step=%%i+1 & goto :Step!step!
)
:: 查看记录进行到第几步了

:Step1
:RenameFiles
if exist %Windir%\system32\wgalogon.old del %Windir%\system32\wgalogon.old
if exist %Windir%\system32\wgatray.old del %Windir%\system32\wgatray.old
:: 检查相关的临时文件是否已存在,若存在则删之
Ren %Windir%\system32\WgaLogon.dll WgaLogon.old
if %errorlevel% neq 0 goto :Abort
Ren %Windir%\system32\WgaTray.exe WgaTray.old
if %errorlevel% neq 0 goto :Abort
:: 将相关文件改名,如果操作失败则中断程序
call :WriteUninstallStatus
:: 将卸载情况记录到文件中
echo 步骤一完成。已重命名文件 WgaLogon.dll 和 WgaTray.exe。
ping -n 2 127.0>nul
goto :RebootPrompt
:: 需要重启计算机

:Step2
:UnregisterServer
Regsvr32 %Windir%\system32\LegitCheckControl.dll /u
:: 将相关的服务注册撤消
call :WriteUninstallStatus
:: 将卸载情况记录到文件中
echo 步骤二完成。已撤消服务 LegitCheckControl.dll。
ping -n 2 127.0>nul
goto :RebootPrompt
:: 需要重启计算机

:Step3
:DeleteFiles
Del %Windir%\system32\wgalogon.old
Del %Windir%\system32\WgaTray.old
Del %Windir%\system32\LegitCheckControl.dll
:: 将相关的文件删除
call :WriteUninstallStatus
:: 将卸载情况记录到文件中
echo 步骤三完成。已删除文件 wgalogon.old WgaTray.old 和 LegitCheckControl.dll。
ping -n 2 127.0>nul

:Step4
:DeleteRegistrySubkeys
set /a step=4
reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify\WgaLogon" /f
reg delete "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\WgaNotify" /f
:: 将相关的子项从注册表中删除
call :WriteUninstallStatus
echo 步骤四完成。已删除注册表中相关的子项。
:: 将卸载情况记录到文件中
ping -n 2 127.0>nul

:Step5
:UninstallDoneSuccessfully
echo.
echo 已完成对WGA的卸载!
del WGA卸载记录.txt
pause>nul
exit

::::以下为程序所调用到的函数::::

:WriteUninstallStatus
:: 该函数用于将当前卸载情况记录到文本文件中
echo WGA卸载已完成第 %step% 步>WGA卸载记录.txt
set /p =更改文件名称:<nul >>WGA卸载记录.txt
call :IsCompleted %step%
set /p =撤消服务注册:<nul >>WGA卸载记录.txt
call :IsCompleted %step%
set /p =删除相关文件:<nul >>WGA卸载记录.txt
call :IsCompleted %step%
set /p =清除注册信息:<nul >>WGA卸载记录.txt
call :IsCompleted %step%
goto :EOF

:IsCompleted
:: 该函数用于判断各个卸载步骤的完成情况
if %1 gtr 0 (
echo 已完成>>WGA卸载记录.txt
set /a step-=1
) else (
echo 未完成>>WGA卸载记录.txt
)
goto :EOF

:RebootPrompt
set /a TimerTick=9
echo.
echo 需要重启计算机才能继续下一步。
echo 请在计算机重启后再运行该批处理,以便继续完成对WGA的卸载。
set /p choice=是否现在就重启? [Y/N]
if "%choice%"=="y" shutdown -r -t 15 -c 因卸载WGA的需要而重启& exit
if "%choice%"=="Y" shutdown -r -t 15 -c 因卸载WGA的需要而重启& exit
echo 稍后请手动重启计算机。
pause>nul
exit

:Abort
echo 程序中止。
echo.
echo 您确信您已经安装了WGA?
pause>nul
exit
::::::::::::::::::::::::::::::::

第七章:常用实例 下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456

7.1 坐在家里周游世界

没错,我想是认真的,对于任何您想去往地球上的地点,您所需要做的仅仅是提供一个目的地的经纬度坐标而已。当然,我是不会给您提供去往世界各地的签证和机票的,但是海量的卫星照片还是能让您过足了瘾的。此时您手头可能并没有足够的坐标资源,不过我这里"恰好"有些陈芝麻烂谷子,是当年在留园网上搜集的地理坐标,具体请参阅《关于Google Maps的趣点》。这里必须得先感谢卫星图片的提供者 Google Maps ,因为你知道,毕竟航拍卫星并不是人人都能买的起的。

本小节也是在我回顾《关于Google Maps的趣点》这篇文章开始的。因为当年在搜集这些该死的地理坐标时,偷了一些懒,结果有些是小数格式的,有些是度分秒格式的,查看起来并不方便。如果有 Google Earth 来转换它们之间的格式倒也不难。但我知道,此时您的迅雷网快电驴里已经排满了各种各样电影、游戏、动漫的下载任务,看来您并没有下载 Google Earth 的意思。没关系,下面这个批处理多少能让您更方便地浏览 Google Maps 上的卫星照片。

::::::坐在家里周游世界.bat::::::
@echo off
title 坐在家里周游世界
:: 设置标题
:Start
cls
:: 清屏
set choice=1
set /p choice=请选择经纬度格式(1. 小数格式 2. 度分秒格式 3. 退出):
:: 选择经纬度的格式 或 退出程序
if %choice%==3 goto :EOF
if %choice%==2 goto :DMSFormat

:DecimalFormat
:: 小数格式的经纬度处理
set latitude=39.906477
set longitude=116.391467
:: 初始化为伟大首都天安门的坐标
set /p latitude=纬度:
set /p longitude=经度:
:: 提示用户输入目标的经纬度 [注1]
goto :LaunchMap

:DMSFormat
:: 度分秒格式的经纬度处理
set "DMSlatitude=39 54 23 N"
set /a degree=39
set /a minute=54
set /a second=23
set NorthOrSouth=N
:: 初始化为美丽首都天安门度分秒格式的纬度
echo.
set /p DMSlatitude=纬度(格式 [度] [分] [秒] [N ^| S]):
:: 提示用户输入目标的纬度 [注2]
for /f "tokens=1,2,3,4,5" %%i in ("%DMSlatitude%") do (
set degree=%%i
set minute=%%j
set second=%%k
set NorthOrSouth=%%l
if "%%l"=="" echo 不正确的格式 & goto :DMSFormat
)
:: 分别获得纬度的度、分、秒,以及南半球或北半球
if %degree% lss 0 echo 纬度必须大于0度 或不正确的格式 & goto :DMSFormat
if %degree% gtr 90 echo 纬度必须小于90度 或不正确的格式 & goto :DMSFormat
if %minute% lss 0 echo 纬度必须大于0分 或不正确的格式 & goto :DMSFormat
if %minute% gtr 60 echo 纬度必须小于60分 或不正确的格式 & goto :DMSFormat
if %second% lss 0 echo 纬度必须大于0秒 或不正确的格式 & goto :DMSFormat
if %second% gtr 60 echo 纬度必须小于60秒 或不正确的格式 & goto :DMSFormat
:: 判断纬度的度、分、秒格式是否有效 [注3]
set /a degree*=3600
set /a minute*=60
set /a second=%degree%+%minute%+%second%
set /a latitude=%second%*2500/9
:: 将度分秒计算并转换为小数格式 [注4]
if %NorthOrSouth%==N goto :LatitudeLockDown
if %NorthOrSouth%==n goto :LatitudeLockDown
if %NorthOrSouth%==S set /a latitude=0-%latitude% & goto :LatitudeLockDown
if %NorthOrSouth%==s set /a latitude=0-%latitude% & goto :LatitudeLockDown
:: 判断南北半球格式是否有效
echo 南北半球标识不明确,请使用 N 或 S 来表示,不区分大小写。 & goto :DMSFormat

:LatitudeLockDown
set "DMSlongitude=116 23 29 E"
set /a degree=116
set /a minute=23
set /a second=29
set EastOrWest=E
:: 初始化为可爱首都天安门度分秒格式的经度
echo.
set /p DMSlongitude=经度(格式 [度] [分] [秒] [E ^| W]):
:: 提示用户输入目标的经度
for /f "tokens=1,2,3,4" %%i in ("%DMSlongitude%") do (
set degree=%%i
set minute=%%j
set second=%%k
set EastOrWest=%%l
if "%%l"=="" echo 不正确的格式 & goto :LatitudeLockdown
)
:: 分别获得经度的度、分、秒,以及东半球或西半球
if %degree% lss 0 echo 经度必须大于0度 或不正确的格式 & goto :LatitudeLockDown
if %degree% gtr 180 echo 经度必须小于180度 或不正确的格式 & goto :LatitudeLockDown
if %minute% lss 0 echo 经度必须大于0分 或不正确的格式 & goto :LatitudeLockDown
if %minute% gtr 60 echo 经度必须小于60分 或不正确的格式 & goto :LatitudeLockDown
if %second% lss 0 echo 经度必须大于0秒 或不正确的格式 & goto :LatitudeLockDown
if %second% gtr 60 echo 经度必须小于60秒 或不正确的格式 & goto :LatitudeLockDown
:: 判断经度的度、分、秒格式是否有效
set /a degree*=3600
set /a minute*=60
set /a second=%degree%+%minute%+%second%
set /a longitude=%second%*2500/9
:: 将度分秒计算并转换为小数格式
if %EastOrWest%==E goto :LongitudeLockDown
if %EastOrWest%==e goto :LongitudeLockDown
if %EastOrWest%==W set /a longitude=0-%longitude% & goto :LongitudeLockDown
if %EastOrWest%==w set /a longitude=0-%longitude% & goto :LongitudeLockDown
:: 判断东西半球格式是否有效
echo 东西半球标识不明确,请使用 E 或 W 来表示,不区分大小写。 & goto :LatitudeLockDown

:LongitudeLockDown
set latitude=%latitude:~0,-6%.%latitude:~-6%
set longitude=%longitude:~0,-6%.%longitude:~-6%
:: 整理纬度和经度

:LaunchMap
echo.
set /a zoom=16
set /p zoom=放缩度(0~18 默认值:%zoom%):
:: 提示用户输入照片的放缩值
echo.
echo 正在打开 纬度:%latitude% 经度:%longitude% 的卫星照片...
start "正在打开Google Maps..." "http://maps.google.com/?t=k&z=%zoom%&ll=%latitude%,%longitude%"
:: 将放缩值和经纬度值作为 Google Maps 链接参数,打开相应的照片 [注5]

pause
goto :Start
::::::::::::::::::::::::::::::::

注1. 纬度或经度尽量能精确到小数点后3位数以上,因为越精确的坐标越能准确地帮您找到目标的位置

注2. 度分秒格式的纬度(或经度)您大概并不陌生,如果高中时代的您没有选择在地理课上逃课的话。例如 39°54' 23.32" N 就表示北纬39度54分23.32秒。另外,正在上高中的朋友在政治课上可以适当地翘课,个人研究表明,政治这玩意学多了不利于青少年大脑的发育。

注3. 纬度不能超过90度,而经度不能超过180度,分和秒的范围是0~59,这些常识是绝不会难到您的。

注4. 经纬度从度分秒格式转换为小数格式只需要:(度*60*60+分*60+秒)/60/60 即可,北纬39度54分23.32秒转换为小数即39.906477度。事实上,此处latitude的值是实际纬度的一百万倍,因为DOS命令中并不支持浮点型(实数)的变量,不用担心,在后面会有小数点向前移动6位的处理。同时,希望您也没有在数学课上翘课的习惯。

注5. 该链接正指向此时经纬度和放缩值的卫星地图,其中具体参数的含义可以参考《关于Google Maps的趣点》文中的解释。

好吧,您可能已经迫不及待地想试试这东西了。那么在运行该批处理后,首先您会得到选择两种经纬度类型的提示。选1的话,只需要分别输入小数格式的纬度和精度,以及放缩值即可(如果您还不确定放缩值是啥东西的话,可以置空直接使用默认值)。
如果在程序的一开始选择了2,也就是度分秒格式的经纬度。比如我们的目的地是:北纬15°17' 55" ,东经19°25' 47" ,您可以依次在纬度和经度里输入 15 17 55 n 和 19 25 47 e ,然后在放缩值里填上 22 。这样您就能看到北非中部一个小村庄里的几位村民,以及他们的奶牛和骆驼。


坐在家里周游世界.bat 的运行界面


坐在家里周游世界.bat 的运行结果

事实上,上面的照片是 Google Maps 中罕见的几张高清卫星照片之一,并不是每张照片的放缩值都能达到23滴,因为你也知道,即使是能买得起航拍卫星的家伙,也是没有足够的资金和精力来把地球的每一个角落都拍得一清二楚的。

========================================朴实的分割线========================================

7.2 进程分析者

写了一篇很简单却又很占篇幅的玩意,被我称之为"进程分析者"。此批处理的构思很轻松,只是简单地使用 tasklist 列出所有的进程,再显示为容易理解的文字说明而已。这与任务管理器中的进程相比,除了易于识别以外,还能帮您鉴别出那些喜欢"偷梁换柱"的隐患进程。比如用肉眼去观察 winhlep.exe 或 winhe1p.exe 的时候很容易忽视它们,而使用"进程分析者"却不会。

:::::::::进程分析者.bat:::::::::
@echo off
setlocal enabledelayedexpansion

title 进程分析者
set SPACE=
set /a NumOfTotal=0
set /a NumOfSafe=0
set /a NumOfNasty=0
set /a NumOfUnknown=0
set IconOfSafe=√
set IconOfNasty=×
set IconOfUnknown=?

:::::::: 以下定义为可信任的进程,可自定义更多的扩充 ::::::::
:: 1. 系统进程
set alg.exe=%IconOfSafe%处理Windows网络连接共享和网络连接防火墙[系统进程]
set csrss.exe=%IconOfSafe%管理Windows图形相关任务[系统进程]
set explorer.exe=%IconOfSafe%用于显示系统桌面的图标,任务栏等[系统进程]
set lsass.exe=%IconOfSafe%用于本地安全和登陆策略[系统进程]
set services.exe=%IconOfSafe%管理启动和停止服务[系统进程]
set smss.exe=%IconOfSafe%用于对话管理子系统调用和系统对话操作[系统进程]
set spoolsv.exe=%IconOfSafe%用于将打印机任务发送到本地打印机[系统进程]
set svchost.exe=%IconOfSafe%用于执行动态链接库DLL文件[系统进程]
set System=%IconOfSafe%[系统进程]
set taskmgr.exe=%IconOfSafe%任务管理器,用于显示系统正在运行的进程[系统进程]
set winlogon.exe=%IconOfSafe%用于处理系统的登陆和登陆过程[系统进程]
set winmgmt.exe=%IconOfSafe%用于系统管理员创建WIndows管理脚本[系统进程]
:: 2. 基本进程
set cmd.exe=%IconOfSafe%Windows系统的命令行程序
set msimn.exe=%IconOfSafe%OutlookExpress相关程序
set mspaint.exe=%IconOfSafe%画图板
set notepad.exe=%IconOfSafe%记事本
set wab.exe=%IconOfSafe%通讯薄,用于储存地址、联系人和Email
set ctfmon.exe=%IconOfSafe%提供语音识别、手写识别等
set conime.exe=%IconOfSafe%输入法编辑器相关程序
set SOUNDMAN.EXE=%IconOfSafe%Realtek声卡相关程序
set tasklist.exe=%IconOfSafe%这是本批处理的核心所在-_-b
set wmiprvse.exe=%IconOfSafe%用于通过WinMgmt.exe程序处理WMI操作
:: 3. 工作进程
set EXCEL.EXE=%IconOfSafe%Excel
set WINWORD.EXE=%IconOfSafe%Word
set XDICT.EXE=%IconOfSafe%金山词霸
set sqlservr.exe=%IconOfSafe%用于SQL基础服务
set wmplayer.exe=%IconOfSafe%Windows Media Player
set Mplayerc.exe=%IconOfSafe%暴风影音
set WinRAR.exe=%IconOfSafe%WinRAR
:: 4. 防护进程
set 360tray.exe=%IconOfSafe%360安全卫士实时监控程序
set AntiArp.exe=%IconOfSafe%ARP防火墙
set CCenter.exe=%IconOfSafe%瑞星信息中心
set RavMonD.exe=%IconOfSafe%瑞星监控程序
set rfwsrv.exe=%IconOfSafe%瑞星个人防火墙相关程序
set RavStub.exe=%IconOfSafe%瑞星杀毒软件相关程序
set RfwMain.exe=%IconOfSafe%瑞星防火墙主程序
set RavTask.exe=%IconOfSafe%瑞星定时杀毒程序
set RavMon.exe=%IconOfSafe%瑞星监控程序
:: 5. 网络进程
set iexplore.exe=%IconOfSafe%IE浏览器
set Maxthon.exe=%IconOfSafe%傲游浏览器
set BaiduHi.exe=%IconOfSafe%百度Hi
set msmsgs.exe=%IconOfSafe%MSN网络聊天工具
set QQ.exe=%IconOfSafe%腾迅QQ
set TXPlatform.exe=%IconOfSafe%腾迅平台
set Thunder5.exe=%IconOfSafe%迅雷下载
set Skype.exe=%IconOfSafe%Skype语音聊天
set Contentfilter.exe=%IconOfSafe%Skype的相关程序
set skypePM.exe=%IconOfSafe%Skype语音聊天

:::::::: 以下定义为已知的危险进程,可自定义更多的扩充 ::::::::
set a.exe=%IconOfNasty%蠕虫
set av.exe=%IconOfNasty%蠕虫
set blss.exe=%IconOfNasty%木马/拨号器
set cmd32.exe=%IconOfNasty%病毒
set crss.exe=%IconOfNasty%蠕虫
set csrse.exe=%IconOfNasty%病毒/木马
set Desktop.exe=%IconOfNasty%木马/病毒/间谍
set directs.exe=%IconOfNasty%蠕虫
set dllhlp.exe=%IconOfNasty%木马
set dllreg.exe=%IconOfNasty%病毒
set explore.exe=%IconOfNasty%灰鸽子
set explored.exe=%IconOfNasty%蠕虫
set optimize.exe=%IconOfNasty%拨号器/广告
set pcsvc.exe=%IconOfNasty%间谍
set rundll16.exe=%IconOfNasty%木马
set run32dll.exe=%IconOfNasty%间谍
set scvhost.exe=%IconOfNasty%木马/广告
set svchosts.exe=%IconOfNasty%木马
set system32.exe=%IconOfNasty%木马
set updater.exe=%IconOfNasty%蠕虫
set web.exe=%IconOfNasty%病毒/木马
set win32.exe=%IconOfNasty%病毒
set windows.exe=%IconOfNasty%蠕虫
set winlogin.exe=%IconOfNasty%病毒/木马
set winstart.exe=%IconOfNasty%间谍/广告
set wintsk32.exe=%IconOfNasty%病毒
set winupdate.exe=%IconOfNasty%病毒
set winxp.exe=%IconOfNasty%病毒
set winhlep.exe=%IconOfNasty%病毒

:::::::: 以下定义为未知的进程 ::::::::
set UnknownTask=%IconOfUnknown%未识别的进程

:: 程序开始
echo 进程名称 分析结果
echo.

for /f "tokens=1" %%i in ('tasklist /NH') do (
set TaskName=%%i%SPACE%
set TaskName=!TaskName:~0,20!
if defined %%i (
echo !TaskName! !%%i!
if "!%%i:~0,1!"=="%IconOfNasty%" (
set /a NumOfNasty+=1
)
) else (
echo !TaskName! %UnknownTask%
set /a NumOfUnknown+=1
)
set /a NumOfTotal+=1
)
echo ________________________________________________________________
echo.
echo 共 %NumOfTotal% 个进程。
if %NumOfNasty% gtr 0 (
echo %NumOfNasty% 个存在安全隐患的进程!
)
if %NumOfUnknown% gtr 0 (
echo %NumOfUnknown% 个未知进程。
)
pause>nul
::::::::::::::::::::::::::::::::

结果:


图7-3 进程分析者.bat 的运行结果

分析完毕!

========================================朴实的分割线========================================

7.3 公元2738年11月28日是星期几[编写中]

公元2738和11月28日,是个好日子。我打赌您不知道这天究竟是星期几而且您也并不想知道。

:::公元2738-11-28是星期几.bat:::[β版]
@echo off
setlocal enabledelayedexpansion

:Initialization
:: 初始化
set TargetDate=%date:~0,4%%date:~5,2%%date:~8,2%
set /a Year = 0 & rem 年 范围: 1~9999
set /a Mon = 0 & rem 月 范围: 1~12
set /a Day = 0 & rem 日 范围: 1~28 29 30 31
set /a IsLeapYear = 0 & rem 是否闰年 范围: 0~1

:PromptEntering
:: 提示输入日期
set /p TargetDate=请输入日期(格式: YYYYMMDD 例如: %TargetDate%):

:: 转换为有效的年月日[注1]
if %TargetDate:~0,3% == 000 (
set /a Year = %TargetDate:~3,1%
) else (
if %TargetDate:~0,2% == 00 (
set /a Year = %TargetDate:~2,2%
) else (
if %TargetDate:~0,1% == 0 (
set /a Year = %TargetDate:~1,3%
) else (
set /a Year = %TargetDate:~0,4%
)
)
)
if %TargetDate:~4,1% == 0 (
set /a Mon = %TargetDate:~5,1%
) else (
set /a Mon = %TargetDate:~4,2%
)
if %TargetDate:~6,1% == 0 (
set /a Day = %TargetDate:~7,1%
) else (
set /a Day = %TargetDate:~6,2%
)

:CheckLeapYear
:: 检测是否是闰年[注2]
set /a DivisionBy4 = %Year%%%4
set /a DivisionBy100 = %Year%%%100
set /a DivisionBy400 = %Year%%%400

if %DivisionBy4% == 0 set /a IsLeapYear = 1 & rem 能被4整除的是闰年
if %DivisionBy100% == 0 set /a IsLeapYear = 0 & rem 能被100整除的非闰年
if %DivisionBy400% == 0 set /a IsLeapYear = 1 & rem 能被400整除的是闰年

set /a Month1 = 31
set /a Month2 = 28 + %IsLeapYear%
set /a Month3 = 31
set /a Month4 = 30
set /a Month5 = 31
set /a Month6 = 30
set /a Month7 = 31
set /a Month8 = 31
set /a Month9 = 30
set /a Month10= 31
set /a Month11= 30
set /a Month12= 31

:: 检测范围是否有效[注3]
if %Year% lss 1 echo 年份应该大于0 & goto :Initialization
if %Year% gtr 9999 echo 年份不能超过9999 & goto :Initialization
if %Mon% lss 1 echo 月份不能小于1 & goto :Initialization
if %Mon% gtr 12 echo 月份不能大于12 & goto :Initialization
if %Day% lss 1 echo 日数不能小于1 & goto :Initialization
if %Day% gtr 31 echo 日数不能大于31 & goto :Initialization
if %IsLeapYear% == 0 (
if %Mon%%Day% == 229 echo %Year%年的2月没有29日 & goto :Initialization
)
if %Day% gtr !Month%Mon%! echo %Mon%月没有%Day%日 & goto :Initialization

:: 开始计算天数
set /a Year -= 1 & rem 该年未结束,应减一年
set /a Day = %Day% + Year * 365 & rem 一年365天
set /a Day = %Day% + Year/4 & rem 每4年多一闰
set /a Day = %Day% - Year/100 & rem 可每100年少一闰
set /a Day = %Day% + Year/400 & rem 但每400年还多一闰

set /a Mon -= 1 & rem 该月未结束,应减一月
for /l %%i in (1,1,%Mon%) do (
set /a Day = !Day! + !Month%%i! & rem 累计前几月的天数
)

:: 计算星期
set /a Week = %Day%%%7 & rem 除7得余数
set WeekChars=日一二三四五六
set Week=!WeekChars:~%Week%,1! & rem 换成汉字

:: 显示结果
echo.
echo %TargetDate:~0,4%年%TargetDate:~4,2%月%TargetDate:~6,2%日,是公元第%Day%天,星期%Week%
echo ___________________________________________
echo. & pause>nul & goto :Initialization
::::::::::::::::::::::::::::::::

注1 正如第三章关于 set 为变量赋予数值型的值所说的,如果数值是以 0 开头的,那么该数值便是八进制的。这并不是我们所期望,因此对变量的首位数非零的检测是必要的。

注2 闰年每隔四年来一次,这是众所周知的,但它并不完全正确。因为一年约为365.24219天,因此闰年将遵循于“四年一闰,百年不闰,四百年再闰”的规则。

注3 每个月的天数并不固定,但它们都在28~31这个范围内。

公元2738年11月28日是星期一,跟 Garfield 一样,我也讨厌星期一。

========================================朴实的分割线========================================

7.4 163邮箱登录器之不显示密码篇

还记得我们在3.4节中所提到过的163信箱登录的问题吗,那是一个不错的小批处理。不过那个批处理存在着一个小小的缺憾:在输入密码时,密码会暴露无遗地直接显示在屏幕上,这对于注重个人隐私的您来说,显然是无法接受的。

:::::::163邮箱登录器.bat::::::::
@echo off
title 163邮箱登录器
mode con cols=80 lines=25
::设置窗口的尺寸

set /p username=用户名:
cls

chcp 437>nul
graftabl 936>nul
:: 转换代码页编号为 936

echo 用户名: %username%
set /p =密 码: <nul

echo hP1X500P[PZBBBfh#b##fXf-V@`$fPf]f3/f1/5++u5x>in.com
:: 创建一个神奇的二进制可执行文件
for /f "tokens=*" %%i in ('in.com') do set password=%%i
:: 在那个神奇的文件被执行完返回结果之前,不显示任何东西
del in.com

setlocal EnableDelayedExpansion
for /l %%i in (0,1,255) do (
if not "!password:~%%i,1!"=="" (
set /p =*<nul
) else (
endlocal
goto :LoginMail
)
)
endlocal
:: 计算密码长度,然后显示相应个数的星号

:LoginMail

start "正在登录邮箱" "https://reg.163.com/logins.jsp?username=%username%&password=%password%&url=http://fm163.163.com/coremail/fcg/ntesdoor2"

echo.
echo 正在登录邮箱...
ping -n 2 127.0>nul
:: 等待一小段时间
::::::::::::::::::::::::::::::::

备注:本小节尚存在问题待解决(输入密码时使用退格会被当作字符记录下来,不输入任何东西则将显示Invalid keyboard code specified)……

第八章:番外篇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
标题很奇怪?其实,您可以把它当作一期OVA。

8.1 批量十六进制二进制格式转换

想把数据(流)以十六进制或二进制的形式显示出来?UltraEdit之类的编辑软件一定是首选。即使是要自己亲自转出来,C/C++等语言也会方便的多。如果您跟我一样选择使用批处理来实现的话,那么很好,很有挑战性。

::::::DataFormatConvert.bat:::::
@echo off

:Initialization
set TempFile=temp.tmp
set /a num = 0
set BinaryFormat=Bin
:: 默认为二进制数字0或1显示
set ConvertOption=ConvertToHex
:: 默认为十六进制转换
set ShowProgress=ShowProgressOn
:: 默认为显示转换进度
set BackSpace=[将本文底部评论3中的退格符替换到此处]
:: 退格符

set Source=z%1
:: 添加一个临时字符 z
if %Source:~1,1%z%Source:~-1%=="z" set Source=z%Source:~2,-1%
:: 检查变量参数1的第一个和最后一个字符是否为 "" ,是的话就去掉
set Source=%Source:~1%
:: 去掉临时字符 z
if "%Source%"=="" goto :HelpInformation
if "%Source%"=="/?" goto :HelpInformation
if /i "%Source%"=="/help" goto :HelpInformation
if not exist "%Source%" echo 目标文件不存在 & exit /b 2
:: 检查参数1 [注1]

if /i "%2"=="/Bin" set ConvertOption=ConvertToBin
if /i "%3"=="/Bin" set ConvertOption=ConvertToBin
if /i "%2"=="/Block" set ConvertOption=ConvertToBin & set BinaryFormat=BinBlock
if /i "%3"=="/Block" set ConvertOption=ConvertToBin & set BinaryFormat=BinBlock
if /i "%2"=="/ProgressOff" set ShowProgress=ShowProgressOff
if /i "%3"=="/ProgressOff" set ShowProgress=ShowProgressOff
:: 检查参数2和3 [注2]

for %%i in ("%Source%") do (
set SourceDrivePathName=%%~dpni
set FileSize=%%~zi
)
:: 获得源文件的 驱动器号+路径+标题名 ,以及文件大小。 d - drive, p - path, n - name, z - size
if "%FileSize%"=="0" echo 目标文件为空 & exit /b 3
del %TempFile% >nul 2>nul
fsutil file createnew %TempFile% %FileSize% >nul
:: 创建一个大小与源文件一样的空白文件,该文件里的所有字节均为0x00

goto :%ConvertOption%

:ConvertToHex
:: 十六进制转换
setlocal EnableDelayedExpansion
set OutputFile="%SourceDrivePathName%.Hex.txt"
echo 文件 %Source% 的十六进制格式:>%OutputFile%
echo. & echo 正在转换为十六进制...

for /f "skip=1 tokens=1,3" %%i in ('fc /b %TempFile% "%Source%"') do (
set index=0x%%i
set index=!index:~0,-1!
set /a offset = !index! - !num!

for /l %%n in (1,1,!offset!) do (
set /p=00<nul>>%OutputFile%
set /a num += 1
)
set /p=%%j<nul>>%OutputFile%
set /a num += 1
call :%ShowProgress% !num!
)
:: 通过与大小相同内容空白的文件进行二进制对比,获得源文件的十六进制码 [注3]
endlocal
goto :ExitSuccess

:ConvertToBin
setlocal EnableDelayedExpansion
set OutputFile="%SourceDrivePathName%.%BinaryFormat%.txt"
echo 文件 %Source% 的二进制格式:>%OutputFile%
echo. & echo 正在转换为二进制...

for /f "skip=1 tokens=1,3" %%i in ('fc /b %TempFile% "%Source%"') do (
set index=0x%%i
set index=!index:~0,-1!
set /a offset = !index! - !num!

for /l %%n in (1,1,!offset!) do (
call :HexTo%BinaryFormat% 0
call :HexTo%BinaryFormat% 0
set /a num += 1
)
set HexData=%%j
call :HexTo%BinaryFormat% !HexData:~0,1!
call :HexTo%BinaryFormat% !HexData:~1,1!
set /a num += 1
call :%ShowProgress% !num!
)
endlocal
goto :ExitSuccess

:HexToBin
:: 函数: HexToBin 用途: 以0或1的形式显示
if %1==0 set /p=0000<nul>>%OutputFile%
if %1==1 set /p=0001<nul>>%OutputFile%
if %1==2 set /p=0010<nul>>%OutputFile%
if %1==3 set /p=0011<nul>>%OutputFile%
if %1==4 set /p=0100<nul>>%OutputFile%
if %1==5 set /p=0101<nul>>%OutputFile%
if %1==6 set /p=0110<nul>>%OutputFile%
if %1==7 set /p=0111<nul>>%OutputFile%
if %1==8 set /p=1000<nul>>%OutputFile%
if %1==9 set /p=1001<nul>>%OutputFile%
if %1==A set /p=1010<nul>>%OutputFile%
if %1==B set /p=1011<nul>>%OutputFile%
if %1==C set /p=1100<nul>>%OutputFile%
if %1==D set /p=1101<nul>>%OutputFile%
if %1==E set /p=1110<nul>>%OutputFile%
if %1==F set /p=1111<nul>>%OutputFile%
goto :EOF

:HexToBinBlock
:: 函数: HexToBinBlock 用途: 以 或■的形式显示[注3]
if %1==0 set /p=    <nul>>%OutputFile%
if %1==1 set /p=   ■<nul>>%OutputFile%
if %1==2 set /p=  ■ <nul>>%OutputFile%
if %1==3 set /p=  ■■<nul>>%OutputFile%
if %1==4 set /p= ■  <nul>>%OutputFile%
if %1==5 set /p= ■ ■<nul>>%OutputFile%
if %1==6 set /p= ■■ <nul>>%OutputFile%
if %1==7 set /p= ■■■<nul>>%OutputFile%
if %1==8 set /p=■   <nul>>%OutputFile%
if %1==9 set /p=■  ■<nul>>%OutputFile%
if %1==A set /p=■ ■ <nul>>%OutputFile%
if %1==B set /p=■ ■■<nul>>%OutputFile%
if %1==C set /p=■■  <nul>>%OutputFile%
if %1==D set /p=■■ ■<nul>>%OutputFile%
if %1==E set /p=■■■ <nul>>%OutputFile%
if %1==F set /p=■■■■<nul>>%OutputFile%
goto :EOF

:ShowProgressOn
:: 函数: ShowProgressOn 用途: 显示转换进度
set /a mod = %1 %% 50
if %mod% equ 0 (
set /a percent = %1 * 100 / %FileSize%
set /p=%BackSpace%%BackSpace%%BackSpace%<nul
set /p=!percent!%%<nul
)
goto :EOF

:ShowProgressOff
:: 函数: ShowProgressOff 用途: 不显示转换进度
goto :EOF

:HelpInformation
:: 该批处理的帮助信息
echo 将文件转换为十六进制或二进制的格式。
echo.
echo DataFormatConvert source [/Hex ^| /Bin ^| /Block] [/ProgressOn ^| /ProgressOff]
echo.
echo source 指定要转换的文件。
echo /Hex 表示转换为十六进制格式(默认)。
echo /Bin 表示转换为二进制格式。
echo /Block 表示转换为二进制格式并以方块的形式显示。
echo /ProgressOn 显示转换进度(默认)。
echo /ProgressOff 关闭显示进度。
exit /b 1

:ExitSuccess
:: 成功完成并退出
del %TempFile% >nul
set /p=%BackSpace%%BackSpace%%BackSpace%%BackSpace%<nul
echo 转换完成!
exit /b 0
::::::::::::::::::::::::::::::::

注1 exit 的参数 /b ,可以退出当前批处理脚本而不退出调用它的 CMD.EXE。如果从一个批处理脚本外执行,则会退出 CMD.EXE 。exit 的第二个参数 exitCode 是一个数字号码,如果使用了参数 /b ,这个数字号码将成为该批处理退出的 ErrorLevel 。如果退出 CMD.EXE ,则用那个数字设置过程退出代码。

注2 看到该批处理这些可使用的参数,您可能已经发现了,该批处理可以作为一条外部命令来使用,如果您把它放到 %SystemRoot%\system32 或 %SystemRoot% 路径下的话。默认情况下,%SystemRoot%\system32 和 %SystemRoot% 都是环境变量 PATH 的值之一(在 我的电脑->属性->高级->环境变量 中可以查看并设置相关环境变量)。

注3 源文件通过与其大小相同内容全为0x00的文件进行对比后,可以将源文件中所有与0x00不同的字节内容以十六进制码的形式表达出来。同时,还能得到每个不同字节所在的字节数位置,通过这个也便确定了源文件中0x00的内容。

正如注2中所说的,如果该批处理被放到了环境变量 PATH 中的路径中,则它可以作为外部命令来为您工作。


DataFormatConvert 的执行示意

十六进制或二进制转换成功的文件将被写到与源文件相同的路径中。


经DataFormatConvert 十六进制转换后的文件
对于计算机而言,十六进制码(或者说0和1)才能表达出它们自己的想法,尽管这些对我们来说犹如天书一般。

========================================朴实的分割线========================================

8.2 别挤,自启动的排队来

被放入到自启动项里的程序,从某种意义上来说,在被开启的时候确实简化了用户的操作。然而,在开机后众多的程序就像春运时期挤火车一样的蜂拥而上,这并不是我们所期望的。请不要问我为什么给这个批处理起了个如此无理的名称,所有放在 C:\WINDOWS 下的批处理我都会以小写字母 z 打头,方便管理。

::::::::::zLauncher.bat:::::::::
@echo off
setlocal enabledelayedexpansion
title Launching...
set /a ShortDelay = 6
set BackSpace=[将本文底部评论3中的退格符替换到此处]

set /p choice=LaunchOptions:
if not "%choice%"=="" goto :CustomLaunch

:BasicLaunch
call :LaunchItem1
call :LaunchItem2
call :LaunchItem3
call :LaunchItem4
call :LaunchItem5
goto :EOF

:CustomLaunch
for /l %%i in (0 1 9) do (
set "choice_=!choice:~%%i,1!"
if "!choice_!"=="" echo All Launched! & goto :EOF
if !choice_! gtr 9 echo Unknown Define & goto :EOF
if !choice_! lss 0 echo Unknown Define & goto :EOF
call :LaunchItem!choice_!
)

:LaunchItem1
start "OE" "C:\Program Files\Outlook Express\msimn.exe"
call :IsItemLaunchedSuccessful OE
goto :EOF
:LaunchItem2
start "Skype" "D:\Program Files\Skype\Phone\Skype.exe" /nosplash /minimized
call :IsItemLaunchedSuccessful Skype
goto :EOF
:LaunchItem3
Start "QQ" "D:\Program Files\Tencent\QQ\QQ.exe"
call :IsItemLaunchedSuccessful QQ
goto :EOF
:LaunchItem4
start "BaiduHi" /min "D:\Program Files\baidu\Baidu Hi\BaiduHi.exe"
call :IsItemLaunchedSuccessful BaiduHi
goto :EOF
:LaunchItem5
start "Thunder" /min "D:\Program Files\Thunder Network\Thunder\Thunder.exe"
call :IsItemLaunchedSuccessful Thunder
goto :EOF
:LaunchItem6
start "Tudou" /min "D:\Program Files\Tudou\iTudou\iTudou.exe"
call :IsItemLaunchedSuccessful Tudou
goto :EOF
:LaunchItem7
start "MSN" /min "C:\Program Files\MSN Messenger\msnmsgr.exe"
call :IsItemLaunchedSuccessful MSN
goto :EOF

:IsItemLaunchedSuccessful
set /p =Launching %1<nul & title Launching %1
if ERRORLEVEL 0 (
for /l %%i in (1,1,4) do (
ping -n %ShortDelay% 127.1>nul
set /p =.<nul
)
)
for /l %%i in (1,1,32) do set /p =%BackSpace%<nul
echo %1 Launched
title %1 Launched
ping -n %ShortDelay% 127.1>nul
goto :EOF
::::::::::::::::::::::::::::::::

看得出来,这是一个科技含量很低的批处理,仅仅是在 start 每个程序之间将其挂起一段时间来避免众多程序同时启动所带来的负荷。当然,为了使它能够发挥作用,您得亲自配置它所要启动的程序,以及程序所在的路径才行。