[新手上路]批处理新手入门导读[视频教程]批处理基础视频教程[视频教程]VBS基础视频教程[批处理精品]批处理版照片整理器
[批处理精品]纯批处理备份&还原驱动[批处理精品]CMD命令50条不能说的秘密[在线下载]第三方命令行工具[在线帮助]VBScript / JScript 在线参考
返回列表 发帖

[原创] 脚本学习及应用分享 — 批处理和脚本的交互

脚本学习及应用分享

批处理和Windows脚本的交互

(一)在批处理中调用Windows脚本

---------------

脚本可以使用 cscript.exe 在控制台中运行,而我们知道命令控制台中的输出,包括标准输出和标准错误输出,可以使用管道 1 或者 2 来重定向以加以利用,所以在批处理中调用Windows脚本并处理它的结果是比较容易的。

示例:
一般情况下调用VBS都带参数的,因此我们在这个小脚本中加入参数的获取。
它的功能是我们给定一个人名,显示 XX你好,如果参数不对则提示错误。
(关于脚本参数的获取可以查阅 WScript.Arguments 相关的资料)
SayHello.vbs
  1. ' 进入程序是显示欢迎信息
  2. WScript.Echo "欢迎使用本程序。"
  3. ' 得到参数对象
  4. Set args = WScript.Arguments
  5. ' 如果参数个数是一个,正常运行,否则提示错误信息并退出。
  6. If args.Count = 1 Then
  7.   ' 得到第一个参数
  8.   name = WScript.Arguments(0)
  9.   ' 在标准输出中,即控制台的 通道1 输出一行文字
  10.   WScript.StdOut.WriteLine name & ",你好。"
  11. Else
  12.   ' 在标准错误中,即控制台的 通道2 输出一行文字
  13.   WScript.StdErr.WriteLine "参数个数不为1,请重试。"
  14.   ' 退出程序
  15.   WScript.Quit
  16. End If
复制代码
cscript.exe默认会在标准输出的开始加上微软版权信息,我们一般加上 //NoLogo 参数来屏蔽掉。
我们在CMD中来试试运行这个脚本,分别执行下面四条命令,看看结果如何:
  1. cscript //nologo SayHello.vbs "%username%"
复制代码
  1. cscript //nologo SayHello.vbs "%username%" 1>nul
复制代码
  1. cscript //nologo SayHello.vbs "%username%" "第二个参数"
复制代码
  1. cscript //nologo SayHello.vbs "%username%" "第二个参数" 2>nul
复制代码
结果显示,
第一条显示欢迎和XX你好,第二天什么都不显示,第三条显示欢迎和错误信息,第四条只显示欢迎。

用 cscript.exe 在控制台运行脚本时:
① WScript.Echo 在标准输出,通道1 中输出一些信息,并且在输出信息后会换行;
② WScript.StdOut 和 WScript.StdErr 分别在 【通道1】 和 【通道2】输出信息,WriteLine 输出信息后会自动换行,而用 Write 方法的话默认输出信息后不换行;

以前VBS需要交互输入信息时都是使用 InputBox 函数,其实在控制台运行时,还可以直接读取控制台中的输入,像批处理一样:
批处理
SayHello2.bat
  1. @echo off
  2. echo *** 开始: %date% %time% ***
  3. set /p name=请输入你的名字:
  4. echo %name%,你好!
  5. set /p=按回车结束程序...
  6. echo *** 结束: %date% %time% ***
复制代码
外观很相近的VBS脚本
SayHello2.vbs
  1. WScript.Echo "*** 开始:" & Now & " ***"
  2. WScript.StdOut.Write "请输入你的名字:"
  3. name = WScript.StdIn.ReadLine()
  4. WScript.StdOut.WriteLine name & ",你好!"
  5. WScript.StdOut.Write "按回车结束程序..."
  6. WScript.StdIn.ReadLine
  7. WScript.Echo "*** 结束:" & Now & " ***"
复制代码
③ 在控制台中运行脚本可以用 var = WScript.StdIn.ReadLine() 读取输入的一行信息,也可以用来实现暂停程序的作用。

