c# - 如何从另一个 .NET 程序使用交互式命令行程序

标签 c# .net command-line pipe redirectstandardoutput

我需要为交互式命令行程序编写一个包装器。

这意味着我需要能够通过其标准输入向其他程序发送命令并通过其标准输出接收响应。

问题是,当输入流仍然打开时,标准输出流似乎被阻塞了。一旦我关闭输入流,我就会得到响应。但是我无法发送更多命令。

这是我目前正在使用的(主要来自 here ):

void Main() {
    Process process;
    process = new Process();
    process.StartInfo.FileName = "atprogram.exe";
    process.StartInfo.Arguments = "interactive";

    // Set UseShellExecute to false for redirection.
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.CreateNoWindow = true;

    // Redirect the standard output of the command.  
    // This stream is read asynchronously using an event handler.
    process.StartInfo.RedirectStandardOutput = true;
    // Set our event handler to asynchronously read the output.
    process.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);

    // Redirect standard input as well. This stream is used synchronously.
    process.StartInfo.RedirectStandardInput = true;
    process.Start();

    // Start the asynchronous read of the output stream.
    process.BeginOutputReadLine();

    String inputText;
    do 
    {
        inputText = Console.ReadLine();
        if (inputText == "q")
        {
            process.StandardInput.Close();   // After this line the output stream unblocks
            Console.ReadLine();
            return;
        }
        else if (!String.IsNullOrEmpty(inputText))
        {
            process.StandardInput.WriteLine(inputText);
        }
    }
}

我也尝试过同步读取标准输出流,但结果相同。在输入流关闭之前,对输出流的任何方法调用都会无限期阻塞 - 即使是 Peek()EndOfStream

有没有办法以全双工方式与其他进程通信?

最佳答案

我试图用我自己的一个小测试套件重现您的问题。 我没有使用事件处理程序,而是以我能想到的最简单的方式来做到这一点:同步。这样就不会给问题增加额外的复杂性。

这是我用 Rust 编写的小“echoApp”,只是为了咯咯笑,也有机会遇到永恒的线路终止 war 问题( \n vs \r\r\n)。根据您编写命令行应用程序的方式,这确实可能是您的问题之一。

use std::io;

fn main() {
    let mut counter = 0;
    loop {
        let mut input = String::new();
        let _ = io::stdin().read_line(&mut input);
        match &input.trim() as &str {
            "quit" => break,
            _ => {
                println!("{}: {}", counter, input);
                counter += 1;
            }
        }
    }
}

而且 - 作为一个不喜欢为如此小的测试创建解决方案的懒人,我使用 F# 而不是 C# 作为控制端 - 我认为它很容易阅读:

open System.Diagnostics;

let echoPath = @"E:\R\rustic\echo\echoApp\target\debug\echoApp.exe"

let createControlledProcess path = 
    let p = new Process()
    p.StartInfo.UseShellExecute <- false
    p.StartInfo.RedirectStandardInput <- true
    p.StartInfo.RedirectStandardOutput <- true
    p.StartInfo.Arguments <- ""
    p.StartInfo.FileName <- path
    p.StartInfo.CreateNoWindow <- true
    p

let startupControlledProcess (p : Process) =
    if p.Start() 
    then 
        p.StandardInput.NewLine <- "\r\n"
    else ()

let shutdownControlledProcess (p : Process) =
    p.StandardInput.WriteLine("quit");
    p.WaitForExit()
    p.Close()

let interact (p : Process) (arg : string) : string =
    p.StandardInput.WriteLine(arg);
    let o = p.StandardOutput.ReadLine()
    // we get funny empty lines every other time... 
    // probably some line termination problem ( unix \n vs \r\n etc - 
    // who can tell what rust std::io does...?)
    if o = "" then p.StandardOutput.ReadLine()
    else o

let p = createControlledProcess echoPath
startupControlledProcess p
let results = 
    [
        interact p "Hello"
        interact p "World"
        interact p "Whatever"
        interact p "floats"
        interact p "your"
        interact p "boat"
    ]
shutdownControlledProcess p

在 f# interactive 中执行此操作(在 Visual Studio 中按 CTRL-A ALT-Enter)会产生:

val echoPath : string = "E:\R\rustic\echo\echoApp\target\debug\echoApp.exe"

val createControlledProcess : path:string -> Process

val startupControlledProcess : p:Process -> unit

val shutdownControlledProcess : p:Process -> unit

val interact : p:Process -> arg:string -> string

val p : Process = System.Diagnostics.Process

val results : string list =

["0: Hello"; "1: World"; "2: Whatever"; "3: floats"; "4: your"; "5: boat"]

val it : unit = ()

我无法重现任何阻塞或死锁等。 因此,在您的情况下,我会尝试调查您的 NewLine 属性是否需要一些调整(请参阅函数 startupControlledProcess。如果受控应用程序无法将输入识别为一行,它可能不会响应,仍在等待输入行的其余部分,您可能会得到您想要的效果。

关于c# - 如何从另一个 .NET 程序使用交互式命令行程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45485660/

相关文章:

c# - WPF DocumentViewer - 无需确认即可打印

c# - 如何引用公共(public)部分类 MainWindow : Window from other static class? 的成员

.net - 在生产中使用 Entity Framework (代码优先)迁移

command-line - 如何在 IntelliJ IDEA 中输入命令行参数?

perl - 如何在加载的模块中使用 Smart::Comments 而不更改其源?

c# - 以下 PLINQ 代码没有改进

c# - inetsvr 的 appcmd 在发出后发出错误

c# - C# 中的简单浏览器控件

linux - 如何在连接之前插入新行?

c# - 在 C# 中使用任意位位置和长度跨字节边界提取值