我从网络上的各种来源整理了以下代码,用于通过 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;
行上设置断点,它永远不会到达那里。
谁能帮帮我?我确定我遗漏了什么。
最佳答案
此示例中代码执行内容的简要说明:
首先运行 shell 命令 (cmd.exe
),使用 start/WAIT
作为参数。或多或少与 /k
相同的功能:控制台在没有任何特定任务的情况下启动,在发送命令时等待处理命令。
StandardOutput
, StandardError
和 StandardInput
全部重定向,设置 RedirectStandardOutput , RedirectStandardError和 RedirectStandardInput ProcessStartInfo 的属性为 true
。
控制台输出流在写入时将引发 OutputDataReceived事件;它的内容可以从 DataReceivedEventArgs 的 e.Data
成员中读取。 .
StandardError
将使用其 ErrorDataReceived出于同一目的而举办的事件。
您可以对这两个事件使用一个事件处理程序,但经过一些测试后,您可能会意识到这可能不是一个好主意。将它们分开可以避免一些奇怪的重叠,并可以轻松区分错误和正常输出(请注意,您可以找到写入错误流而不是输出流的程序)。
StandardInput
可以重定向,将其分配给 StreamWriter流。
每次将字符串写入流时,控制台都会将该输入解释为要执行的命令。
此外,进程被指示上升它的 Exited终止事件,设置其 EnableRaisingEvents属性为 true
。
Exited
事件在 Process 关闭时引发,因为处理了 Exit
命令或调用了 .Close()方法(或者,最终是 .Kill() 方法,它应该只在 Process 由于某种原因不再响应时使用)。
由于我们需要将控制台输出传递给一些 UI 控件(本例中为 RichTextBoxes
)并且 Process 事件是在 ThreadPool 线程中引发的,因此我们必须将此上下文与 UI 同步。
这可以使用过程 SynchronizingObject 完成属性,将其设置为父表单或使用 Control.BeginInvoke方法,它将在控件句柄所属的线程上执行委托(delegate)函数。
在这里,一个 MethodInvoker代表委托(delegate)用于此目的。
用于实例化进程并设置其属性和事件处理程序的核心函数:
using System;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
public partial class frmCmdInOut : Form
{
Process cmdProcess = null;
StreamWriter stdin = null;
public frmCmdInOut() => InitializeComponent();
private void MainForm_Load(object sender, EventArgs e)
{
rtbStdIn.Multiline = false;
rtbStdIn.SelectionIndent = 20;
}
private void btnStartProcess_Click(object sender, EventArgs e)
{
btnStartProcess.Enabled = false;
StartCmdProcess();
btnEndProcess.Enabled = true;
}
private void btnEndProcess_Click(object sender, EventArgs e)
{
if (stdin.BaseStream.CanWrite) {
stdin.WriteLine("exit");
}
btnEndProcess.Enabled = false;
btnStartProcess.Enabled = true;
cmdProcess?.Close();
}
private void rtbStdIn_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == (char)Keys.Enter) {
if (stdin == null) {
rtbStdErr.AppendText("Process not started" + Environment.NewLine);
return;
}
e.Handled = true;
if (stdin.BaseStream.CanWrite) {
stdin.Write(rtbStdIn.Text + Environment.NewLine);
stdin.WriteLine();
// To write to a Console app, just
// stdin.WriteLine(rtbStdIn.Text);
}
rtbStdIn.Clear();
}
}
private void StartCmdProcess()
{
var pStartInfo = new ProcessStartInfo {
FileName = "cmd.exe",
// Batch File Arguments = "/C START /b /WAIT somebatch.bat",
// Test: Arguments = "START /WAIT /K ipconfig /all",
Arguments = "START /WAIT",
WorkingDirectory = Environment.SystemDirectory,
// WorkingDirectory = Application.StartupPath,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
};
cmdProcess = new Process {
StartInfo = pStartInfo,
EnableRaisingEvents = true,
// Test without and with this
// When SynchronizingObject is set, no need to BeginInvoke()
//SynchronizingObject = this
};
cmdProcess.Start();
cmdProcess.BeginErrorReadLine();
cmdProcess.BeginOutputReadLine();
stdin = cmdProcess.StandardInput;
// stdin.AutoFlush = true; <- already true
cmdProcess.OutputDataReceived += (s, evt) => {
if (evt.Data != null)
{
BeginInvoke(new MethodInvoker(() => {
rtbStdOut.AppendText(evt.Data + Environment.NewLine);
rtbStdOut.ScrollToCaret();
}));
}
};
cmdProcess.ErrorDataReceived += (s, evt) => {
if (evt.Data != null) {
BeginInvoke(new Action(() => {
rtbStdErr.AppendText(evt.Data + Environment.NewLine);
rtbStdErr.ScrollToCaret();
}));
}
};
cmdProcess.Exited += (s, evt) => {
stdin?.Dispose();
cmdProcess?.Dispose();
};
}
}
由于 StandardInput 已被重定向到 StreamWriter:
stdin = cmdProcess.StandardInput;
我们只是写入 Stream 以执行命令:
stdin.WriteLine(["Command Text"]);
示例表单可以是downloaded from PasteBin .
VB.Net版
控件名称:
rtbStdOut
-> RichTextBox(蓝色背景),接收 StdOut
rtbStdErr
-> RichTextBox(在中间),接收 StdErr
rtbStdIn
-> RichTextBox(底部),写入 StdIn
btnStartProcess
-> 按钮(右侧),启动流程
btnEndProcess
-> 按钮(左侧),停止进程
Download this Form from Google Drive
Imports System.Diagnostics
Imports System.IO
Public Class frmCmdInOut
Private cmdProcess As Process = Nothing
Private stdin As StreamWriter = Nothing
Protected Overrides Sub OnLoad(e As EventArgs)
MyBase.OnLoad(e)
rtbStdIn.Multiline = False
rtbStdIn.SelectionIndent = 20
End Sub
Private Sub btnStartProcess_Click(sender As Object, e As EventArgs) Handles btnStartProcess.Click
btnStartProcess.Enabled = False
StartCmdProcess(Me)
btnEndProcess.Enabled = True
End Sub
Private Sub btnEndProcess_Click(sender As Object, e As EventArgs) Handles btnEndProcess.Click
If stdin.BaseStream IsNot Nothing AndAlso stdin.BaseStream.CanWrite Then stdin.WriteLine("exit")
btnEndProcess.Enabled = False
btnStartProcess.Enabled = True
cmdProcess?.Close()
End Sub
Private Sub rtbStdIn_KeyPress(sender As Object, e As KeyPressEventArgs) Handles rtbStdIn.KeyPress
If e.KeyChar = ChrW(Keys.Enter) Then
If stdin Is Nothing Then
rtbStdErr.AppendText("Process not started" + Environment.NewLine)
Return
End If
e.Handled = True
If stdin.BaseStream.CanWrite Then
stdin.Write(rtbStdIn.Text + Environment.NewLine)
stdin.WriteLine() ' To write to a Console app, just stdin.WriteLine(rtbStdIn.Text);
End If
rtbStdIn.Clear()
End If
End Sub
Private Sub StartCmdProcess(synchObj As Control)
' Arguments = $"start /WAIT cscript.exe script.vbs /xpr",
' Batch File Arguments = "/C START /b /WAIT batchfile.bat",
' Test: Arguments = "START /WAIT /K ipconfig /all",
' start with /U
' StandardErrorEncoding = Encoding.Unicode,
' StandardOutputEncoding = Encoding.Unicode,
Dim pStartInfo = New ProcessStartInfo() With {
.FileName = "cmd.exe",
.Arguments = "START /WAIT",
.CreateNoWindow = True,
.RedirectStandardError = True,
.RedirectStandardInput = True,
.RedirectStandardOutput = True,
.UseShellExecute = False,
.WindowStyle = ProcessWindowStyle.Hidden,
.WorkingDirectory = Application.StartupPath
}
cmdProcess = New Process() With {
.EnableRaisingEvents = True,
.StartInfo = pStartInfo,
.SynchronizingObject = synchObj
}
cmdProcess.Start()
cmdProcess.BeginErrorReadLine()
cmdProcess.BeginOutputReadLine()
stdin = cmdProcess.StandardInput
AddHandler cmdProcess.OutputDataReceived,
Sub(s, evt)
If evt.Data IsNot Nothing Then
rtbStdOut.AppendText(evt.Data + Environment.NewLine)
rtbStdOut.ScrollToCaret()
End If
End Sub
AddHandler cmdProcess.ErrorDataReceived,
Sub(s, evt)
If evt.Data IsNot Nothing Then
rtbStdErr.AppendText(evt.Data + Environment.NewLine)
rtbStdErr.ScrollToCaret()
End If
End Sub
AddHandler cmdProcess.Exited,
Sub(s, evt)
stdin?.Dispose()
cmdProcess?.Dispose()
End Sub
End Sub
End Class
关于c# - 如何让命令的输出实时显示在窗体的控件中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51680382/