到此我们知道了用VBS如何在控制台中读取出入和现实输出或错误信息,那批处理中如何获取这些信息并加以使用呢?
这不是新鲜的东西了,cscript.exe 就是一个命令行嘛,现在问题就变成了“批处理中如何获取命令的输出信息到变量?”
我用过的有两种方法,
一种是把输出信息重定向到临时文件,然后再读取这个文件到变量,比如接着用上面的脚本:
  1. @echo off
  2. cscript //nologo SayHello.vbs "%username%" 1>temp.txt 2>nul
  3. echo VBS运行结果是
  4. echo.
  5. echo ------- 全  部 --------
  6. type temp.txt
  7. echo -----------------------
  8. echo.
  9. echo ------- 第一行 --------
  10. set /p line1=<temp.txt
  11. echo %line1%
  12. echo -----------------------
  13. echo.
  14. echo ------- 连  接 --------
  15. setlocal enabledelayedexpansion
  16. set lines=
  17. for /f "delims=" %%a in (temp.txt) do (set lines=!lines!%%a )
  18. echo !lines!
  19. endlocal
  20. echo -----------------------
  21. del temp.txt
  22. pause>nul
复制代码
另一种就是用 for %%a in ('command') do 这种格式来获取,比如我们就要获取所有行连起来的内容

  1. @echo off
  2. setlocal enabledelayedexpansion
  3. set lines=
  4. for /f "delims=" %%a in ('cscript //nologo SayHello.vbs "%username%" 2^>nul') do (
  5.   set lines=!lines!%%a
  6. )
  7. echo 结果是
  8. echo !lines!
  9. pause>nul
复制代码


注意命令里面有特殊符号是要用转义符进行标志,如 2>nul 改为 2^>nul ,不然要出错。
一般我们编写的脚本都是只输出一行信息,或者我们只在乎的都是最后一行信息,于是可以采取这种方式:
  1. @echo off
  2. for /f "delims=" %%a in ('cscript //nologo SayHello.vbs "%username%" 2^>nul') do (set "lines=%%a")
  3. echo 结果是
  4. echo %lines%
  5. pause>nul
复制代码


写了个函数专门获取最后一行的输出,不知道实用性如何:
  1. @echo off
  2. call :GetScriptExecuteResult 0 result SayHello.vbs "%username%"
  3. echo 脚本执行的输出是:%result%
  4. pause>nul
  5. goto :EOF
  6. :: 执行脚本并将最后一行输出信息存入变量
  7. :: 参数依次为
  8. ::     第一个:0=所有输出,1=仅获取通道1, 2=仅获取通道2
  9. ::     第二个:要存入的变量名
  10. ::     第三个:要执行的脚本
  11. ::     后面若干个:脚本需要传入的参数
  12. :GetScriptExecuteResult
  13. IF [%1]==[] GOTO :GetScriptExecuteResult_End
  14. SET "___PT="
  15. IF [%1]==[0] SET "___PT= "
  16. IF [%1]==[1] SET "___PT= 2^>nul "
  17. IF [%1]==[2] SET "___PT= 1^>nul "
  18. IF "%___PT%"=="" GOTO :GetScriptExecuteResult_End
  19. IF [%2]==[] (GOTO :GetScriptExecuteResult_End) ELSE (SET "__VAR=%2")
  20. IF [%3]==[] (GOTO :GetScriptExecuteResult_End) ELSE (SET "___FN=%3")
  21. SET "__CMD=CSCRIPT.EXE //NOLOGO "%___FN%""
  22. SHIFT
  23. SHIFT
  24. :GetScriptExecuteResult_Block1
  25. SHIFT
  26. IF NOT [%1]==[] (
  27.   SET "__CMD=%__CMD% %1"
  28.   GOTO :GetScriptExecuteResult_Block1
  29. )
  30. FOR /F "DELIMS=" %%I IN ('%__CMD%%___PT%') DO (SET "%__VAR%=%%I")
  31. :GetScriptExecuteResult_End
复制代码
=======================================

最后再看一个有意思的模拟等待的例子,回车可以把光标移动到行首,所以可以擦写屏幕的一行。
本例的退出条件是时间到了,使用时根据自己的需求更改条件。

