如何:在c#中执行命令行,得到STD输出结果

我如何从c#执行命令行程序,并获得STD OUT结果?具体来说,我想对两个以编程方式选择的文件执行DIFF,并将结果写入一个文本框。

544184 次浏览

这可能不是最好/最简单的方法,但可能是一种选择:

当从代码执行时,添加“> output.txt”,然后读入output.txt文件。

// 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();

Code来自MSDN

你需要使用启用了RedirectStandardOutputProcessStartInfo -然后你就可以读取输出流了。您可能会发现使用“>”将输出重定向到一个文件(通过操作系统)更容易,然后只需读取该文件。

[编辑:像Ray那样:+1]

您可以使用Process类启动任何命令行程序,并使用您创建的流阅读器(基于字符串或内存位置)设置Process实例的StandardOutput属性。流程完成后,您就可以在流上执行所需的任何diff。

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

这里有一个简单的例子:

//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();

PublicDomain开源代码中有一个ProcessHelper类,你可能会感兴趣。

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

pProcess.StartInfo.CreateNoWindow = true;

这有助于从用户完全隐藏黑色控制台窗口,如果这是你想要的。

// 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) +
"'";
}

如果您试图查询PC/服务器上的本地ARP缓存,这可能对某人有用。

List<string[]> results = new List<string[]>();


using (Process p = new Process())
{
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.Arguments = "/c arp -a";
p.StartInfo.FileName = @"C:\Windows\System32\cmd.exe";
p.Start();


string line;


while ((line = p.StandardOutput.ReadLine()) != null)
{
if (line != "" && !line.Contains("Interface") && !line.Contains("Physical Address"))
{
var lineArr = line.Trim().Split(' ').Select(n => n).Where(n => !string.IsNullOrEmpty(n)).ToArray();
var arrResult = new string[]
{
lineArr[0],
lineArr[1],
lineArr[2]
};
results.Add(arrResult);
}
}


p.WaitForExit();
}

如果你不介意引入依赖项,CliWrap可以为你简化:

using CliWrap;
using CliWrap.Buffered;


var result = await Cli.Wrap("target.exe")
.WithArguments("arguments")
.ExecuteBufferedAsync();


var stdout = result.StandardOutput;

本页公认的答案有一个缺点,在极少数情况下很麻烦。有两个文件句柄,程序通过约定、stdout和stderr写入。 如果您只读取一个文件句柄,例如来自Ray的答案,并且您正在启动的程序写入足够的输出到stderr,它将填满输出stderr缓冲区和块。那么您的两个进程就会死锁。缓冲区大小可能是4K。 这种情况在短时间运行的程序中非常罕见,但是如果您有一个长时间运行的程序,并且反复输出到stderr,那么这种情况最终会发生。

有一些很好的方法来处理这个问题。

  1. 一种方法是执行cmd.exe而不是你的程序,并使用cmd.exe的/c参数来调用你的程序,同时使用cmd.exe的“2>&1”参数来告诉它合并stdout和stderr。

            var p = new Process();
    p.StartInfo.FileName = "cmd.exe";
    p.StartInfo.Arguments = "/c mycmd.exe 2>&1";
    
  2. Another way is to use a programming model which reads both handles at the same time.

            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();
    

只是为了好玩,这里是我完整的获得PYTHON输出的解决方案-在单击按钮下-带有错误报告。只需添加一个名为“butPython”的按钮和一个名为“llHello”的标签…

    private void butPython(object sender, EventArgs e)
{
llHello.Text = "Calling Python...";
this.Refresh();
Tuple<String,String> python = GoPython(@"C:\Users\BLAH\Desktop\Code\Python\BLAH.py");
llHello.Text = python.Item1; // Show result.
if (python.Item2.Length > 0) MessageBox.Show("Sorry, there was an error:" + Environment.NewLine + python.Item2);
}


