DOS批处理教程
写在前面
本文后面章节很大程度上转载了这篇教程,欢迎大家看原作者的教程,写得很好。转载的目的是方便修改和避免丢失。
什么是批处理
批处理(batch)就是对某对象进行批量处理。
DOS批处理脚本是基于Dos命令编写的脚本,应用于DOS和Windows系统中,由DOS或Windows系统内部的命令解释器(通常是COMMAND.COM或CMD.EXE)解释运行,类似于Unix的Shell脚本。
不区分大小写
简单批处理命令
先看一段小代码:
@echo off
echo 今天是7月12日
pause
这段代码的执行结果见下图:
看运行结果很容易猜到echo + 一句话会在命令行显示出这句话;同时如果不加pause,命令行窗口就会一闪而过。
但你可能会问,代码中的@是什么作用,echo off又是什么命令?先来做几个小实验——
(1)首先把@去掉
echo off
echo 今天是7月12日
pause
运行结果:
(2)然后把echo off 换成echo on
@echo on
echo 今天是7月12日
pause
运行结果:
(3)然后把@去掉的同时把echo off 换成echo on
echo on
echo 今天是7月12日
pause
运行结果:
对照上面四组代码的运行结果,@的作用就呼之欲出了,简单来说,它的作用就是在执行窗口中不显示它后面这一行的命令本身。也就是说如果在行首加了@,那么这一行的命令就不会显示了(该执行还是要执行滴~)。
echo off的命令就是将它后面的所有命令都不显示命令本身只显示执行后的结果除非执行到echo on命令。一般情况下我们是不需要将命令显示出来的,所以大部分的批处理脚本第一句命令就是@echo off。
echo命令
echo的中文为“反馈”“回显”的意思。
上面的echo off和echo on只是echo的其中一个用法;而echo + 想要显示的信息是另一种用法,综合起来其命令格式如下:
echo [{on|off}|{message}]
[ ]表示必选项,{ }表示可选项,|表示或。下同。
help 命令
遇到陌生的dos命令,除了上网找,还可以在命令提示符中使用help命令。比如我们要看一下echo命令的解释,就可以用echo /? 或 help echo。效果如下:
help给出的解释已经很详细了。
>和>>
> 表示将输出结果打印到某处。比如:echo Hello world!>d:\a.txt 表示将 Hello world! 这句话写入到 D:\a.txt 文件中。如果以前该文件中已经存在,并且有自己的内容,那么以前的内容就被覆盖掉了。
>> 与 > 类似,也可以将输出结果打印到某处,不同的是它不会覆盖原有内容,而是加在原有内容后面。
如果一条命令后面跟上 >nul ,比如 pause>nul 表示将 pause 这条命令的输出显示到空设备里, nul 表示为空。用了 pause>nul 这条命令后,"按任意键继续..."的提示就不再出现了。
rem命令
rem 后面跟上一段文字,在批处理中可以作为注释用。rem 和它后面跟的文字在实际运行时并不会起任何作用,只是为了方便人们阅读该批处理时更容易理解而已。
除了 rem 外,两个连续的冒号 :: 也起同样的作用。rem 与 :: 的区别在于,rem 也是一种命令,在 echo on 的情况下会被显示出来,而 :: 却不会。
prompt命令
prompt ,这就是命令提示符中所谓的"提示符"了。在命令提示符中输入 prompt 加一段文字能够使得提示符不再是以传统的路径名和大于号组成的,而是以我们刚才输入的那段文字开头的。
我们来看一下help对它的解释:
比如输入prompt $D:
此外,要想恢复以前的路径名和大于号为开头的提示符,只需要再输入 prompt $p$g 即可。这里$p 表示当前驱动器和路径, $g 表示大于号。
其他常用命令
| 功能 | 格式 | 示例 |
|---|---|---|
| 打开文件或文件夹 | start [文件(夹)路径] | start D:\some_dir |
| 删除文件 | del [文件路径] | del *.txt |
| 复制文件 | copy [file] [dst_dir] | copy *.jpg D:\images| |
| 重命名文件 | ren [源文件] [目的文件] | ren *.jpg *.mp3 |
| 创建文件夹 | md [dir] | md some_dir |
| 创建文件 | cd.>[文件名] | cd.>a.txt |
| 自动关机 | shutdown -s -t [秒数] | shutdown -s -t 300 |
| 取消自动关机 | shutdown -a | |
| 立刻重启 | shutdown -r -t 0 | |
| 自动休眠 | shutdown -h -t [秒数] | shutdown -h -t 80 |
| 隐藏文件夹 | attrib +s +h [文件夹] | attrib +s +h D:\some_dir |
| 取消隐藏文件夹 | attrib -s -h [文件夹] | attrib -s -h D:\some_dir |
| 用浏览器访问网址 | explorer [网址] | explorer http://www.baidu.com |
attrib命令
attrib [+|-] [r|a|s|h]
+ 设置属性
- 清除属性
r 只读属性
a 存档属性
s 系统属性
h 隐藏属性
赋值、调用和参数
赋值(set命令)
给变量赋予字符串的值
SET [variable=[string]] 就能简单地给该变量赋值为字符串了。例如 set var=Hello world! 。
为了确认一下变量 var 的值是否是 Hello world! ,可以用 set var 来查看变量 var 的值。用 set v 可以查看所有以字母 v 开头变量的值。直接输入 set 可以查看所有变量的值。
另外,变量两侧加上百分符号 % 用来表示该变量的值(内容)。这样做可以将该变量的值赋给其他变量或是用做计算显示等处理。比如echo var 和 echo %var% ,所得到的返回输出分别为:var 和 Hello world! 。
SET 命令不允许变量名含有等号。
给变量赋予数值型的值
在 set 后面加上 /a 的参数可以给变量赋予一个数值型的值,例如 set /a var=48 表示将数字48赋给变量var。该数值型的变量是一个32位的整数型数值,即占用4个字节,能表示的数值个数为2的32次方,含正负号,范围为:-2147483648~2147483647。
从外部获得输入的赋值方式
在 set 后面加上 /p 的参数,可以将变量设成用户输入的一行输入。读取输入行之前,显示指定的 提示文字。当然,提示文字也可以是空的。比如 set /p var=请输入一些文字: ,可以显示出一段提示文字"请输入一些文字:"并能将用户输入的信息存到变量var里。
/p 的参数还有很多诸如对字符串的替代、提取、增减等功能,具体可以参考 set 的相关帮助信息。
对数值型变量进行运算操作
set /a num=48
set /a result=%num%+12
上面的命令表示先给数值 48 赋给变量 num ,然后再把变量 num 的数值与数值 12 相加后的结果赋给变量 result。
除了加法操作,set /a还支持以下操作:
对变量中的字符串进行替换
替换功能其格式为:原始变量的名称后面跟上冒号 : ,再加上想要被代替的内容,紧接着一个等号 = ,然后再加上用来代替的新内容,最后用两个百分号把以上这些包括起来即可。虽然此时原始变量的值并没有改变,但百分号里的内容可以赋给一个变量,这个变量可以是原始变量。
例:echo %var:o=z% ,效果为把 Hello world! 里所有的字母 o 都用字母 z 代替,并显示出来,而变量 var 的值却没有变化。如果是set var=%var: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 个字符为止的内容。
为了清晰的看到效果,我们定义变量 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个字符为止 |
调用
与很多编程类的东西一样,批处理并不一定非得按照文本中命令的排列顺序一行一行地执行。如果遇到了 goto、call、start 这样的跳转、调用、启动等语句,程序通常会变得多层化,执行起来会更加有效。
跳转(goto)
goto 对于多少有点编程基础的朋友来说,想必不是一件难以理解的东西。goto 跟上标签就能直接让程序从该标签处开始继续执行随后的命令,不论标签的位置是在该 goto 命令的前面还是后面。
标签必须以单个冒号 : 开头,但不区分大小写。有个特殊的标签 :EOF 或 :eof 能将控制转移到当前批脚本文件的结尾处,它是不需要事先定义的。
::::::::::::跳转.bat::::::::::::
@echo off
goto :FirstLable
:SecondLable
echo 然后显示这句
pause
goto :EOF
:FirstLable
echo 首先显示这句
pause
goto :SecondLable
::::::::::::::::::::::::::::::::
调用(call)
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 的用法介绍)。
启动(start)
start 虽然也不是一个简单的命令,但用法绝对不难理解。来几个例子:
- start msconfig 用来打开"系统配置应用程序";
- start notepad 则可以打开一个记事本;
- start "这就是所谓的标题" cmd 用来打开一个新的命令提示符;
- start "随便写个标题" http://www.baidu.com 便打开百度的首页;
- start "开玩了" E:\game\starcraft\starcraft.exe 却是开始星际争霸(如果您的电脑里安装了星际且路径与上述一致的话)等等。
虽然 start 的参数很多(具体用法在输入 help start 后可以得到),但通常情况下我们只需要知道 start 后面加上标题,再跟上想要执行程序、命令或网址即可。值得注意的是:标题要用双引号引用起来,否则会被作为可执行的文件来处理;所要执行的东西如果不是系统内部程序或命令的话,则需要我们给出具体的路径,比如绝对路径。
当然,start 也可以打开另外一个批处理。这看起来似乎与 call 相仿,却有一些区别。当使用start打开一个程序或批处理时,此时这两个程序已经完全独立开了,是两个独立的进程。之前的批处理程序也同时继续执行着。
参数
参数的传递
比如在一个批处理脚本中有如下代码:
:::::::::::测试.bat:::::::::::
echo 您输入的第1条参数为 %1
echo 您输入的第2条参数为 %2
pause
::::::::::::::::::::::::::::::::
其中,%1 和 %2 分别代表运行"测试.bat"批处理时所跟的两个参数。那么它该如何获得所谓的参数1和参数2呢。双击运行"测试.bat"当然是不行的了。在命令提示符里输入"测试.bat"的全名,并在后面加上两个参数即可。
比如测试.bat Tom and Jerry 。运行时我们会发现 %1 和 %2 分别显示为 Tom 和 and ,Jerry 为作为第3个参数来处理,但该批处理中却未用到 %3 。
提示:在XP等操作系统中,对于汉字的输入可用 Ctrl + 空格 切换出中文输入法;也可以按 Tab 键让其自动切换并补充完成您所想要输入的路径。
参数的输入与输出
参数不仅可以是值,也可以是变量。如果是变量的话,它既可以作为输入,也可以作为输出。
这里用两个.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 ,然后再对比一下结果,相信您一定会有所收获。
函数
在一个批处理的内部调用时,使用参数会怎么样呢?那么这就是函数的雏形。
把之前的"调用.bat"稍微也改一下
::::::::::::调用.bat::::::::::::
@echo off
call :FirstLable 很好很强大
:SecondLable
echo 然后显示这句
pause
goto :EOF
:FirstLable
echo 首先显示这句,后面跟的参数为 %1
pause
::::::::::::::::::::::::::::::::
上面的很好很强大就是参数,由%1接收。
goto 所跟的标签不能加参数。start 所跟的程序可以加参数。
条件和循环语句
条件
批处理程序的语言格式相比较我们常见的 C 语言来说,并不是那么的严谨,至少看上去是更自由一些。比如 if 在批处理中的具体用法及格式就有很多,使用和发挥的余地也很大,但随之带来的问题就是我们不得不多花一些时间来记忆其各种用法和格式并分辨它们之间的差异。为了简化该问题使之更容易理解,在此我们并不打算过早地接触 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 的时候,不妨总是在字符串上使用双引号,这样既好阅读,又不容易引起歧异。
if-else
@echo off
if "%TIME:~0,2%" lss "12" (
echo 现在是上午
) else (
echo 现在是下午
)
其中,变量 TIME 是动态环境变量之一,表示当前时间(在 set/? 中有介绍)。%TIME:~0,2% 的含义还没忘记吧,意思是取变量 TIME 的前两个字符。lss 是 if 命令扩展用法,表示 小于 的意思。此外,还有等于、不等于、大于、大于等于、小于等于 的缩写,详细信息可以在 if/? 中获得。因此,对于上述批处理的理解就是:如果当前时间(前两位表示小时)小于12(点)的话,那么将显示输出“现在是上午”,否则就显示为“现在是下午”。
注意这里的大小比较判断只是对其ASCII符的大小比较,并不是真正的数值型变量的比较,稍后下文会有关于数值型变量比较的介绍。
嵌套
@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 现在是晚上
)
)
数值型变量的比较
@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
延迟变量扩充
考虑到读取一行文本时所遇到的目前扩充的限制时,延迟变量扩充是很有用的,而不是在执行的时候。
下面的例子可以很好的说明直接变量扩充与延迟变量扩充的区别。
@echo off
setlocal EnableDelayedExpansion
set /a num=5
if %num% == 5 (
set /a num*=3
echo 在 if 语句之前,变量 num 等于 %num%
echo 但变量 num 在经过运算后,且由于延迟变量扩充被启用,变量 num 等于 !num!
)
echo 但最终变量 num 还是等于 %num%
pause
注意到批处理中的setlocal EnableDelayedExpansion (setlocal/? 查看相关信息),这表示开启延迟变量扩充。此时的!num!才有意义。不然 !num! 将无法被识别,因为在默认情况下,延迟变量扩充是被停用的。if 条件下的两行 echo 在输出变量值的时候用到的符号不一样,一个是用百分号 % 包括起来的,另一个用的却是惊叹号 ! 。虽然在显示 %num% 之前已经使变量 num 的数值乘了3倍,但是由于延迟变量的扩充,使得 %num% 的结果仍然是 5 ,但用 !num! 显示出的值已经变为 15 了。
其他用法
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 的判断对象不是文件,而是变量,它用于判断环境变量是否被定义。
循环
在看过 for/? 后,可以归纳出 for 大致可以分三种常用的类型(或者叫使用方法)。从针对的循环目标来看,它们分别是针对于文件、数字、以及文字。
for %i in (.) do @echo %i
这就是 for 的一般使用格式。其内容可以理解为:在某一范围内(in),对于其中的某一文件来说(for),做如下的处理(do)。而 for %i in (.) do @echo %i 就是在当前工作目录的所有文件中(in (.)),对于其中的某一文件(for %i),做出显示其名称的处理(do @echo %i)。变量 i 仅在当前循环语句 for 里起作用,%i 表示其值。
注意:以上是直接在命令提示符里以命令的形式表达出来的写法;在批处理文件中应使用双百分号 %% 代替单百分比号 % ,就像:%%i。这是因为%也用于转义字符。
ESCAPE字符 %
SCAPE字符通常被译为转义字符,但也有更形象的译名脱逸字符、逃逸字符等。类似于C语言中的转义字符 \ 。
批量修改文件名
批量修改文件名是for循环典例的应用。
@echo off
setlocal EnableDelayedExpansion
set /a num=1
for %%i in (D:\test\*.txt) do (
ren "%%i" !num!.txt
set /a num+=1
)
使用了 setlocal EnableDelayedExpansion 后,可以让 for 或 if 后面的执行语句中变量的值随其变化而不断更新(所以后面使用了 !num! 而不是 %num%)。整个批处理的处理过程就是对 D:\test*.txt 中的所有文本文件进行批量改名,文件名从 1.txt 开始依次为 2.txt 、3.txt ……。
以上批处理是固定了文件的路径以及文件后缀名。为了增加该批处理的功能,我们可以让用户自己选择要进行改名的文件所在路径,以及选择所进行文件修改的后缀名。当然,有些朋友还希望有给文件批量加上前缀(比如:前缀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
)
::::::::::::::::::::::::::::::::
对数字循环
/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 。
::::::::::圆圈方阵.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
::::::::::::::::::::::::::::::::
对文字循环
for 后面跟参数 /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.
组合命令和管道命令
组合命令
&和&&
echo Checking what executable files we have in WINDOWS... & dir C:\WINDOWS\*.exe & echo And we got lots of stuff here.
不难理解 & 在多个命令之间所起的连接作用。事实上,我们完全可以将这三者分成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
管道命令
> 、>>
它们是输出重定向命令,在前面已经介绍过。其主要功能就是将一条命令或某个程序输出结果的重定向到特定文件中。> 与 >> 的区别在于,> 会清除调原有文件中的内容后写入指定文件,而 >> 只会追加内容到指定文件中,而不会改动其中的内容。下面将会是一个很有用的例子。
众所周知,System32 文件夹是多数木马潜伏和发作的好地方。当我们刚装好机器的时候,可以给此时机器里还没有病毒、木马的 System32 文件夹里的所有可执行文件(.exe)和动态链接库文件(.dll)作个记录。等以后发觉 System32 里多了可疑的东西的时候再作个记录,然后跟之前的记录对比一下,就很容易发现问题了。
为了方便作记录,我们可以执行类似下面的一条命令:
dir %windir%\system32\*.exe>D:\%DATE:~0,10%的exe文件.txt
其中,%windir% 是当前启动系统所在的目录,默认情况下通常是 C:\WINDOWS ;%DATE:~0,10% 是指当天的日期,比如2007-11-30。而整条命令的结果就是把 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
|
没错,只是一条竖线而已。它可以将它左边命令的输出结果放到它右边的命令里作为输入参数。这种用法在 Unix 里很常见。
有个能简单检测机器是否中冰河木马的例子:
netstat /a /n | find "7626" && echo 已被冰河感染 || echo 未被冰河感染
其中:netstat 用于显示当前的网络连接情况,参数 /a 显示所有连接和监听端口;/n 以数字形式显示地址和端口号。仅仅执行 netstat /a /n 后,您将会看到输出的结果为:各种协议下的本地和目标的地址及端口,以及它们的连接状态。
命令 find 则可以搜索指定的字符串,在指定的文件中。netstat /a /n 的输出结果将通过 | 成为命令 find 的第二个参数。因此整个的一条命令可以理解为:在网络连接状态的输出结果中,查找字符串 7626 ,如果查找成功的话,将输出被感染的提示,否则便提示未感染。&& 和 || 请参考第5.1.3节的例子。(端口 7626 是冰河所使用的默认端口。事实上,这并不是一个严谨的检测冰河木马存在的方法,但它却是说明组合命令和管道命令一个很好的例子)
常用实例
批量修改文件名
在前面,我们已经会使用循环命令对大量文件改名进行批量处理。但总结一下,该批处理并不是很健壮。判断一个程序的好坏,往往不是站在程序员的角度,而从用户的角度出发。比如:在用户使用它的时候,如果输入了不正确的路径格式怎么办?如果输入了含有非法符号的前缀怎么办?输入的扩展名也有问题怎么办?改完名后看不到是否执行成功的反馈信息,等等。带着这些想法,我们将原程序再次修改一下。
:::::::批量修改文件名.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
::::::::::::::::::::::::::::::::
批量备份进程映像列表以及注册表自启动项
什么是备份;为什么要备份;怎么去备份;备份些什么,说起来我们可能会更关心这些问题。
什么是备份:将某事物复制出额外一份完全一样的事物,并妥善保存起来的过程;
为啥要备份:当原事物出现异常或无法使用的时候,取出复制品并代替原来无法使用的事物;
怎么去备份:复制,与原事物完全一样地去复制,然后保存到安全的地方;
备份些什么:系统、文档、数据库、工程、记录、进度等等。
不过,这些都不是本文的主旨。
本节的写作起因是从《利用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个只是常见的自启动键值,病毒或木马所在注册表中添加或修改的项不仅此而已,更多需要备份的项可用类似的方法自行添加。
对于查找可以进程的方法,与前文 中所提到的对比系统文件夹找差异的过程类似,使用命令 fc 对比两个不同时期的进程映像名列表,找出多余的可疑进程(如果您无法确定某个进程是干什么用的或者是否存在安全风险,可以先到网上搜索并了解一下,比如:进程知识库)。至于是用 taskkill 还是 ntsd 来终止可以的进程,怎样用属性加时间来查找并锁定可以的文件,以及 reg import 注册表导入恢复的用法,这些仍然不是本文的重点。
批量查看同一子网络下的所有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行的那一堆奇怪的符号,表示的是退格符号,能删除掉当前行中以显示的内容。
卸载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
::::::::::::::::::::::::::::::::
环游世界
没错,我想是认真的,对于任何您想去往地球上的地点,您所需要做的仅仅是提供一个目的地的经纬度坐标而已。当然,我是不会给您提供去往世界各地的签证和机票的,但是海量的卫星照片还是能让您过足了瘾的。此时您手头可能并没有足够的坐标资源,不过我这里"恰好"有些陈芝麻烂谷子,是当年在留园网上搜集的地理坐标,具体请参阅《关于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. 经纬度从度分秒格式转换为小数格式只需要:(度6060+分*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 的运行界面

进程分析
写了一篇很简单却又很占篇幅的玩意,被我称之为"进程分析者"。此批处理的构思很轻松,只是简单地使用 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
::::::::::::::::::::::::::::::::
公元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 一样,我也讨厌星期一。
163邮箱登录器之不显示密码篇
还记得我们在前面所提到过的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)……
批量十六进制二进制格式转换
想把数据(流)以十六进制或二进制的形式显示出来?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 中的路径中,则它可以作为外部命令来为您工作。

浙公网安备 33010602011771号