VBS脚本
Loading.vbs
  1. Set args = WScript.Arguments
  2. If args.Count < 3 Then WScript.Quit
  3. o = Timer
  4. t = args(0)
  5. s = args(1)
  6. w = args(2)
  7. l = Len(w) + 1
  8. w = w & String(Len(w), " ")
  9. i = 0
  10. While CInt(Timer) < o + t
  11.   i = i + 1
  12.   WScript.Sleep 333
  13.   temp = s & Left(w, i Mod l) & Right(w, l - (i Mod l)) & Chr(13)
  14.   WScript.StdOut.Write temp
  15. WEnd
  16. WScript.StdOut.WriteLine vbCrLf & "完成"
复制代码
在CMD中输入命令
  1. cscript -nologo Loading.vbs 10 "正在下载,请稍候" "......"
复制代码
在 10 秒钟之内显示等待,那六个点是循环一直在变的,到时间后退出脚本。
1

评分人数

学习了,新手中

TOP

支持~~顶顶~~~安心大巴

TOP

(二)在Windows脚本中使用批处理或程序

---------------

这些东西一直没整理好发出去,这才发觉一晃好多年过去了,对我个人的来说这些功能其实没有多大实用意义,好多内容可能别人的帖子里面也有提及的,但还是贴出来了却以往的一个心结吧。

需要说明的就是,本人的用词可能不是很科学或者说精准,对于某些原理之类的东西也一般没有去深究,也不会再文中大量说明,第一是关于各种资料性的文档仅仅本论坛发过的应该就有很多了,再重复没有意义,第二点,也是最重要的一点就是,我不是来搞研究的,用脚本只是想要把电脑弄的好玩一点,所以我主要精力放在实际结果上了,细节的原理有些也不太明白,说错了误导就不好了,作为一个工具我需要用到什么功能的时候再去查就行。

在脚本中可以创建 WScript.Shell 的对象,其中有 Run 和 Exec 方法可以运行外部程序。其中 Run 最典型的例子就是
1.         有参数可以控制窗口的状态,可以用于隐藏运行程序。比如
隐藏运行批处理 http://bbs.bathome.net/thread-5131-1-97.html
2.         自动登录QQ,自动XXXXXX。原理就是启动一个程序,然后模拟按键进行输入,模拟按键需要 Sendkeys 函数支持,论坛里面已经有很多帖子了,比如
启动记事本进行操作 http://bbs.bathome.net/thread-1380-1-1.html

看起来像是脚本于程序交互?其实这种方式很不稳定,我也是在最开始装13的时候用过,自动安装腾讯QQ2007。延时有点问题或者遇到焦点跑掉就可能产生无法遇到的后果。
哈哈,对于焦点跑掉的情况,基本上都是用一个 AppActivate "标题" 的方法去重新进行激活到最前,但有很多缺陷,比如有重名的可能不准确,有些窗口未知或者会变的。
如果用 Exec 方法启动的话,就能获取到该进程的 PID ,然后可以进行精确的激活,比如
  1. Set ws = WScript.CreateObject("WScript.Shell")
  2. Set ps = ws.Exec("C:\WINDOWS\notepad.exe")
  3. WScript.Sleep 1000
  4. WScript.Echo "已启动记事本,进程ID为 " & ps.ProcessID
  5. ' 暂停 5秒,这段时间你可以试试把其他窗口,比如浏览器切换到最前
  6. WScript.Sleep 5000
  7. ws.AppActivate ps.ProcessID
复制代码
方法 Exec 另一个不同之处是启动之后可以得到它的标准输入输出,但是没法进行可视界面操作。用来得到命令行的输出结果还是很方便的。

来看个简单的例子把,如果你刚刚学了最基本VBS脚本语法,你可能不知道怎样获取计算机当前登录用户名和处理器信息呢,但是想到在命令行里面不是一个变量就搞定了吗
EXP02U.BAT
  1. @echo %username%
复制代码
我们可以调用这个批处理,然后获取它的输出,不就得到信息了吗,于是有
EXP02U.VBS
  1. Dim sUser, ws, bat, batout
  2. ' 创建对象
  3. Set ws = WScript.CreateObject("WScript.Shell")
  4. ' 执行批处理文件
  5. Set bat = ws.Exec("EXP02U.BAT")
  6. ' 获取标准输出
  7. Set batout = bat.StdOut
  8. ' 从输出中读取全部内容
  9. sUser = batout.ReadAll()
  10. ' 显示结果
  11. WScript.Echo "用户名是:" & sUser
