背景
前段时间,遇到一个需求,需要解压文件,并且执行里面的 bat 文件。还需要获取执行进度,并且在错误的时候,中断执行。在这期间,在网上查找了许多的实例,不断地尝试,兜兜转转的绕了一大圈,记录一下走过的一些坑。
直接调用bat文件
我最开始想到的这个方法,最简单,不需要考虑bat的变量,脚本命令等如@ECHO OFF
,相当于双击执行了这个脚本文件。但是存在一个问题就是,无法展示执行进度,所以放弃了。
using (Process myPro = new Process())
{
myPro.StartInfo.FileName = Path.Combine(dirPath, batFilePath);
myPro.StartInfo.UseShellExecute = false;
myPro.StartInfo.CreateNoWindow = true;
myPro.Start();
myPro.WaitForExit();
}
用 cmd.exe 逐行执行命令
相当于打开了一个cmd.exe的窗口,然后在这个窗体里,一行一行的输入进去命令执行。如下图:
这样有的好处就是,bat文件不用大修改:
-
里面的临时变量,不用重新替换赋值;如上图的哪个
Bslot_images_path
变量,在脚本文件中大量使用; -
不用删除 windows 命令,如
ping -n 6 127.0.0.1
这个命令;
缺点有三点:
-
批处理的特殊变量无法识别。如
%~dp0
只可以用在批处理文件中,它是由它所在的批处理文件的目录位置决定的,是批处理文件所在的盘符:+路径。 - 超时设置的问题,如果设置超时,只是针对整个批处理文件,但是每个批处理文件大小不一,并且命令预期执行时长也不尽相同。我想到的最好的办法就是,针对每条不同的命令,设置不同的超时时长。如果不设置超时,则会出现假死想象,下图。
-
执行结果的获取,无法做到实时的获取每条命令的返回结果,并做出判断。只能在退出后
&exit
,才能获取。否则执行p.StandardOutput.ReadToEnd();
会出现假死状况。waiting for any device等情况,只能ctrl+c强制退出。
下面是网上找的一个简单的演示版本,关键就是循环输入处,无法实时的获得执行的结果;另外就是超时时间问题。
static void Main(string[] args)
{
Console.WriteLine("请输入要执行的命令:");
string strInput = Console.ReadLine();
Process p = new Process();
p.StartInfo.FileName = "cmd.exe"; //设置要启动的应用程序
p.StartInfo.UseShellExecute = false; //是否使用操作系统shell启动
p.StartInfo.RedirectStandardInput = true; // 接受来自调用程序的输入信息
p.StartInfo.RedirectStandardOutput = true; //输出信息
p.StartInfo.RedirectStandardError = true; // 输出错误
p.StartInfo.CreateNoWindow = true; //不显示程序窗口
p.Start(); //启动程序
p.StandardInput.WriteLine(strInput+"&exit"); //向cmd窗口发送输入信息,如果批处理,需要这里做循环输入
p.StandardInput.AutoFlush=true;
string strOuput = p.StandardOutput.ReadToEnd(); //获取输出信息
p.WaitForExit(60 * 1000); //等待程序执行完退出进程,cmd.exe超时时间
p.Close();
Console.WriteLine(strOuput);
Console.ReadKey();
}
解析命令,执行命令
这个咋一看起来和用 cmd.exe 逐行执行命令
很像,这个不同点就是 把每个命令的exe文件单独拿出来执行,而不是使用cmd.exe
来执行。并且可以给每一条命令,设置一个单独的超时时间。
需要注意的点,重新编辑 bat 批处理文件:
-
删去
批处理独有的命令
,原因为无法变成exe执行(如:"@SET BASEPATH=%~dp0","@ECHO OFF"等); -
删去
windows系统命令
,原因为工作目录
非系统PATH,无法找到系统exe(如:"ping -n 6 127.0.0.1","cd A_Debug"等); -
替换临时变量为
具体值
,目的是为了,变成可以单独一条拿出来执行的命令(如:"fastboot flash boot0 "A_Debug/boot0.img" "); -
删除
空白行
以及等待用户操作
的命令(如:"pause > nul")
命令需要拆分:exe执行程序
,参数
,超时时长
三部分;如:“fastboot flash boot0 "A_Debug/boot0.img" ” 拆分为:
- exe执行程序“fastboot”;
- 参数“flash boot0 "A_Debug/boot0.img"”;
- 超时时长“60000”。
然后把拆分后的参数,传入执行,具体执行命令的代码如下。
private List<string> Shell(string exeFile, string command, int timeout, string workingDir, out int exitCode)
{
List<string> response = new List<string>();
List<string> output = new List<string>();
List<string> error = new List<string>();
Process process = new Process();
process.StartInfo.FileName = exeFile; //设置要启动的应用程序,如:fastboot
process.StartInfo.Arguments = command; // 设置应用程序参数,如: flash boot0 "A_Debug/boot0.img"
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;
process.EnableRaisingEvents = true; // 获取或设置在进程终止时是否应激发 Exited 事件;不论是正常退出还是异常退出。
process.StartInfo.WorkingDirectory = workingDir; // **重点**,工作目录,必须是 bat 批处理文件所在的目录
process.OutputDataReceived += (object sender, DataReceivedEventArgs e) => Redirected(output, sender, e);
process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => Redirected(error, sender, e);
process.Start();
process.BeginOutputReadLine(); // 开启异步读取输出操作
process.BeginErrorReadLine(); // 开启异步读取错误操作
bool exited = process.WaitForExit(timeout);
if (!exited)
{
process.Kill(); // 通过超时判断是否执行失败,极可能为假死状态。
// 记录日志
response.Add("Error: timed out");
}
response.AddRange(output);
response.AddRange(error);
exitCode = process.ExitCode; // 0 为正常退出。
return response;
}
private void Redirected(List<string> dataList, object sender, DataReceivedEventArgs e)
{
if (e.Data != null){ dataList.Add(e.Data); }
}