从 C# Windows 窗体内部调用的 Bash 脚本

Posted

技术标签:

【中文标题】从 C# Windows 窗体内部调用的 Bash 脚本【英文标题】:Bash Script called from inside C# Windows Forms 【发布时间】:2022-01-17 14:14:05 【问题描述】:

我正在尝试从 Windows 窗体应用程序中运行 bash 脚本。我的代码如下所示:

            // Command String
            string flashNVS =    "c:\\msys32\\usr\\bin\\env MSYSTEM=MINGW32 " +
                                 "c:\\msys32\\usr\\bin\\bash -l -c " +
                                 "\"cd " + unixProdPath + 
                                 " && ./provision-device-secure " + deviceId + " \"";

            cmdProcess.ExecuteCommandSync(flashNVS);

使用 cmdProcess 代码:

public static void ExecuteCommandSync(object command)
        
            flashOK = 0;
            try
            
                // create the ProcessStartInfo using "cmd" as the program to be run, and "/c " as the parameters.
                // Incidentally, /c tells cmd that we want it to execute the command that follows, and then exit.
                System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo("cmd", "/c " + command);
                // The following commands are needed to redirect the standard output. 
                //This means that it will be redirected to the Process.StandardOutput StreamReader.
                procStartInfo.RedirectStandardOutput = true;
                procStartInfo.RedirectStandardError = true;
                procStartInfo.UseShellExecute = false;
                // Do not create the black window.
                procStartInfo.CreateNoWindow = true;
                // Now we create a process, assign its ProcessStartInfo and start it
                System.Diagnostics.Process proc = new System.Diagnostics.Process();
                proc.StartInfo = procStartInfo;
                proc.Start();
                
                // Get the output into a string
                string result = proc.StandardOutput.ReadToEnd();
                //resultString = result;
                if ( (result.IndexOf("Hard resetting via RTS pin") > 0) ||  // Success FW Flash OR NVS Flash!
                     (result.IndexOf("Hash of data verified") > 0) )   // Success NVS Flash
                
                    Console.WriteLine("SUCCESS : FlashOK = 1");
                    flashOK = 1;
                

                // Display the command output.
                Console.WriteLine(result);
            
            catch (Exception objException)
            
                // Log the exception
                Console.WriteLine("ExecuteCommandSync failed" + objException.Message);
            
        

文件 ./provision-device-secure 脚本做了很多事情。除了一行之外,它工作正常:

$IDF_PATH/components/esptool_py/esptool/esptool.py -p $ESPPORT --before default_reset --after no_reset write_flash 0x3E0000 mfg_config_encrypted.bin 0x3E8000 nvs_keys.bin

完全令人困惑的是,如果我直接从 git bash 内部运行脚本,它就可以正常工作。但是从 Windows 窗体应用程序内部,该行没有正确完成......我需要它作为我正在编写的 Windows 应用程序的一部分运行。关于如何调整我的代码以使其运行的任何建议?以及为什么不这样做的解释?

只是为了描述失败。在 git bash 中,它使用以下输出执行:

Created encryption keys: ===>  C:\Users\thebi\esp\esp32_repo\11_SN_WI_003_WiFi\prod\keys\nvs_keys.bin

Creating NVS binary with version: V2 - Multipage Blob Support Enabled

Created NVS binary: ===> C:\Users\thebi\esp\esp32_repo\11_SN_WI_003_WiFi\prod\mfg_config_encrypted.bin

但在 windows 窗体中它只这样做:

Created encryption keys: ===>  C:/Users/thebi/esp/esp32_repo/11_SN_WI_003_WiFi/prod/keys/nvs_keys.bin

Creating NVS binary with version: V2 - Multipage Blob Support Enabled

而且输出文件(mfg_config_encrypted.bin)的长度为零……

【问题讨论】:

令我惊讶的是,一方面,您构建了一个要执行的复杂命令,另一方面,您从未打印出此命令的实际内容(用于日志记录/调试)。你如何确定flashNVS 包含正确的命令? @user1934428 添加了 Console.WriteLine(flashNVS); -> c:\msys32\usr\bin\env MSYSTEM=MINGW32 c:\msys32\usr\bin\bash -l -c "cd C:/Users/thebi/esp/esp32_repo/11_SN_WI_003_WiFi/prod/ && ./provision-device-secure test-dev " 我从未使用过 msys32,但我猜如果你通过 env 调用 bash,你将不得不使用正斜杠来指定 bash路径,因为env 可能无法处理反斜杠。 【参考方案1】:

首先你应该引用你的参数,因为它们里面有空格。其次...我只会给你我写的课程:

public delegate void ProcessMessageDelegate(object sender, string msg);

public class ProcessManager

    public event ProcessMessageDelegate OnOutputData;
    public event ProcessMessageDelegate OnErrorData;
    public event EventHandler OnProcessEnd;

    Process process;
    List<string> outputBuffer = new List<string>();

    public string GetProcessOutput(string cmdName, List<string> args)
    
        InitProcess(cmdName, args);
        string result = "";

        if (process.Start())
        
            try
            
                process.BeginOutputReadLine();
                process.StandardError.ReadToEnd();
                process.WaitForExit();
                return OutputBufferToStr();
            
            catch (Exception ex)
            
                if (string.IsNullOrWhiteSpace(result))
                
                    OnErrorData?.Invoke(this, ex.Message);
                    return null;
                
                else
                    return result;
            

        

        return result;
    

    public void RunProcessAsync(string cmdName, List<string> args)
    
        InitProcess(cmdName, args);

        if (process.Start())
        
            process.BeginOutputReadLine();
            process.StandardError.ReadToEnd();
        

    

    void InitProcess(string cmdName, List<string> args)
    
        ProcessStartInfo psi = new ProcessStartInfo();

        psi.FileName = cmdName;
        List<string> escapedArgs = new List<string>();
        foreach (var arg in args)
        
            string validArg = arg;
            if (validArg.Contains(" "))
                validArg = $"\"validArg\"";

            escapedArgs.Add(validArg);

        

        psi.Arguments = string.Join(" ", escapedArgs);

        psi.ErrorDialog = true;
        psi.UseShellExecute = false;
        psi.RedirectStandardError = true;
        psi.RedirectStandardOutput = true;
        psi.WindowStyle = ProcessWindowStyle.Hidden;
        psi.CreateNoWindow = true;

        process = new Process();
        process.StartInfo = psi;
        process.OutputDataReceived += Process_OutputDataReceived;
        process.Exited += Process_Exited;
        process.Disposed += Process_Disposed;
        process.EnableRaisingEvents = true;

        outputBuffer.Clear();
    

    

    string OutputBufferToStr()
    
        return string.Join(Environment.NewLine, outputBuffer);
    

    

    private void Process_Disposed(object sender, EventArgs e)
    
        OnProcessEnd?.Invoke(this, EventArgs.Empty);
    

    private void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
    
        outputBuffer.Add(e.Data);
        OnErrorData?.Invoke(this, e.Data);
    

    private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
    
        outputBuffer.Add(e.Data);
        OnOutputData?.Invoke(this, e.Data);
    

    private void Process_Exited(object sender, EventArgs e)
    
        if (process != null)
            process.Dispose();
        OnProcessEnd?.Invoke(this, EventArgs.Empty);
    





请随意使用。 现在,您有两种主要方法: GetProcessOutput 同步运行进程并将整个输出返回给您。您只需将路径传递给进程和参数列表。

第二种方法是RunProcessAsync - 它异步运行进程。您以相同的方式调用它,但是您必须连接到事件: OnOutputData - 每次将某些内容写入输出时都会调用它 OnErrorData - 发生错误时 OnProcessEnd - 进程何时结束。

希望你会喜欢。

【讨论】:

虽然它没有解决眼前的问题,但这是一段非常有用的代码。感谢分享。【参考方案2】:

只是要把这个扔掉。我在这里非常不稳定,因为我并不真正了解这些东西的任何细节。但最终起作用的是更改命令以选择 git bash,而不是 MINW32。像这样:

 string flashNVS = "\"C:\\Program Files\\Git\\git-bash.exe\" -c \"cd " + unixProdPath + 
            " && ./provision-device-secure test-dev > unixLog.txt \"'";

 Console.WriteLine(flashNVS);

不过,我不会接受我自己的答案,因为它没有解释其中的原因。如果有人能填空,那就太好了。

【讨论】:

以上是关于从 C# Windows 窗体内部调用的 Bash 脚本的主要内容,如果未能解决你的问题,请参考以下文章

从 C# 中的外部 DLL 访问 windows 窗体控件

C# - 从线程更新 windows 窗体元素

C#窗体设计——多个窗体之间的调用

c#中让Windows窗体只运行一次,并在第二次启动窗体时激活该窗体

你如何在 Windows 窗体上显示动画 GIF (c#)

c#利用ApplicationContext类 同时启动双窗体的实现