【问题标题】:Process.start: how to get the output?Process.start:如何获得输出?
【发布时间】:2011-05-16 13:39:19
【问题描述】:

我想从我的 Mono/.NET 应用程序中运行一个外部命令行程序。 例如,我想运行 mencoder。有没有可能:

  1. 要获取命令行 shell 输出,并将其写入我的文本框?
  2. 要获取数值以显示经过时间的进度条?

【问题讨论】:

    标签: c# .net mono process.start


    【解决方案1】:

    执行此操作的标准 .NET 方法是从 Process 的 StandardOutput 流中读取。链接的 MSDN 文档中有一个示例。类似地,您可以从StandardError 读取,并写入StandardInput

    【讨论】:

      【解决方案2】:
      1. 可以按照此处所述获取进程的命令行 shell 输出:http://www.c-sharpcorner.com/UploadFile/edwinlima/SystemDiagnosticProcess12052005035444AM/SystemDiagnosticProcess.aspx

      2. 这取决于 mencoder。如果它在命令行上输出此状态,那么是的:)

      【讨论】:

        【解决方案3】:

        当您适当地创建 Process 对象集 StartInfo 时:

        var proc = new Process 
        {
            StartInfo = new ProcessStartInfo
            {
                FileName = "program.exe",
                Arguments = "command line arguments to your executable",
                UseShellExecute = false,
                RedirectStandardOutput = true,
                CreateNoWindow = true
            }
        };
        

        然后启动进程并从中读取:

        proc.Start();
        while (!proc.StandardOutput.EndOfStream)
        {
            string line = proc.StandardOutput.ReadLine();
            // do something with line
        }
        

        您可以使用int.Parse()int.TryParse() 将字符串转换为数值。如果您读取的字符串中有无效的数字字符,您可能需要先进行一些字符串操作。

        【讨论】:

        • 我想知道您如何处理 StandardError ?顺便说一句,我真的很喜欢这个代码 sn-p !干净整洁。
        • 谢谢,但我想我并不清楚:我应该添加另一个循环吗?
        • 在进程本身终止之前读取是否比等待流结束更健壮?
        • @Gusdor - 我不这么认为。当进程终止时,它的流将自动关闭。此外,一个进程可能会在它终止之前很久就关闭它的流。
        • 我正在尝试在 Ffmpeg 上使用此代码,任何帮助,卡在找出进程工作已完成。
        【解决方案4】:

        您可以同步异步处理您的输出。

        1.同步示例

        static void runCommand()
        {
            Process process = new Process();
            process.StartInfo.FileName = "cmd.exe";
            process.StartInfo.Arguments = "/c DIR"; // Note the /c command (*)
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.RedirectStandardError = true;
            process.Start();
            //* Read the output (or the error)
            string output = process.StandardOutput.ReadToEnd();
            Console.WriteLine(output);
            string err = process.StandardError.ReadToEnd();
            Console.WriteLine(err);
            process.WaitForExit();
        }
        

        注意最好同时处理输出错误:它们必须分开处理。

        (*) 对于某些命令(此处为StartInfo.Arguments),您必须添加/c directive,否则进程会在WaitForExit() 中冻结。

        2。异步示例

        static void runCommand() 
        {
            //* Create your Process
            Process process = new Process();
            process.StartInfo.FileName = "cmd.exe";
            process.StartInfo.Arguments = "/c DIR";
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.RedirectStandardError = true;
            //* Set your output and error (asynchronous) handlers
            process.OutputDataReceived += new DataReceivedEventHandler(OutputHandler);
            process.ErrorDataReceived += new DataReceivedEventHandler(OutputHandler);
            //* Start process and handlers
            process.Start();
            process.BeginOutputReadLine();
            process.BeginErrorReadLine();
            process.WaitForExit();
        }
        
        static void OutputHandler(object sendingProcess, DataReceivedEventArgs outLine) 
        {
            //* Do your stuff with the output (write to console/log/StringBuilder)
            Console.WriteLine(outLine.Data);
        }
        

        如果不需要对输出做复杂的操作,可以绕过OutputHandler方法,直接内联添加handler即可:

        //* Set your output and error (asynchronous) handlers
        process.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);
        process.ErrorDataReceived += (s, e) => Console.WriteLine(e.Data);
        

        【讨论】:

        • 爱异步!我能够在 VB.net 中使用此代码(稍作转录)
        • note '字符串输出 = process.StandardOutput.ReadToEnd();'如果有很多行输出,可能会产生一个大字符串;异步示例和 Ferruccio 的答案都逐行处理输出。
        • 注意:您的第一个(同步)方法不正确!您不应该同时阅读 StandardOutput 和 StandardError !它会导致死锁。至少其中一个必须是异步的。
        • Process.WaitForExit() 是线程阻塞,因此是同步的。不是答案的重点,但我想我可以添加这个。添加 process.EnableRaisingEvents = true 并利用 Exited 事件完全异步。
        • 你错过了一个处置。
        【解决方案5】:

        您可以使用共享内存让两个进程进行通信,查看MemoryMappedFile

        您将主要使用“using”语句在父进程中创建一个内存映射文件mmf,然后创建第二个进程直到它终止并让它使用BinaryWriter将结果写入mmf,然后读取mmf 使用父进程的结果,您也可以使用命令行参数或硬编码传递 mmf 名称。

        确保在父进程中使用映射文件时,让子进程在映射文件在父进程中释放之前将结果写入映射文件

        示例: 父进程

            private static void Main(string[] args)
            {
                using (MemoryMappedFile mmf = MemoryMappedFile.CreateNew("memfile", 128))
                {
                    using (MemoryMappedViewStream stream = mmf.CreateViewStream())
                    {
                        BinaryWriter writer = new BinaryWriter(stream);
                        writer.Write(512);
                    }
        
                    Console.WriteLine("Starting the child process");
                    // Command line args are separated by a space
                    Process p = Process.Start("ChildProcess.exe", "memfile");
        
                    Console.WriteLine("Waiting child to die");
        
                    p.WaitForExit();
                    Console.WriteLine("Child died");
        
                    using (MemoryMappedViewStream stream = mmf.CreateViewStream())
                    {
                        BinaryReader reader = new BinaryReader(stream);
                        Console.WriteLine("Result:" + reader.ReadInt32());
                    }
                }
                Console.WriteLine("Press any key to continue...");
                Console.ReadKey();
            }
        

        子进程

            private static void Main(string[] args)
            {
                Console.WriteLine("Child process started");
                string mmfName = args[0];
        
                using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting(mmfName))
                {
                    int readValue;
                    using (MemoryMappedViewStream stream = mmf.CreateViewStream())
                    {
                        BinaryReader reader = new BinaryReader(stream);
                        Console.WriteLine("child reading: " + (readValue = reader.ReadInt32()));
                    }
                    using (MemoryMappedViewStream input = mmf.CreateViewStream())
                    {
                        BinaryWriter writer = new BinaryWriter(input);
                        writer.Write(readValue * 2);
                    }
                }
        
                Console.WriteLine("Press any key to continue...");
                Console.ReadKey();
            }
        

        要使用此示例,您需要创建一个包含 2 个项目的解决方案,然后从 %childDir%/bin/debug 获取子进程的构建结果并将其复制到 %parentDirectory%/bin/debug然后运行父项目

        childDirparentDirectory 是你的项目在电脑上的文件夹名称 祝你好运:)

        【讨论】:

          【解决方案6】:

          好的,对于希望同时读取错误和输出但使用其他答案中提供的任何解决方案(如我)获得死锁的任何人,这是我在阅读 MSDN 后构建的解决方案StandardOutput 属性的解释。

          答案基于T30的代码:

          static void runCommand()
          {
              //* Create your Process
              Process process = new Process();
              process.StartInfo.FileName = "cmd.exe";
              process.StartInfo.Arguments = "/c DIR";
              process.StartInfo.UseShellExecute = false;
              process.StartInfo.RedirectStandardOutput = true;
              process.StartInfo.RedirectStandardError = true;
              //* Set ONLY ONE handler here.
              process.ErrorDataReceived += new DataReceivedEventHandler(ErrorOutputHandler);
              //* Start process
              process.Start();
              //* Read one element asynchronously
              process.BeginErrorReadLine();
              //* Read the other one synchronously
              string output = process.StandardOutput.ReadToEnd();
              Console.WriteLine(output);
              process.WaitForExit();
          }
          
          static void ErrorOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) 
          {
              //* Do your stuff with the output (write to console/log/StringBuilder)
              Console.WriteLine(outLine.Data);
          }
          

          【讨论】:

          • 感谢您添加此内容。请问你用的是什么命令?
          • 我正在用 c# 开发一个应用程序,旨在启动 mysqldump.exe,向用户显示应用程序生成的每条消息,等待它完成,然后执行更多任务。我不明白你在说什么样的命令?整个问题都是关于从 c# 启动一个进程。
          • 如果您使用两个单独的处理程序,您将不会遇到死锁
          • 同样在你的例子中,你只读取了一次 process.StandardOutput ......在你启动它之后,但是人们想要在进程运行时连续读取它,不是吗?
          • @Curbman,我认为 T30 是在询问“什么命令”,因为您正在触发名为“cmd.exe”的进程。
          【解决方案7】:

          如何启动一个进程(如bat文件、perl脚本、控制台程序)并在windows窗体上显示其标准输出:

          processCaller = new ProcessCaller(this);
          //processCaller.FileName = @"..\..\hello.bat";
          processCaller.FileName = @"commandline.exe";
          processCaller.Arguments = "";
          processCaller.StdErrReceived += new DataReceivedHandler(writeStreamInfo);
          processCaller.StdOutReceived += new DataReceivedHandler(writeStreamInfo);
          processCaller.Completed += new EventHandler(processCompletedOrCanceled);
          processCaller.Cancelled += new EventHandler(processCompletedOrCanceled);
          // processCaller.Failed += no event handler for this one, yet.
          
          this.richTextBox1.Text = "Started function.  Please stand by.." + Environment.NewLine;
          
          // the following function starts a process and returns immediately,
          // thus allowing the form to stay responsive.
          processCaller.Start();    
          

          您可以在此链接上找到ProcessCallerLaunching a process and displaying its standard output

          【讨论】:

            【解决方案8】:

            您可以使用以下代码记录进程输出:

            ProcessStartInfo pinfo = new ProcessStartInfo(item);
            pinfo.CreateNoWindow = false;
            pinfo.UseShellExecute = true;
            pinfo.RedirectStandardOutput = true;
            pinfo.RedirectStandardInput = true;
            pinfo.RedirectStandardError = true;
            pinfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
            var p = Process.Start(pinfo);
            p.WaitForExit();
            Process process = Process.Start(new ProcessStartInfo((item + '>' + item + ".txt"))
            {
                UseShellExecute = false,
                RedirectStandardOutput = true
            });
            process.WaitForExit();
            string output = process.StandardOutput.ReadToEnd();
            if (process.ExitCode != 0) { 
            }
            

            【讨论】:

              【解决方案9】:

              在 win 和 linux 中对我有用的解决方案如下

              // GET api/values
                      [HttpGet("cifrado/{xml}")]
                      public ActionResult<IEnumerable<string>> Cifrado(String xml)
                      {
                          String nombreXML = DateTime.Now.ToString("ddMMyyyyhhmmss").ToString();
                          String archivo = "/app/files/"+nombreXML + ".XML";
                          String comando = " --armor --recipient bibankingprd@bi.com.gt  --encrypt " + archivo;
                          try{
                              System.IO.File.WriteAllText(archivo, xml);                
                              //String comando = "C:\\GnuPG\\bin\\gpg.exe --recipient licorera@local.com --armor --encrypt C:\\Users\\Administrador\\Documents\\pruebas\\nuevo.xml ";
                              ProcessStartInfo startInfo = new ProcessStartInfo() {FileName = "/usr/bin/gpg",  Arguments = comando }; 
                              Process proc = new Process() { StartInfo = startInfo, };
                              proc.StartInfo.RedirectStandardOutput = true;
                              proc.StartInfo.RedirectStandardError = true;
                              proc.Start();
                              proc.WaitForExit();
                              Console.WriteLine(proc.StandardOutput.ReadToEnd());
                              return new string[] { "Archivo encriptado", archivo + " - "+ comando};
                          }catch (Exception exception){
                              return new string[] { archivo, "exception: "+exception.ToString() + " - "+ comando };
                          }
                      }
              

              【讨论】:

              • 被普通catch(Exception)捕获的异常必须重新抛出,否则它会吞下可以被“上层”代码等待的异常。在给定的示例中,如果异常发生在 try 块内,调试器将不会停止
              【解决方案10】:

              我在调用Process.StandardOutput.ReadLineProcess.StandardOutput.ReadToEnd 时遇到了臭名昭著的死锁问题。

              我的目标/用例很简单。启动一个进程并重定向它的输出,以便我可以捕获该输出并通过 .NET Core 的ILogger&lt;T&gt; 将其记录到控制台,并将重定向的输出附加到文件日志中。

              这是我使用内置异步事件处理程序 Process.OutputDataReceivedProcess.ErrorDataReceived 的解决方案。

              var p = new Process
              {
                  StartInfo = new ProcessStartInfo(
                      command.FileName, command.Arguments
                  )
                  {
                      RedirectStandardOutput = true,
                      RedirectStandardError = true,
                      UseShellExecute = false,
                  }
              };
              
              
              // Asynchronously pushes StdOut and StdErr lines to a thread safe FIFO queue
              var logQueue = new ConcurrentQueue<string>();
              p.OutputDataReceived += (sender, args) => logQueue.Enqueue(args.Data);
              p.ErrorDataReceived += (sender, args) => logQueue.Enqueue(args.Data);
              
              // Start the process and begin streaming StdOut/StdErr
              p.Start();
              p.BeginOutputReadLine();
              p.BeginErrorReadLine();
              
              // Loop until the process has exited or the CancellationToken is triggered
              do
              {
                  var lines = new List<string>();
                  while (logQueue.TryDequeue(out var log))
                  {
                      lines.Add(log);
                      _logger.LogInformation(log)
                  }
                  File.AppendAllLines(_logFilePath, lines);
              
                  // Asynchronously sleep for some time
                  try
                  {
                      Task.Delay(5000, stoppingToken).Wait(stoppingToken);
                  }
                  catch(OperationCanceledException) {}
              
              } while (!p.HasExited && !stoppingToken.IsCancellationRequested);
              

              【讨论】:

                【解决方案11】:

                System.Diagnostics.Process 不是最愉快的工作,所以你可能想试试CliWrap。它提供了许多不同的模型来处理输出,包括管道、缓冲和实时流。以下是一些示例(摘自自述文件)。

                只需启动命令行可执行文件:

                using CliWrap;
                
                var result = await Cli.Wrap("path/to/exe")
                    .WithArguments("--foo bar")
                    .WithWorkingDirectory("work/dir/path")
                    .ExecuteAsync();
                    
                // Result contains:
                // -- result.ExitCode        (int)
                // -- result.StartTime       (DateTimeOffset)
                // -- result.ExitTime        (DateTimeOffset)
                // -- result.RunTime         (TimeSpan)
                

                启动命令行可执行文件并在内存中缓冲 stdout/stderr:

                using CliWrap;
                using CliWrap.Buffered;
                
                // Calling `ExecuteBufferedAsync()` instead of `ExecuteAsync()`
                // implicitly configures pipes that write to in-memory buffers.
                var result = await Cli.Wrap("path/to/exe")
                    .WithArguments("--foo bar")
                    .WithWorkingDirectory("work/dir/path")
                    .ExecuteBufferedAsync();
                
                // Result contains:
                // -- result.StandardOutput  (string)
                // -- result.StandardError   (string)
                // -- result.ExitCode        (int)
                // -- result.StartTime       (DateTimeOffset)
                // -- result.ExitTime        (DateTimeOffset)
                // -- result.RunTime         (TimeSpan)
                

                使用手动管道配置启动命令行可执行文件:

                using CliWrap
                
                var buffer = new StringBuilder();
                
                var result = await Cli.Wrap("foo")
                    .WithStandardOutputPipe(PipeTarget.ToFile("output.txt"))
                    .WithStandardErrorPipe(PipeTarget.ToStringBuilder(buffer))
                    .ExecuteAsync();
                

                以事件流的形式启动命令行可执行文件:

                using CliWrap;
                using CliWrap.EventStream;
                
                var cmd = Cli.Wrap("foo").WithArguments("bar");
                
                await foreach (var cmdEvent in cmd.ListenAsync())
                {
                    switch (cmdEvent)
                    {
                        case StartedCommandEvent started:
                            _output.WriteLine($"Process started; ID: {started.ProcessId}");
                            break;
                        case StandardOutputCommandEvent stdOut:
                            _output.WriteLine($"Out> {stdOut.Text}");
                            break;
                        case StandardErrorCommandEvent stdErr:
                            _output.WriteLine($"Err> {stdErr.Text}");
                            break;
                        case ExitedCommandEvent exited:
                            _output.WriteLine($"Process exited; Code: {exited.ExitCode}");
                            break;
                    }
                }
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2019-10-24
                  • 2010-11-26
                  • 2021-12-15
                  • 2019-03-09
                  • 2013-03-22
                  • 1970-01-01
                  相关资源
                  最近更新 更多