复制代码
如果要获取处理器信息,换成这样
  1. @set|find "PROCESSOR"
复制代码
那我们怎么不能一次就获取两个呢?改成这样
EXP02UP.BAT
  1. @echo %username%
  2. @set|find "PROCESSOR"
复制代码
这时输出的就不能一股脑的全部获取了,要分别区分,第一行是用户名,剩下的是处理器信息
EXP02UP.VBS
  1. Set ws = WScript.CreateObject("WScript.Shell")
  2. Set bat = ws.Exec("EXP02UP.BAT")
  3. Set batout = bat.StdOut
  4. sUser = batout.ReadLine()
  5. sTemp = batout.ReadAll()
  6. WScript.Echo "登录人:" & vbCrLf & sUser
  7. WScript.Echo "处理器信息:" & vbCrLf & sTemp
复制代码
等等,刚才运行过那两个批处理文件都是一闪而过啊,完全没有看到是什么东西,我们改成这样吧
EXP02UP.BAT 第二版
  1. @echo off
  2. echo %username%
  3. set|find "PROCESSOR"
  4. pause>nul
复制代码
嘿,行了,这才像是批处理嘛,多亲切的黑框框,按下回车结束它。
然后在运行一下我们的VBS脚本,咦,怎么一直就是个黑框,什么也没有,难道是卡住了?
等了一分钟,算了,估计出不来了,点X关闭吧,嘿,真行了,脚本又有提示了。

这是怎么回事呢?原来 ReadAll 方法想要读取全部内容,但是输出流在 pause 的时候还没有结束,于是要等到我们 “随便按一下”,也就是输入一个任意字符,这时批处理才会 “自杀” 以结束,才能得到输出流全部的内容。
黑的什么也没有,就是因为标准输出流被脚本“接管”了;我们使劲按键盘也不会结束,因为标准输入也被“接管”了,外面怎么按都没反映啦。
但是在“卡住”的时候,它已经将我们需要的信息都输出了,所以,在我们百般无奈把它 “强X” 的时候,所有的一切自然都断开玩完了,脚本也认为程序的输出结束了,就将之前已经有的部分读取到了。

难道我们只能用这么野蛮的办法吗?当然不是啦,刚才不是已经用过 StdOut 么,还有个输入的 StdIn 没用呢,既然你要按(其实人家不是要按啦,不要Sendkey哦,而是要从标准输入流中接收一个字符),那我们就给你吧,在脚本里面加一句,这样就可以 “圆寂” 啦
EXP02UP.VBS 第二版
  1. Set ws = WScript.CreateObject("WScript.Shell")
  2. Set bat = ws.Exec("EXP02UP.BAT")
  3. ' 获取标准输入流
  4. Set batin = bat.StdIn
  5. ' 随便输入一个字符
  6. batin.Write "Spring Brother"
  7. Set batout = bat.StdOut
  8. sUser = batout.ReadLine()
  9. sTemp = batout.ReadAll()
  10. WScript.Echo "登录人:" & vbCrLf & sUser
  11. WScript.Echo "处理器信息:" & vbCrLf & sTemp
复制代码
你不是说输入“一个”字符么,怎么那么多个。。。还有,为什么输入放到那么前面,没关系吗?
试试就知道了,估计多了的别人也不管你吧。
其实你在控制台运行命令的时候有没有发现过,如果一个命令正在执行中的还未结束的时候,你输入另外一个命令并且回车,那么这个命令会在当前过程结束后马上执行的,比如你正在执行
  1. dir /s C:\*.sb
复制代码
这可能会花一些时间,不要紧,你正好可以慢慢的在键盘敲入
  1. echo %time%
复制代码
并且回车,现在还卡着没有反映?如果不想再等查找了,按下 CTRL+C 将其强制结束,你会发现之后的显示时间其实是执行了的,如果你还在怀疑后面这句是在当时按下回车就执行了,还是强制结束查找之后才执行的,看看那个时间就清楚啦。那个 StdIn.Write 干的事就跟你刚才敲键盘的效果一样,所以你可以理解它为什么放哪里都没关系了。

但是这样也许也不靠谱,如果批处理执行过程中有些不可遇到的异常,你输入一堆东西它可能也不会结束,所以,我们就用提供的 Terminate 方法私下决定给当事人一个 “安乐死” 吧
EXP02UP.VBS 第三版
  1. Set ws = WScript.CreateObject("WScript.Shell")
  2. Set bat = ws.Exec("EXP02UP.BAT")
  3. ' 等他一会儿,看起来已经 “没有意识” 了
  4. WScript.Sleep 333
  5. ' 这样痛苦的活着,不如给他解脱吧
  6. bat.Terminate
  7. Set batout = bat.StdOut
  8. sUser = batout.ReadLine()
  9. sTemp = batout.ReadAll()
  10. WScript.Echo "登录人:" & vbCrLf & sUser
  11. WScript.Echo "处理器信息:" & vbCrLf & sTemp
复制代码
写批处理代码起家的,有没有觉得那个BAT文件还不完美啊,一点都不灵活对吧,弄成一个可以灵活选择需要什么信息就输入相应代码的怎么样:
EXP02UPT.BAT
  1. @echo off
  2. :Spring
  3. echo What can I do for you?
  4. echo 1.用户名 2.处理器信息 3.日期时间 0.退出
  5. set "Brother="
  6. set /p Brother=
  7. if [%Brother%]==[0] GOTO :EOF
  8. if [%Brother%]==[1] GOTO :U
  9. if [%Brother%]==[2] GOTO :P
  10. if [%Brother%]==[3] GOTO :T
  11. echo *** ERROR ***
  12. GOTO :Spring
  13. :U
  14. echo %username%
  15. endlocal
  16. GOTO :Spring
  17. :P
  18. set|find "PROCESSOR"
  19. GOTO :Spring
  20. :T
  21. echo %date% %time%
  22. GOTO :Spring
复制代码
试了一下,功能强大啊,可以无限执行下去,所以要实时的时间都可以一直取的到哦。
那我的脚本要依次读取 时间、用户名、处理器信息 该怎么写呢?
EXP02UPT.VBS
  1. Set ws = WScript.CreateObject("WScript.Shell")
  2. Set bat = ws.Exec("EXP02UPT.BAT")
  3. Set batin = bat.StdIn
  4. Set batout = bat.StdOut
  5. batin.Write "3" & vbCr
  6. WScript.Sleep 333
  7. batin.Write "1" & vbCr
  8. WScript.Sleep 333
  9. batin.Write "2" & vbCr
  10. WScript.Sleep 333
  11. batin.Write "0" & vbCr
  12. WScript.Sleep 333
  13. sAll = batout.ReadAll()
  14. WScript.Echo sAll
复制代码
加延时是因为需要输入间隔么,还是需要给他一点处理时间,这我不太清楚了,反正用 vbCrLf 好像有些问题。哎,得到的结果就是一整砣,批处理那些没用的提示信息也混在一起,不好拆出来啊,我都懒得写了,有兴趣的自己试试吧,就当字符串操作练手。
我们还有一个标准错误输出流可以用啊,既然不好剔除无用信息,那我们就分通道吧
在批处理中指定提示信息还是默认的 1 通道,把需要的结果放到 2 通道,也就是一般用于输出错误信息的(你管我这是不是错误信息呢,我的地盘我做主)
EXP02UPT.BAT 第二版
  1. @echo off
  2. :Spring
  3. echo What can I do for you?
  4. echo 1.用户名 2.处理器信息 3.日期时间 0.退出
  5. set "Brother="
  6. set /p Brother=
  7. if [%Brother%]==[0] GOTO :EOF
  8. if [%Brother%]==[1] GOTO :U
  9. if [%Brother%]==[2] GOTO :P
  10. if [%Brother%]==[3] GOTO :T
  11. echo *** ERROR ***
  12. GOTO :Spring
  13. :U
  14. >&2 (echo %username%)
  15. endlocal
  16. GOTO :Spring
  17. :P
  18. >&2 (set|find "PROCESSOR")
  19. GOTO :Spring
  20. :T
  21. >&2 (echo %date% %time%)
  22. GOTO :Spring
