ChatGPT解决这个技术问题 Extra ChatGPT

Process.start:如何获得输出?

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

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


m
meJustAndrew

当您适当地创建 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 ?顺便说一句,我真的很喜欢这个代码片段!干净整洁。
谢谢,但我想我不清楚:我应该添加另一个循环吗?
在进程本身终止之前读取而不是等待流结束是否更健壮?
@Gusdor - 我不这么认为。当进程终止时,它的流将自动关闭。此外,一个进程可能会在它终止之前很久就关闭它的流。
我正在尝试在 Ffmpeg 上使用此代码,任何帮助,都卡在找出进程工作已完成。
L
Legends

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

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 方法,直接内联添加处理程序:

//* 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 中使用此代码(稍加转录)
注意:您的第一个(同步)方法不正确!您不应该同时阅读 StandardOutput 和 StandardError !它会导致死锁。至少其中一个必须是异步的。
Process.WaitForExit() 是线程阻塞,因此是同步的。不是答案的重点,但我想我可以添加这个。添加 process.EnableRaisingEvents = true 并利用 Exited 事件完全异步。
你错过了一个处置。
这是太棒了。与@RichardBarker 类似,我也可以将它与一些转录成VB.Net 一起使用,这完全符合我的需要!为每个 OutputDataReceivedErrorDataReceived 添加事件处理程序并将数据附加到 Public StringBuilders(将它们用于多个 shell 命令)使我能够挂钩 StdOut/StdErr 数据并对其进行处理以向我的用户提供反馈!很棒的东西!
Y
Yousha Aleayoub

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

答案基于 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”的进程。
d
driis

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


s
stema

可以按此处所述获取进程的命令行 shell 输出:http://www.c-sharpcorner.com/UploadFile/edwinlima/SystemDiagnosticProcess12052005035444AM/SystemDiagnosticProcess.aspx 这取决于mencoder。如果它在命令行上输出此状态,那么是的:)


s
shakram02

您可以为 2 个进程使用共享内存进行通信,请查看 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 是您在 PC 上的项目的文件夹名称,祝您好运 :)


S
SHUBHAM PATIDAR

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

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

a
abberdeen

如何启动进程(例如 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


T
Tyrrrz

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

J
Jorge Santos Neill

在 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 块内,调试器将不会停止
J
Jason Harris

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

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

这是我使用内置异步事件处理程序 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);