标题: [原创] 脚本学习及应用分享 — 批处理和脚本的交互 [打印本页]
作者: Spring 时间: 2010-6-27 05:55 标题: 脚本学习及应用分享 — 批处理和脚本的交互
脚本学习及应用分享
批处理和Windows脚本的交互
(一)在批处理中调用Windows脚本
---------------
脚本可以使用 cscript.exe 在控制台中运行,而我们知道命令控制台中的输出,包括标准输出和标准错误输出,可以使用管道 1 或者 2 来重定向以加以利用,所以在批处理中调用Windows脚本并处理它的结果是比较容易的。
示例:
一般情况下调用VBS都带参数的,因此我们在这个小脚本中加入参数的获取。
它的功能是我们给定一个人名,显示 XX你好,如果参数不对则提示错误。
(关于脚本参数的获取可以查阅 WScript.Arguments 相关的资料)
SayHello.vbs- ' 进入程序是显示欢迎信息
- WScript.Echo "欢迎使用本程序。"
- ' 得到参数对象
- Set args = WScript.Arguments
- ' 如果参数个数是一个,正常运行,否则提示错误信息并退出。
- If args.Count = 1 Then
- ' 得到第一个参数
- name = WScript.Arguments(0)
- ' 在标准输出中,即控制台的 通道1 输出一行文字
- WScript.StdOut.WriteLine name & ",你好。"
- Else
- ' 在标准错误中,即控制台的 通道2 输出一行文字
- WScript.StdErr.WriteLine "参数个数不为1,请重试。"
- ' 退出程序
- WScript.Quit
- End If
复制代码
cscript.exe默认会在标准输出的开始加上微软版权信息,我们一般加上 //NoLogo 参数来屏蔽掉。
我们在CMD中来试试运行这个脚本,分别执行下面四条命令,看看结果如何:- cscript //nologo SayHello.vbs "%username%"
复制代码
- cscript //nologo SayHello.vbs "%username%" 1>nul
复制代码
- cscript //nologo SayHello.vbs "%username%" "第二个参数"
复制代码
- 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- @echo off
- echo *** 开始: %date% %time% ***
- set /p name=请输入你的名字:
- echo %name%,你好!
- set /p=按回车结束程序...
- echo *** 结束: %date% %time% ***
复制代码
外观很相近的VBS脚本
SayHello2.vbs- WScript.Echo "*** 开始:" & Now & " ***"
- WScript.StdOut.Write "请输入你的名字:"
- name = WScript.StdIn.ReadLine()
- WScript.StdOut.WriteLine name & ",你好!"
- WScript.StdOut.Write "按回车结束程序..."
- WScript.StdIn.ReadLine
- WScript.Echo "*** 结束:" & Now & " ***"
复制代码
③ 在控制台中运行脚本可以用 var = WScript.StdIn.ReadLine() 读取输入的一行信息,也可以用来实现暂停程序的作用。
到此我们知道了用VBS如何在控制台中读取出入和现实输出或错误信息,那批处理中如何获取这些信息并加以使用呢?
这不是新鲜的东西了,cscript.exe 就是一个命令行嘛,现在问题就变成了“批处理中如何获取命令的输出信息到变量?”
我用过的有两种方法,
一种是把输出信息重定向到临时文件,然后再读取这个文件到变量,比如接着用上面的脚本:- @echo off
- cscript //nologo SayHello.vbs "%username%" 1>temp.txt 2>nul
- echo VBS运行结果是
- echo.
- echo ------- 全 部 --------
- type temp.txt
- echo -----------------------
- echo.
- echo ------- 第一行 --------
- set /p line1=<temp.txt
- echo %line1%
- echo -----------------------
- echo.
- echo ------- 连 接 --------
- setlocal enabledelayedexpansion
- set lines=
- for /f "delims=" %%a in (temp.txt) do (set lines=!lines!%%a )
- echo !lines!
- endlocal
- echo -----------------------
- del temp.txt
- pause>nul
复制代码
另一种就是用 for %%a in ('command') do 这种格式来获取,比如我们就要获取所有行连起来的内容
- @echo off
- setlocal enabledelayedexpansion
- set lines=
- for /f "delims=" %%a in ('cscript //nologo SayHello.vbs "%username%" 2^>nul') do (
- set lines=!lines!%%a
- )
- echo 结果是
- echo !lines!
- pause>nul
复制代码
注意命令里面有特殊符号是要用转义符进行标志,如 2>nul 改为 2^>nul ,不然要出错。
一般我们编写的脚本都是只输出一行信息,或者我们只在乎的都是最后一行信息,于是可以采取这种方式:
- @echo off
- for /f "delims=" %%a in ('cscript //nologo SayHello.vbs "%username%" 2^>nul') do (set "lines=%%a")
- echo 结果是
- echo %lines%
- pause>nul
复制代码
写了个函数专门获取最后一行的输出,不知道实用性如何:- @echo off
- call :GetScriptExecuteResult 0 result SayHello.vbs "%username%"
- echo 脚本执行的输出是:%result%
- pause>nul
- goto :EOF
-
- :: 执行脚本并将最后一行输出信息存入变量
- :: 参数依次为
- :: 第一个:0=所有输出,1=仅获取通道1, 2=仅获取通道2
- :: 第二个:要存入的变量名
- :: 第三个:要执行的脚本
- :: 后面若干个:脚本需要传入的参数
- :GetScriptExecuteResult
- IF [%1]==[] GOTO :GetScriptExecuteResult_End
- SET "___PT="
- IF [%1]==[0] SET "___PT= "
- IF [%1]==[1] SET "___PT= 2^>nul "
- IF [%1]==[2] SET "___PT= 1^>nul "
- IF "%___PT%"=="" GOTO :GetScriptExecuteResult_End
- IF [%2]==[] (GOTO :GetScriptExecuteResult_End) ELSE (SET "__VAR=%2")
- IF [%3]==[] (GOTO :GetScriptExecuteResult_End) ELSE (SET "___FN=%3")
- SET "__CMD=CSCRIPT.EXE //NOLOGO "%___FN%""
- SHIFT
- SHIFT
- :GetScriptExecuteResult_Block1
- SHIFT
- IF NOT [%1]==[] (
- SET "__CMD=%__CMD% %1"
- GOTO :GetScriptExecuteResult_Block1
- )
- FOR /F "DELIMS=" %%I IN ('%__CMD%%___PT%') DO (SET "%__VAR%=%%I")
- :GetScriptExecuteResult_End
复制代码
=======================================
最后再看一个有意思的模拟等待的例子,回车可以把光标移动到行首,所以可以擦写屏幕的一行。
本例的退出条件是时间到了,使用时根据自己的需求更改条件。
VBS脚本
Loading.vbs- Set args = WScript.Arguments
- If args.Count < 3 Then WScript.Quit
- o = Timer
- t = args(0)
- s = args(1)
- w = args(2)
- l = Len(w) + 1
- w = w & String(Len(w), " ")
- i = 0
- While CInt(Timer) < o + t
- i = i + 1
- WScript.Sleep 333
- temp = s & Left(w, i Mod l) & Right(w, l - (i Mod l)) & Chr(13)
- WScript.StdOut.Write temp
- WEnd
- WScript.StdOut.WriteLine vbCrLf & "完成"
复制代码
在CMD中输入命令- cscript -nologo Loading.vbs 10 "正在下载,请稍候" "......"
复制代码
在 10 秒钟之内显示等待,那六个点是循环一直在变的,到时间后退出脚本。
作者: famersoft 时间: 2012-5-7 09:11
学习了,新手中
作者: junmt 时间: 2012-12-2 09:04
支持~~顶顶~~~安心大巴
作者: Spring 时间: 2014-5-24 00:37
(二)在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 ,然后可以进行精确的激活,比如- Set ws = WScript.CreateObject("WScript.Shell")
- Set ps = ws.Exec("C:\WINDOWS\notepad.exe")
- WScript.Sleep 1000
- WScript.Echo "已启动记事本,进程ID为 " & ps.ProcessID
- ' 暂停 5秒,这段时间你可以试试把其他窗口,比如浏览器切换到最前
- WScript.Sleep 5000
- ws.AppActivate ps.ProcessID
复制代码
方法 Exec 另一个不同之处是启动之后可以得到它的标准输入输出,但是没法进行可视界面操作。用来得到命令行的输出结果还是很方便的。
来看个简单的例子把,如果你刚刚学了最基本VBS脚本语法,你可能不知道怎样获取计算机当前登录用户名和处理器信息呢,但是想到在命令行里面不是一个变量就搞定了吗
EXP02U.BAT复制代码
我们可以调用这个批处理,然后获取它的输出,不就得到信息了吗,于是有
EXP02U.VBS- Dim sUser, ws, bat, batout
- ' 创建对象
- Set ws = WScript.CreateObject("WScript.Shell")
- ' 执行批处理文件
- Set bat = ws.Exec("EXP02U.BAT")
- ' 获取标准输出
- Set batout = bat.StdOut
- ' 从输出中读取全部内容
- sUser = batout.ReadAll()
- ' 显示结果
- WScript.Echo "用户名是:" & sUser
复制代码
如果要获取处理器信息,换成这样复制代码
那我们怎么不能一次就获取两个呢?改成这样
EXP02UP.BAT- @echo %username%
- @set|find "PROCESSOR"
复制代码
这时输出的就不能一股脑的全部获取了,要分别区分,第一行是用户名,剩下的是处理器信息
EXP02UP.VBS- Set ws = WScript.CreateObject("WScript.Shell")
- Set bat = ws.Exec("EXP02UP.BAT")
- Set batout = bat.StdOut
- sUser = batout.ReadLine()
- sTemp = batout.ReadAll()
- WScript.Echo "登录人:" & vbCrLf & sUser
- WScript.Echo "处理器信息:" & vbCrLf & sTemp
复制代码
等等,刚才运行过那两个批处理文件都是一闪而过啊,完全没有看到是什么东西,我们改成这样吧
EXP02UP.BAT 第二版- @echo off
- echo %username%
- set|find "PROCESSOR"
- pause>nul
复制代码
嘿,行了,这才像是批处理嘛,多亲切的黑框框,按下回车结束它。
然后在运行一下我们的VBS脚本,咦,怎么一直就是个黑框,什么也没有,难道是卡住了?
等了一分钟,算了,估计出不来了,点X关闭吧,嘿,真行了,脚本又有提示了。
这是怎么回事呢?原来 ReadAll 方法想要读取全部内容,但是输出流在 pause 的时候还没有结束,于是要等到我们 “随便按一下”,也就是输入一个任意字符,这时批处理才会 “自杀” 以结束,才能得到输出流全部的内容。
黑的什么也没有,就是因为标准输出流被脚本“接管”了;我们使劲按键盘也不会结束,因为标准输入也被“接管”了,外面怎么按都没反映啦。
但是在“卡住”的时候,它已经将我们需要的信息都输出了,所以,在我们百般无奈把它 “强X” 的时候,所有的一切自然都断开玩完了,脚本也认为程序的输出结束了,就将之前已经有的部分读取到了。
难道我们只能用这么野蛮的办法吗?当然不是啦,刚才不是已经用过 StdOut 么,还有个输入的 StdIn 没用呢,既然你要按(其实人家不是要按啦,不要Sendkey哦,而是要从标准输入流中接收一个字符),那我们就给你吧,在脚本里面加一句,这样就可以 “圆寂” 啦
EXP02UP.VBS 第二版- Set ws = WScript.CreateObject("WScript.Shell")
- Set bat = ws.Exec("EXP02UP.BAT")
- ' 获取标准输入流
- Set batin = bat.StdIn
- ' 随便输入一个字符
- batin.Write "Spring Brother"
- Set batout = bat.StdOut
- sUser = batout.ReadLine()
- sTemp = batout.ReadAll()
- WScript.Echo "登录人:" & vbCrLf & sUser
- WScript.Echo "处理器信息:" & vbCrLf & sTemp
复制代码
你不是说输入“一个”字符么,怎么那么多个。。。还有,为什么输入放到那么前面,没关系吗?
试试就知道了,估计多了的别人也不管你吧。
其实你在控制台运行命令的时候有没有发现过,如果一个命令正在执行中的还未结束的时候,你输入另外一个命令并且回车,那么这个命令会在当前过程结束后马上执行的,比如你正在执行复制代码
这可能会花一些时间,不要紧,你正好可以慢慢的在键盘敲入复制代码
并且回车,现在还卡着没有反映?如果不想再等查找了,按下 CTRL+C 将其强制结束,你会发现之后的显示时间其实是执行了的,如果你还在怀疑后面这句是在当时按下回车就执行了,还是强制结束查找之后才执行的,看看那个时间就清楚啦。那个 StdIn.Write 干的事就跟你刚才敲键盘的效果一样,所以你可以理解它为什么放哪里都没关系了。
但是这样也许也不靠谱,如果批处理执行过程中有些不可遇到的异常,你输入一堆东西它可能也不会结束,所以,我们就用提供的 Terminate 方法私下决定给当事人一个 “安乐死” 吧
EXP02UP.VBS 第三版- Set ws = WScript.CreateObject("WScript.Shell")
- Set bat = ws.Exec("EXP02UP.BAT")
- ' 等他一会儿,看起来已经 “没有意识” 了
- WScript.Sleep 333
- ' 这样痛苦的活着,不如给他解脱吧
- bat.Terminate
- Set batout = bat.StdOut
- sUser = batout.ReadLine()
- sTemp = batout.ReadAll()
- WScript.Echo "登录人:" & vbCrLf & sUser
- WScript.Echo "处理器信息:" & vbCrLf & sTemp
复制代码
写批处理代码起家的,有没有觉得那个BAT文件还不完美啊,一点都不灵活对吧,弄成一个可以灵活选择需要什么信息就输入相应代码的怎么样:
EXP02UPT.BAT- @echo off
- :Spring
- echo What can I do for you?
- echo 1.用户名 2.处理器信息 3.日期时间 0.退出
- set "Brother="
- set /p Brother=
- if [%Brother%]==[0] GOTO :EOF
- if [%Brother%]==[1] GOTO :U
- if [%Brother%]==[2] GOTO :P
- if [%Brother%]==[3] GOTO :T
- echo *** ERROR ***
- GOTO :Spring
-
- :U
- echo %username%
- endlocal
- GOTO :Spring
-
- :P
- set|find "PROCESSOR"
- GOTO :Spring
-
- :T
- echo %date% %time%
- GOTO :Spring
复制代码
试了一下,功能强大啊,可以无限执行下去,所以要实时的时间都可以一直取的到哦。
那我的脚本要依次读取 时间、用户名、处理器信息 该怎么写呢?
EXP02UPT.VBS- Set ws = WScript.CreateObject("WScript.Shell")
- Set bat = ws.Exec("EXP02UPT.BAT")
- Set batin = bat.StdIn
- Set batout = bat.StdOut
- batin.Write "3" & vbCr
- WScript.Sleep 333
- batin.Write "1" & vbCr
- WScript.Sleep 333
- batin.Write "2" & vbCr
- WScript.Sleep 333
- batin.Write "0" & vbCr
- WScript.Sleep 333
- sAll = batout.ReadAll()
- WScript.Echo sAll
复制代码
加延时是因为需要输入间隔么,还是需要给他一点处理时间,这我不太清楚了,反正用 vbCrLf 好像有些问题。哎,得到的结果就是一整砣,批处理那些没用的提示信息也混在一起,不好拆出来啊,我都懒得写了,有兴趣的自己试试吧,就当字符串操作练手。
我们还有一个标准错误输出流可以用啊,既然不好剔除无用信息,那我们就分通道吧
在批处理中指定提示信息还是默认的 1 通道,把需要的结果放到 2 通道,也就是一般用于输出错误信息的(你管我这是不是错误信息呢,我的地盘我做主)
EXP02UPT.BAT 第二版- @echo off
- :Spring
- echo What can I do for you?
- echo 1.用户名 2.处理器信息 3.日期时间 0.退出
- set "Brother="
- set /p Brother=
- if [%Brother%]==[0] GOTO :EOF
- if [%Brother%]==[1] GOTO :U
- if [%Brother%]==[2] GOTO :P
- if [%Brother%]==[3] GOTO :T
- echo *** ERROR ***
- GOTO :Spring
-
- :U
- >&2 (echo %username%)
- endlocal
- GOTO :Spring
-
- :P
- >&2 (set|find "PROCESSOR")
- GOTO :Spring
-
- :T
- >&2 (echo %date% %time%)
- GOTO :Spring
复制代码
然后VBS运行它的时候,我们就不管 StdOut 里面的东西了,从 StdErr 里面获取纯的想要的信息
EXP02UPT.VBS 第二版- Set ws = WScript.CreateObject("WScript.Shell")
- Set bat = ws.Exec("EXP02UPT.BAT")
- Set batin = bat.StdIn
- Set baterr = bat.StdErr
- batin.Write "3" & vbCr
- WScript.Sleep 333
- batin.Write "1" & vbCr
- WScript.Sleep 333
- batin.Write "2" & vbCr
- WScript.Sleep 333
- bat.Terminate
- sTime = baterr.ReadLine()
- sUser = baterr.ReadLine()
- sProccesor = baterr.ReadAll()
- WScript.Echo "时间:" & sTime
- WScript.Echo "用户:" & sUser
- WScript.Echo "处理器:" & vbCrLf & sProccesor
复制代码
忽然灵机一动,如果用 Exec 运行 FTP 是不是就可以实现模拟人敲命令自动上传下载了呢?
呵呵,直到这里都讨论的是“标准”输入输出,对于“不标准”的,可能就爱莫能助了,谁知道哪些标准哪些不标准呢,你可以自己去试试,也可以搜搜相关的讨论,我就不废话太多了,毕竟实践才是检验真理的唯一标准啊。
能不能摒弃 WScript.Sleep 延时语句呢,每次看到有这个东西第一感觉就是不专业!
如果想要实现根据第一次获取时间,如果是奇数秒就获取用户名,如果是偶数就获取处理器信息,又怎么弄呢?
输出流也有 Read() 方法,用 Read 或者 ReadLine 逐字读取分析就好了,感觉这些都没什么实用性,就不写在这里了,有兴趣的可以参看附件中内容。
回头一看,这一大串实例都是由想要在 VBS 中获取 %username% 但又不会写引起的,对于环境变量可以这样获取- Set WshShell = WScript.CreateObject("WScript.Shell")
- Set WshEnv = WshShell.Environment("PROCESS")
- sUser = WshEnv("username")
- MsgBox sUser
复制代码
环境变量类型也有几种,其中 SYSTEM 和 USER 类型的都保存在计算机上,在注册表 HKEY_CURRENT_USER\Environment\ 路径可以看到用户环境变量,而在 PROCESS 中设置的只在运行时有效,有兴趣的可以去查查相关文档。
不管是用批处理来调用VBS脚本,还是在VBS中启动外部程序,都可以看成是一个程序甲调用另一个程序乙,对于在甲中设置的环境变量,是可以被乙读取到的,在乙启动之后甲修改值不会对乙有影响,乙中的更改也不会对甲起作用。
最后还是来做一个轻松的小玩意结束吧。
cgcym.bat- @echo off
- set cgcym=春 哥 纯 爷 们
- for %%a in (%cgcym%) do call echo %%a%%%%a%%
- pause>nul
复制代码
看看这个批处理运行起来是什么样的。然后我们在写个 VBS 来启动它。
txzhz.vbs- Set ws = WScript.CreateObject("WScript.Shell")
- Set envp = ws.Environment("PROCESS")
- envp("春") = Chr(8) & "铁"
- envp("哥") = Chr(8) & "血"
- envp("纯") = Chr(8) & "真"
- envp("爷") = Chr(8) & "汉"
- envp("们") = Chr(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
作者: linjuming 时间: 2015-1-29 22:20
怎么样,是不是很好玩。
作者: zrc20d 时间: 2015-12-6 09:56
非常好,学习了
欢迎光临 批处理之家 (http://www.bathome.net/) |
Powered by Discuz! 7.2 |