复制代码
然后VBS运行它的时候,我们就不管 StdOut 里面的东西了,从 StdErr 里面获取纯的想要的信息
EXP02UPT.VBS 第二版
  1. Set ws = WScript.CreateObject("WScript.Shell")
  2. Set bat = ws.Exec("EXP02UPT.BAT")
  3. Set batin = bat.StdIn
  4. Set baterr = bat.StdErr
  5. batin.Write "3" & vbCr
  6. WScript.Sleep 333
  7. batin.Write "1" & vbCr
  8. WScript.Sleep 333
  9. batin.Write "2" & vbCr
  10. WScript.Sleep 333
  11. bat.Terminate
  12. sTime = baterr.ReadLine()
  13. sUser = baterr.ReadLine()
  14. sProccesor = baterr.ReadAll()
  15. WScript.Echo "时间:" & sTime
  16. WScript.Echo "用户:" & sUser
  17. WScript.Echo "处理器:" & vbCrLf & sProccesor
复制代码
忽然灵机一动,如果用 Exec 运行 FTP 是不是就可以实现模拟人敲命令自动上传下载了呢?
呵呵,直到这里都讨论的是“标准”输入输出,对于“不标准”的,可能就爱莫能助了,谁知道哪些标准哪些不标准呢,你可以自己去试试,也可以搜搜相关的讨论,我就不废话太多了,毕竟实践才是检验真理的唯一标准啊。

能不能摒弃 WScript.Sleep 延时语句呢,每次看到有这个东西第一感觉就是不专业!
如果想要实现根据第一次获取时间,如果是奇数秒就获取用户名,如果是偶数就获取处理器信息,又怎么弄呢?
输出流也有 Read() 方法,用 Read 或者 ReadLine 逐字读取分析就好了,感觉这些都没什么实用性,就不写在这里了,有兴趣的可以参看附件中内容。

回头一看,这一大串实例都是由想要在 VBS 中获取 %username% 但又不会写引起的,对于环境变量可以这样获取
  1. Set WshShell = WScript.CreateObject("WScript.Shell")
  2. Set WshEnv = WshShell.Environment("PROCESS")
  3. sUser = WshEnv("username")
  4. MsgBox sUser
复制代码
环境变量类型也有几种,其中 SYSTEM 和 USER 类型的都保存在计算机上,在注册表 HKEY_CURRENT_USER\Environment\ 路径可以看到用户环境变量,而在 PROCESS 中设置的只在运行时有效,有兴趣的可以去查查相关文档。

不管是用批处理来调用VBS脚本,还是在VBS中启动外部程序,都可以看成是一个程序甲调用另一个程序乙,对于在甲中设置的环境变量,是可以被乙读取到的,在乙启动之后甲修改值不会对乙有影响,乙中的更改也不会对甲起作用。
最后还是来做一个轻松的小玩意结束吧。
cgcym.bat
  1. @echo off
  2. set cgcym=春 哥 纯 爷 们
  3. for %%a in (%cgcym%) do call echo %%a%%%%a%%
  4. pause>nul
复制代码
看看这个批处理运行起来是什么样的。然后我们在写个 VBS 来启动它。
txzhz.vbs
  1. Set ws = WScript.CreateObject("WScript.Shell")
  2. Set envp = ws.Environment("PROCESS")
  3. envp("春") = Chr(8) & "铁"
  4. envp("哥") = Chr(8) & "血"
  5. envp("纯") = Chr(8) & "真"
  6. envp("爷") = Chr(8) & "汉"
  7. envp("们") = Chr(8) & "子"
  8. ws.Run "cgcym.bat"
复制代码
怎么样,是不是很好玩。


参考资料:
Run Method (Windows Script Host)
http://msdn.microsoft.com/en-us/library/d5fk67ky

AppActivate Function
http://msdn.microsoft.com/en-us/library/dyz95fhy

Exec Method (Windows Script Host)
http://msdn.microsoft.com/en-us/library/ateytk4a

WshScriptExec Object
http://msdn.microsoft.com/en-us/library/2f38xsxe

Environment Property
http://msdn.microsoft.com/en-us/library/fd7hxfdd

TOP

怎么样,是不是很好玩。

TOP

非常好,学习了

TOP

返回列表