【发布时间】:2019-01-11 19:19:16
【问题描述】:
从网络上的各种来源,我整理了以下代码,用于通过CMD.exe 执行命令并捕获来自STDOUT 和STDERR 的输出。
public static class Exec
{
public delegate void OutputHandler(String line);
// <summary>
/// Run a command in a subprocess
/// </summary>
/// <param name="path">Directory from which to execute the command</param>
/// <param name="cmd">Command to execute</param>
/// <param name="args">Arguments for command</param>
/// <param name="hndlr">Command output handler (null if none)</param>
/// <param name="noshow">True if no windows is to be shown</param>
/// <returns>Exit code from executed command</returns>
public static int Run(String path, String cmd, String args,
OutputHandler hndlr = null, Boolean noshow = true)
{
// Assume an error
int ret = 1;
// Create a process
using (var p = new Process())
{
// Run command using CMD.EXE
// (this way we can pipe STDERR to STDOUT so they can get handled together)
p.StartInfo.FileName = "cmd.exe";
// Set working directory (if supplied)
if (!String.IsNullOrWhiteSpace(path)) p.StartInfo.WorkingDirectory = path;
// Indicate command and arguments
p.StartInfo.Arguments = "/c \"" + cmd + " " + args + "\" 2>&1";
// Handle noshow argument
p.StartInfo.CreateNoWindow = noshow;
p.StartInfo.UseShellExecute = false;
// See if handler provided
if (hndlr != null)
{
// Redirect STDOUT and STDERR
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
// Use custom event handler to capture output
using (var outputWaitHandle = new AutoResetEvent(false))
{
p.OutputDataReceived += (sender, e) =>
{
// See if there is any data
if (e.Data == null)
{
// Signal output processing complete
outputWaitHandle.Set();
}
else
{
// Pass string to string handler
hndlr(e.Data);
}
};
// Start process
p.Start();
// Begin async read
p.BeginOutputReadLine();
// Wait for process to terminate
p.WaitForExit();
// Wait on output processing complete signal
outputWaitHandle.WaitOne();
}
}
else
{
// Start process
p.Start();
// Wait for process to terminate
p.WaitForExit();
}
// Get exit code
ret = p.ExitCode;
}
// Return result
return ret;
}
// <summary>
/// Run a command in a subprocess and return output in a variable
/// </summary>
/// <param name="path">Directory from which to execute the command</param>
/// <param name="cmd">Command to execute</param>
/// <param name="args">Arguments for command</param>
/// <param name="outp">Variable to contain the output</param>
/// <returns>Exit code from executed command</returns>
public static GetOutputReturn GetOutput(String path, String cmd, String args)
{
GetOutputReturn ret = new GetOutputReturn();
ret.ReturnCode = Run(path, cmd, args, (line) =>
{
ret.Output.AppendLine(line);
});
return ret;
}
}
public class GetOutputReturn
{
public StringBuilder Output = new StringBuilder();
public int ReturnCode = 1;
}
我可以通过以下三种不同的方式在控制台应用程序中使用它:
static void Main(string[] args)
{
int ret;
Console.WriteLine("Executing dir with no capture and no window");
ret = Exec.Run(@"C:\", "dir", "");
Console.WriteLine("Execute returned " + ret);
Console.WriteLine("Press enter to continue ...");
Console.ReadLine();
Console.WriteLine("Executing dir with no capture and window");
ret = Exec.Run(@"C:\", "dir", "", null, false);
Console.WriteLine("Execute returned " + ret);
Console.WriteLine("Press enter to continue ...");
Console.ReadLine();
Console.WriteLine("Executing dir with capture and no window");
var results = Exec.GetOutput(@"C:\", "dir", "");
Console.WriteLine(results.Output.ToString());
Console.WriteLine("Execute returned " + results.ReturnCode);
Console.ReadLine();
Console.WriteLine("Executing dir with real-time capture and no window");
ret = Exec.Run(@"C:\", "dir", "", ShowString);
Console.WriteLine("Execute returned " + ret);
}
public delegate void StringData(String str);
static void ShowString(String str)
{
Console.WriteLine(str);
}
public delegate void StringData(String str);
static void ShowString(String str)
{
Console.WriteLine(str);
}
第一次运行不收集任何输出,只显示退出代码。
第二次运行不会收集任何输出,但会显示窗口。
输出实时显示在控制台窗口中的效果。
第三次运行使用 GetOutput 收集输出。
这样做的效果是在运行完成之前不会出现输出。
最后一次运行使用处理程序来实时接收和显示输出。
从外观上看,这看起来像是第二次运行,但非常不同。
对于接收到的每一行输出,都会调用 ShowString。
显示字符串只是显示字符串。
但是,它可以对数据做任何它需要的事情。
我正在尝试调整上次运行,以便可以使用命令的输出实时更新文本框。我遇到的问题是如何在正确的上下文中使用它(因为没有更好的术语)。因为 OutputHandler 是异步调用的,所以它必须使用InvokeRequired/BeginInvoke/EndInvoke 机制来与 UI 线程同步。我对如何使用参数执行此操作有一点问题。在我的代码中,文本框可能是选项卡控件中的几个之一,因为可能会发生多个背景“运行”。
到目前为止,我有这个:
private void btnExecute_Click(object sender, EventArgs e)
{
// Get currently selected tab page
var page = tcExecControl.SelectedTab;
// Get text box (always 3rd control on the page)
var txt = (TextBox)page.Controls[2];
// Create string handler
var prc = new Exec.OutputHandler((String line) =>
{
if (txt.InvokeRequired)
txt.Invoke(new MethodInvoker(() =>
{ txt.Text += line; }));
else txt.Text += line;
});
// Command and arguments are always 1st and 2nd controls on the page
var result = Exec.Run(@"C:\", page.Controls[0].Text, page.Controls[1], prc);
}
但这似乎不起作用。我没有看到 txtBox 的任何输出。
实际上程序基本上挂在处理程序中。
如果我将代码更改为使用 GetOutput,然后将结果输出写入文本框,一切正常。所以我知道我已经正确设置了命令。使用调试器,我可以在“if (txt.InvokeRequired)”行上设置断点,并且我看到第一行输出正确。此时代码采用 if 语句的真实路径,但如果我在 txt.Text += line; 行设置断点,它永远不会到达那里。
谁能帮帮我?我确定我错过了什么。
【问题讨论】:
-
在 txt.Text += 行设置断点时;您是在运行代码还是单步执行?如果是第一次,那么它可能在第一次实际启动之前第二次调用该方法并在那里导致问题。只是尝试一下。
-
我很确定我尝试了两种方式...继续并逐步。
-
尝试将整个 Exec.OutputHandler 代码放入它自己的函数中,看看是否有帮助。换行:{ txt.Text += line; }));改为调用您创建的函数。
-
你的意思是something like this?您可以使用进程事件(包括
Exited事件,启用设置EnableRaisingEvents = true;),抛弃WaitForExit并将StandardInput重定向到StreamWriter,当您可以编写命令时。如果您需要示例代码,请告诉我。 -
@Jimi ...我绝对可以使用示例代码。