public Tuple<String,String> GoPython(string pythonFile, string moreArgs = "")
{
ProcessStartInfo PSI = new ProcessStartInfo();
PSI.FileName = "py.exe";
PSI.Arguments = string.Format("\"{0}\" {1}", pythonFile, moreArgs);
PSI.CreateNoWindow = true;
PSI.UseShellExecute = false;
PSI.RedirectStandardError = true;
PSI.RedirectStandardOutput = true;
using (Process process = Process.Start(PSI))
using (StreamReader reader = process.StandardOutput)
{
string stderr = process.StandardError.ReadToEnd(); // Error(s)!!
string result = reader.ReadToEnd(); // What we want.
return new Tuple<String,String> (result,stderr);
}
}

一行运行命令:

new Process() { StartInfo = new ProcessStartInfo("echo", "Hello, World") }.Start();

以最短的可读代码量读取命令输出:

    var cliProcess = new Process() {
StartInfo = new ProcessStartInfo("echo", "Hello, World") {
UseShellExecute = false,
RedirectStandardOutput = true
}
};
cliProcess.Start();
string cliOut = cliProcess.StandardOutput.ReadToEnd();
cliProcess.WaitForExit();
cliProcess.Close();

如果你还需要在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

由于这里的大多数答案没有实现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");
}

朱利安的解决方案经过了一些小的修正。下面是一个同样使用https://sourceforge.net/projects/bat-to-exe/ GenericConsole.cs和https://www.codeproject.com/Articles/19225/Bat-file-compiler program.txt作为args部分的例子:

using System;
using System.Text;  //StringBuilder
using System.Diagnostics;
using System.IO;




class Program
{
private static bool redirectStandardOutput = true;


private static string buildargument(string[] args)
{
StringBuilder arg = new StringBuilder();
for (int i = 0; i < args.Length; i++)
{
arg.Append("\"" + args[i] + "\" ");
}


return arg.ToString();
}


static void Main(string[] args)
{
Process prc = new Process();
prc.StartInfo = //new ProcessStartInfo("cmd.exe", String.Format("/c \"\"{0}\" {1}", Path.Combine(Environment.CurrentDirectory, "mapTargetIDToTargetNameA3.bat"), buildargument(args)));
//new ProcessStartInfo(Path.Combine(Environment.CurrentDirectory, "mapTargetIDToTargetNameA3.bat"), buildargument(args));
new ProcessStartInfo("mapTargetIDToTargetNameA3.bat");
prc.StartInfo.Arguments = buildargument(args);


prc.EnableRaisingEvents = true;


if (redirectStandardOutput == true)
{
prc.StartInfo.UseShellExecute = false;
}
else
{
prc.StartInfo.UseShellExecute = true;
}


prc.StartInfo.CreateNoWindow = true;


prc.OutputDataReceived += OnOutputDataRecived;
prc.ErrorDataReceived += OnErrorDataReceived;
//prc.Exited += OnExited;


prc.StartInfo.RedirectStandardOutput = redirectStandardOutput;
prc.StartInfo.RedirectStandardError = redirectStandardOutput;


try
{
prc.Start();
prc.BeginOutputReadLine();
prc.BeginErrorReadLine();
prc.WaitForExit();
}
catch (Exception e)
{
Console.WriteLine("OS error: " + e.Message);
}


prc.Close();
}


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


//Handle the error
private static void OnErrorDataReceived(object sender, DataReceivedEventArgs e)
{
Console.WriteLine(e.Data);
}


// Handle Exited event and display process information.
//private static void OnExited(object sender, System.EventArgs e)
//{
//    var process = sender as Process;
//    if (process != null)
//    {
//        Console.WriteLine("ExitCode: " + process.ExitCode);
//    }
//    else
//    {
//        Console.WriteLine("Process exited");
//    }
//}
}

代码需要在VS2007内部编译,使用命令行csc.exe生成的可执行文件将不能正确显示控制台输出,甚至崩溃与CLR20r3错误。注释掉OnExited事件过程,蝙蝠到exe的控制台输出将更像原来的蝙蝠控制台输出。

这里有一个小例子:

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有时可能有用。