背景
前段時間,遇到一個需求,需要解壓文件,並且執行里面的 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); }
}