Process.start:如何获取输出?


306

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

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

Answers:


458

适当地创建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()将字符串转换为数字值。如果您读取的字符串中包含无效的数字字符,则可能必须先进行一些字符串操作。


4
我想知道如何处理StandardError?顺便说一句,我真的很喜欢这个代码片段!干净整洁。
Codea

3
谢谢,但是我想我不清楚:我应该添加另一个循环吗?
编码

@codea-我明白了。您可以创建一个循环,当两个流都到达EOF时终止。这可能会有点复杂,因为一个流不可避免地会首先到达EOF,并且您不再想要阅读它。您还可以在两个不同的线程中使用两个循环。
Ferruccio

1
在进程本身终止之前而不是在流结束之前进行读取是否更健壮?
2014年

@Gusdor-我不这么认为。当进程终止时,其流将自动关闭。同样,进程可能在终止之前很长时间就关闭了其流。
费鲁乔

253

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

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 指令,否则该进程将冻结在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);

2
要爱异步!我能够在VB.net中使用此代码(只需一点点转录)
Richard Barker

注意'字符串输出= process.StandardOutput.ReadToEnd();' 如果有多行输出,可能会产生一个大字符串;异步示例,以及Ferruccio的回答均逐行处理输出。
安德鲁·希尔

5
注意:您的第一种(同步)方法不正确!您不应该同时阅读StandardOutput和StandardError!它将导致死锁。其中至少一个必须是异步的。
S.Serpooshan

6
Process.WaitForExit()是线程阻塞的,因此是同步的。不是答案的重点,但我想我可以补充一下。添加process.EnableRaisingEvents = true并利用Exited事件是完全异步的。
汤姆(Tom)

是否可以直接重定向?我用所有的sass输出颜色?
伊尼(Ini)

14

好了,对于希望同时读取“错误和输出”但又遇到其他答案(如我)所提供的任何解决方案都陷入僵局的任何人,这是我在阅读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);
}

感谢您添加。请问您使用的是什么命令?
T30

我正在用c#开发一个旨在启动mysqldump.exe的应用程序,向用户显示该应用程序生成的每条消息,等待它完成,然后执行更多任务。我不明白您在说哪种命令?整个问题都与从c#启动进程有关。
cubrman '17

1
如果使用两个单独的处理程序,则不会出现死锁
Ovi

同样在您的示例中,您仅读取一次process.StandardOutput ...在启动之后立即读取它,但是您可能想在过程运行时连续读取它,不是吗?
Ovi)



4

您可以将共享内存用于2个进程进行通信,请检出 MemoryMappedFile

您将主要mmf使用“ using”语句在父进程中创建一个内存映射文件,然后创建第二个进程直到终止,然后将结果写入mmfusingBinaryWriter,然后将结果,然后从mmfusing父进程中读取结果,也可以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中,然后运行父项目

childDir并且parentDirectory是在PC好运您的项目的文件夹名称:)


1

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

您可以ProcessCaller在此链接上找到:启动流程并显示其标准输出


1

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

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

1

在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 };
            }
        }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.