3

*C#执行批处理命令

 2 years ago
source link: https://mrbenwang.github.io/post/2019/20190520-csharp-exectue-bat/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前段时间,遇到一个需求,需要解压文件,并且执行里面的 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的窗口,然后在这个窗体里,一行一行的输入进去命令执行。如下图:
cmd执行命令

这样有的好处就是,bat文件不用大修改:

  • 里面的临时变量,不用重新替换赋值;如上图的哪个Bslot_images_path变量,在脚本文件中大量使用;
  • 不用删除 windows 命令,如ping -n 6 127.0.0.1这个命令;

缺点有三点:

  • 批处理的特殊变量无法识别。如%~dp0只可以用在批处理文件中,它是由它所在的批处理文件的目录位置决定的,是批处理文件所在的盘符:+路径。
  • 超时设置的问题,如果设置超时,只是针对整个批处理文件,但是每个批处理文件大小不一,并且命令预期执行时长也不尽相同。我想到的最好的办法就是,针对每条不同的命令,设置不同的超时时长。如果不设置超时,则会出现假死想象,下图。
  • 执行结果的获取,无法做到实时的获取每条命令的返回结果,并做出判断。只能在退出后&exit,才能获取。否则执行p.StandardOutput.ReadToEnd();会出现假死状况。waiting for any device等情况,只能ctrl+c强制退出。

cmd假死情况

下面是网上找的一个简单的演示版本,关键就是循环输入处,无法实时的获得执行的结果;另外就是超时时间问题。

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); }
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK