如何在C#中执行命令行并获取标准输出结果

529

我怎样才能在C#中执行命令行程序并获取标准输出结果?具体而言,我想在两个通过编程选择的文件上执行DIFF并将结果写入文本框。


2
参见 https://dev59.com/om435IYBdhLWcg3whQc5#5367686 - 它展示了输出和错误的事件。 - CAD bloke
相关(但不捕获STDOUT):https://dev59.com/pHM_5IYBdhLWcg3wPAZF - user202729
17个回答

585
// Start the child process.
 Process p = new Process();
 // Redirect the output stream of the child process.
 p.StartInfo.UseShellExecute = false;
 p.StartInfo.RedirectStandardOutput = true;
 p.StartInfo.FileName = "YOURBATCHFILE.bat";
 p.Start();
 // Do not wait for the child process to exit before
 // reading to the end of its redirected stream.
 // p.WaitForExit();
 // Read the output stream first and then wait.
 string output = p.StandardOutput.ReadToEnd();
 p.WaitForExit();

代码来源于MSDN


9
有没有不使用批处理文件的方法?问题是,我需要将一些参数发送给命令。我正在使用 xsd.exe <Assembly> /type:<ClassName> 命令,因此我需要设置程序集和类名,然后运行该命令。 - Carlo
31
你可以通过{YourProcessObject}.StartInfo.Arguments字符串向你的调用添加参数。 - patridge
6
如何将进程以管理员身份运行? - Saher Ahwal
7
我遇到了一些问题,使用这段代码时,我的进程会完全停止,因为该进程已经向 p.StandardError 流写入了足够的数据。当流变满时,似乎进程将停止,直到数据被消耗,因此我必须读取 StandardErrorStandardOutput 以确保任务正确执行。 - Ted Spence
7
来自C#编译器的快速提示:为了重定向IO流,Process对象必须将UseShellExecute属性设置为false。 - IbrarMumtaz
显示剩余8条评论

164

这里是一个快速示例:

//Create process
System.Diagnostics.Process pProcess = new System.Diagnostics.Process();

//strCommand is path and file name of command to run
pProcess.StartInfo.FileName = strCommand;

//strCommandParameters are parameters to pass to program
pProcess.StartInfo.Arguments = strCommandParameters;

pProcess.StartInfo.UseShellExecute = false;

//Set output of program to be written to process output stream
pProcess.StartInfo.RedirectStandardOutput = true;   

//Optional
pProcess.StartInfo.WorkingDirectory = strWorkingDirectory;

//Start the process
pProcess.Start();

//Get program output
string strOutput = pProcess.StandardOutput.ReadToEnd();

//Wait for process to finish
pProcess.WaitForExit();

4
展示如何向正在运行的命令行程序添加参数(这是被接受的答案所没有的),点赞一下。 - Suman

116

还有一个我发现很有用的参数,我用它来消除进程窗口

pProcess.StartInfo.CreateNoWindow = true;

如果这是你想要的,这可以完全隐藏黑色控制台窗口,使用户看不到它。


3
省了我很多麻烦。谢谢。 - The Vivandiere
2
在调用“sc”时,我还必须设置StartInfo.WindowStyle = ProcessWindowStyle.Hidden。 - Pedro

113
// usage
const string ToolFileName = "example.exe";
string output = RunExternalExe(ToolFileName);

public string RunExternalExe(string filename, string arguments = null)
{
    var process = new Process();

    process.StartInfo.FileName = filename;
    if (!string.IsNullOrEmpty(arguments))
    {
        process.StartInfo.Arguments = arguments;
    }

    process.StartInfo.CreateNoWindow = true;
    process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
    process.StartInfo.UseShellExecute = false;

    process.StartInfo.RedirectStandardError = true;
    process.StartInfo.RedirectStandardOutput = true;
    var stdOutput = new StringBuilder();
    process.OutputDataReceived += (sender, args) => stdOutput.AppendLine(args.Data); // Use AppendLine rather than Append since args.Data is one line of output, not including the newline character.

    string stdError = null;
    try
    {
        process.Start();
        process.BeginOutputReadLine();
        stdError = process.StandardError.ReadToEnd();
        process.WaitForExit();
    }
    catch (Exception e)
    {
        throw new Exception("OS error while executing " + Format(filename, arguments)+ ": " + e.Message, e);
    }

    if (process.ExitCode == 0)
    {
        return stdOutput.ToString();
    }
    else
    {
        var message = new StringBuilder();

        if (!string.IsNullOrEmpty(stdError))
        {
            message.AppendLine(stdError);
        }

        if (stdOutput.Length != 0)
        {
            message.AppendLine("Std output:");
            message.AppendLine(stdOutput.ToString());
        }

        throw new Exception(Format(filename, arguments) + " finished with exit code = " + process.ExitCode + ": " + message);
    }
}

private string Format(string filename, string arguments)
{
    return "'" + filename + 
        ((string.IsNullOrEmpty(arguments)) ? string.Empty : " " + arguments) +
        "'";
}

4
一个非常全面的例子,谢谢。 - ShahidAzim
2
可能需要将OutputDataReceived处理程序更改为stdOut.AppendLine()。 - Paul Williams
4
我的看法是,这个解决方案比被采纳的答案更全面。我现在正在使用它,而没有使用被接受的那个答案,但那个答案看起来确实有些欠缺。 - ProfK
1
感谢您使用 process.StartInfo.RedirectStandardError = true;if (process.ExitCode == 0),这是被接受的答案所没有的。 - JohnB
谢谢,它在Ubuntu 20 LTS上运行良好。(仅供记录) - Soren
要运行任意命令并终止,需要在参数前添加“/c”。例如,filename = "cmd.exe"arguments = "/c python --version" - jrbe228

22
这个页面上被接受的答案有一个弱点,这在罕见情况下会引起问题。程序按惯例会写入两个文件句柄 stdout 和 stderr。如果你只读取像 Ray 的答案这样的单个文件句柄,并且你启动的程序向 stderr 输出足够多的输出,它将填满 stderr 输出缓冲区并阻塞。那么你的两个进程就会死锁。缓冲区大小可能为 4K。这在短暂的程序中非常罕见,但如果你有一个长时间运行的程序,它会反复向 stderr 输出,最终会发生这种情况。这很难调试和跟踪。
有几种好的方法可以解决这个问题:
1.一种方法是执行 cmd.exe 而不是你的程序,并使用 /c 参数来调用你的程序,再使用 "2>&1" 参数告诉 cmd.exe 合并 stdout 和 stderr。
        var p = new Process();
        p.StartInfo.FileName = "cmd.exe";
        p.StartInfo.Arguments = "/c mycmd.exe 2>&1";
另一种方法是使用一个编程模型同时读取两个句柄。
        var p = new Process();
        p.StartInfo.FileName = "cmd.exe";
        p.StartInfo.Arguments = @"/c dir \windows";
        p.StartInfo.CreateNoWindow = true;
        p.StartInfo.RedirectStandardError = true;
        p.StartInfo.RedirectStandardOutput = true;
        p.StartInfo.RedirectStandardInput = false;
        p.OutputDataReceived += (a, b) => Console.WriteLine(b.Data);
        p.ErrorDataReceived += (a, b) => Console.WriteLine(b.Data);
        p.Start();
        p.BeginErrorReadLine();
        p.BeginOutputReadLine();
        p.WaitForExit();

3
我认为这个回答更好地回答了原问题,因为它展示了如何通过C#运行CMD命令(而不是文件)。 - TinyRacoon

12
 System.Diagnostics.ProcessStartInfo psi =
   new System.Diagnostics.ProcessStartInfo(@"program_to_call.exe");
 psi.RedirectStandardOutput = true;
 psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
 psi.UseShellExecute = false;
 System.Diagnostics.Process proc = System.Diagnostics.Process.Start(psi); ////
 System.IO.StreamReader myOutput = proc.StandardOutput;
 proc.WaitForExit(2000);
 if (proc.HasExited)
  {
      string output = myOutput.ReadToEnd();
 }

当进程写入大量数据时可能会发生死锁。最好在进程仍在运行时开始读取数据。 - JensG

7
你需要使用启用了RedirectStandardOutputProcessStartInfo,然后你就可以读取输出流。你可能会发现使用">"将输出重定向到文件(通过操作系统),然后简单地读取文件更容易。

11
这句话的意思是,有时候你需要写一个文件来保存一些内容,但这需要获得许可,需要选择一个文件名和位置,并且在使用完后不要忘记删除。相比之下,使用 RedirectStandardOutput 更加容易。 - peSHIr

6

这是一个小例子:

using System;
using System.Diagnostics;

class Program
{
    static void Main(string[] args)
    {
        var p = Process.Start(
            new ProcessStartInfo("git", "branch --show-current")
            {
                CreateNoWindow = true,
                UseShellExecute = false,
                RedirectStandardError = true,
                RedirectStandardOutput = true,
                WorkingDirectory = Environment.CurrentDirectory
            }
        );

        p.WaitForExit();
        string branchName =p.StandardOutput.ReadToEnd().TrimEnd();
        string errorInfoIfAny =p.StandardError.ReadToEnd().TrimEnd();

        if (errorInfoIfAny.Length != 0)
        {
            Console.WriteLine($"error: {errorInfoIfAny}");
        }
        else { 
            Console.WriteLine($"branch: {branchName}");
        }

    }
}

我相信这是最简洁的形式。

请注意,大多数命令行工具很容易混淆标准输出和标准错误,有时将它们合并成一个字符串可能更有意义。

此外,p.ExitCode有时也会非常有用。

上面的示例用于编写类似工具的命令行实用程序,如果您想自己完成,请注意对于 cli 自动化,还可以使用Cake FrostenCake Git extension


5

如果您还需要在cmd.exe中执行某些命令,可以按照以下步骤:

// Start the child process.
Process p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/C vol";
p.Start();
// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
Console.WriteLine(output);

这将仅返回命令本身的输出:

enter image description here

您也可以使用StandardInput而非StartInfo.Arguments

// Start the child process.
Process p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "cmd.exe";
p.Start();
// Read the output stream first and then wait.
p.StandardInput.WriteLine("vol");
p.StandardInput.WriteLine("exit");
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
Console.WriteLine(output);

结果如下:

enter image description here


5

由于大多数答案没有实现using语句来处理IDisposable和其他一些我认为必要的东西,因此我将添加这个答案。

对于 C# 8.0:

// Start a process with the filename or path with filename e.g. "cmd". Please note the 
//using statemant
using myProcess.StartInfo.FileName = "cmd";
// add the arguments - Note add "/c" if you want to carry out tge  argument in cmd and  
// terminate
myProcess.StartInfo.Arguments = "/c dir";
// Allows to raise events
myProcess.EnableRaisingEvents = true;
//hosted by the application itself to not open a black cmd window
myProcess.StartInfo.UseShellExecute = false;
myProcess.StartInfo.CreateNoWindow = true;
// Eventhander for data
myProcess.Exited += OnOutputDataRecived;
// Eventhandler for error
myProcess.ErrorDataReceived += OnErrorDataReceived;
// Eventhandler wich fires when exited
myProcess.Exited += OnExited;
// Starts the process
myProcess.Start();
//read the output before you wait for exit
myProcess.BeginOutputReadLine();
// wait for the finish - this will block (leave this out if you dont want to wait for 
// it, so it runs without blocking)
process.WaitForExit();

// Handle the dataevent
private void OnOutputDataRecived(object sender, DataReceivedEventArgs e)
{
    //do something with your data
    Trace.WriteLine(e.Data);
}

//Handle the error
private void OnErrorDataReceived(object sender, DataReceivedEventArgs e)
{        
    Trace.WriteLine(e.Data);
    //do something with your exception
    throw new Exception();
}    

// Handle Exited event and display process information.
private void OnExited(object sender, System.EventArgs e)
{
     Trace.WriteLine("Process exited");
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接