x0y14
@x0y14

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

[C#] Processを使用してPythonの対話モードを起動し、内容を出力したい。

解決したいこと

C#でProcessを使い、標準出力、標準エラーを出力したい。

  • 何が起きてる(簡単に)
    C#で作った自作のシェルでPython3を対話モードで実行した際、入力待ち表示>>>(標準エラー)がリアルタイムで出力されず、quit()またはCtrl+Dで対話モードを終了した後に一気に流れてくる。

  • 前提
    C#を使ってZshやbashのようなシェルソフトを作ってみたいと考えています。
    現在は、C#のProcessを使用して外部ソフトを起動し、ソフトからの標準出力、標準エラーをConsole.WriteLine()を使用し、表示することに取り組んでいます。

    質問者はOSや、CPU、低レイヤ、標準入力、標準出力、標準エラー、Unix等、聞いたことがある程度しか知らない、そして、bashやZshのソースコードを読んだことがない。

  • 実行環境
    パソコン: MacBookPro 2017年モデル
    プログラム言語: C#、Python3.9.2
    フレームワーク: .NET5
    エディタ: JetBrains Rider(EAP)
    ターミナル: ターミナル.app(macos標準)、Zsh

発生している問題・エラー

C#で作った自作のシェルでPython3を対話モードで実行した際、入力待ち表示>>>(標準エラー)がリアルタイムで出力されず、quit()またはCtrl+Dで対話モードを終了した後に一気に流れてくる。
対話モードに対し、自分で入力したもの、実行結果はリアルタイムで表示される。
最後に詰まったように出て来る>>>の数は、対話モードで何かを入力した回数と一致している。

  • 期待するもの
    スクリーンショット 2021-02-28 16.37.34.png

  • 現在自作シェルで発生しているもの
    スクリーンショット 2021-02-28 16.39.06.png
    ※自作シェルでのPythonの出力の前に表示されているものは以下を表します。

  • s|: C#のProcessのOutputDataReceivedで取得されたもの

  • e|: C#のProcessのErrorDataReceivedで取得されたもの
    ※自作シェルでの出力の最後に表示されているexitedはC#のProcessのExitedが実行された時に意図的に出力しているものです。

該当するソースコード

public string ExecCommand(string userInput)
        {
            var processInfo = new ProcessStartInfo(userInput, "");

            processInfo.RedirectStandardError = true;
            processInfo.RedirectStandardOutput = true;
            processInfo.CreateNoWindow = true;
            processInfo.UseShellExecute = false;

            using var proc = new Process();
            proc.EnableRaisingEvents = true;
            proc.StartInfo = processInfo;

            proc.OutputDataReceived += (sender, ev) =>
            {
                if (!String.IsNullOrEmpty(ev.Data))
                    Console.WriteLine($"s| {ev.Data}");
            };
            proc.ErrorDataReceived += (sender, ev) =>
            {
                if (!String.IsNullOrEmpty(ev.Data))
                    Console.WriteLine($"e| {ev.Data}");
            };
            proc.Exited += (sender, ev) =>
            {
                Console.WriteLine("exited");
                // プロセスが終了すると呼ばれる
            };
            // プロセスの開始
            proc.Start();
            // 非同期出力読出し開始
            proc.BeginOutputReadLine();
            proc.BeginErrorReadLine();
            // 終了まで待つ
            // ctoken.Token.WaitHandle.WaitOne();
            proc.WaitForExit();

            return "";
        }

実際にはuserInputに"python3"が入力されており、自作シェルの機能で、/usr/bin/から該当のコマンドに対応する実行ファイルを呼び出す作業を行なっています。

なお、このソースは以下の記事を参考にさせていただきました。

※上に表示されているものは該当部分だけを抜き出しています。全体はこちらをご覧ください。

自分で試したこと

  1. Processについて調べ、ReadToEnd等でls等のシンプルなコマンドの結果を表示できるようにした。
  2. 参考にさせていただいたサイトに記載されていたものをそのまま書き写した。
  3. 期待したものができなかったので、参考にさせていただいたサイトが使っていたCancellationTokenSourceを使わないように書き換えた。

時間がございましたら、ご指摘いただけると嬉しいです。
長々と失礼いたしました。

0

1Answer

Process.ErrorDataReceived Event
の翻訳

ErrorDataReceivedイベントは、関連付けられたプロセスが、リダイレクトされたStandardErrorストリームに改行 (CR (CR)、ライン フィード (LF)、または CR+LF で終了する行を書き込んだことを示します。

プロンプトは入力待ちカーソルを同じ行に出すために改行なしで出力されます。そのためErrorDataReceivedイベントが発生してないようですね。

「入力待ち状態になったらその時点までのstderrの内容を出力」とするくらいしかないかもしれません。

// プロセスの開始
proc.Start();
// 非同期出力読出し開始
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();//プロンプト以外(末尾改行あり)のstderrを処理するのはこちら

while (proc.WaitForInputIdle())//プロセス終了だとfalseを返すんじゃないかな…(希望的観測)
{
    //入力待ち状態になったらstderrを出力
    Console.Write(proc.StandardError.ReadToEnd());
}
//プロセス終了時の後始末。なくてもよさそうだけど念のため取りこぼしを防ぐ
Console.Write(proc.StandardError.ReadToEnd());

まったくテストしてないのでうまく行くかわかりませんが、アプローチとしてはこんな感じだと思います。
ちなみにWaitForInputIdleはキャンセレーショントークンを引数として受け取るバージョンがないため、対応させようとするとかなりめんどくさくなりそうです。

0Like

Your answer might help someone💌