子プロセスの標準出力を取得する方法

この記事ではコンソールプログラム A から子プログラム B を起動して、B が標準出力に出力した内容を A で受け取る方法について説明します。

標準出力とは何か?ということについては、「「標準出力」「標準エラー出力」とは?」で説明しています。

実際にプログラムを作りながら説明します。

まずは子プログラム B となるコードは次の通りです。Visual Studio ではコンソールプログラムプロジェクトとして、 app1.exe を作ります。

using System;

namespace app1
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                for (var i = 0; i < 5; i++)
                {
                    Console.WriteLine("[app1] Hello! {0}", i + 1);
                    System.Threading.Thread.Sleep(1000);
                }

                throw new Exception("[app1] Hello! Exception!");
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex);
                Console.Error.WriteLine(ex.StackTrace);
            }
        }
    }
}

このプログラムはどうやって起動されたかは全く意識していません。単に標準出力に文字を出力しているだけです。 ついでに標準エラーにも出力してます。

これをそのまま実行すると、次のような出力になります。

> app1
[app1] Hello! 1
[app1] Hello! 2
[app1] Hello! 3
[app1] Hello! 4
[app1] Hello! 5
System.Exception: [app1] Hello! Exception!
   at app1.Program.Main(String[] args) 
   at app1.Program.Main(String[] args)

標準出力に Hello!... という出力を、標準エラー出力で例外のメッセージとスタックトレースを表示しています。

このプログラムを他のプログラムから起動して、これらの出力を受け取る場合にはどうしたら良いでしょうか。

子プロセスの起動には System.Diagnostics.Process.Start が使えます。

このメソッドのパラメータに ProcessStartInfo オブジェクトを渡しますが、 UseShellExecute フィールドを false にすることで、 コマンドウィンドウを共有できます。

言い換えると、UseShellExecutetrue とすると、 それぞれのプログラムが別々のウィンドウで実行されます。既定で true です。

UseShellExecutetrue にして子プロセスを起動すると、 子プロセスに対してコンソールホストが起動され、それが出力を受け取ります。

UseShellExecutefalse にして子プロセスを起動すると、 既存のコンソールホストが親プロセスと共有されます。

従って、app2.exe を次のようにして app1.exe を起動すると、 app1.exe からの出力と app2.exe からの出力は同じウィンドウに表示されます。

using System;
using System.Diagnostics;

namespace app2
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("[app2] Start");
            
            try
            {
                var startInfo = new ProcessStartInfo()
                {
                    FileName = @"app1.exe",
                    UseShellExecute = false,
                };

                using (var process = Process.Start(startInfo))
                {
                    process.WaitForExit();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            Console.WriteLine("[app2] Exit");
        }
    }
}

この結果は次のようになります。

> app2
[app2] Start
[app1] Hello! 1
[app1] Hello! 2
[app1] Hello! 3
[app1] Hello! 4
[app1] Hello! 5
System.Exception: [app1] Hello! Exception!
   at app1.Program.Main(String[] args)
   at app1.Program.Main(String[] args)
[app2] Exit

親プロセスで出力を取得

さて、ここまでで標準出力を親子で同じコンソールウィンドウに出力できました。

それでは、親プロセスで、子プロセスからの出力を取得するにはどうしたらよいでしょうか。もし、子プロセスからの出力を受けとることができれば、 それをログファイルに出力したり、加工するなりできます。

子プロセスの出力を親側で取得するには次のようにします。

using System;
using System.Diagnostics;

namespace app3
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("[app3] Start");

            try
            {
                var startInfo = new ProcessStartInfo()
                {
                    FileName = @"app1.exe",
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true
                };

                using (var process = Process.Start(startInfo))
                {
                    var so = process.StandardOutput.ReadToEnd();
                    var se = process.StandardError.ReadToEnd();

                    process.WaitForExit();

                    Console.WriteLine(so);
                    Console.WriteLine("[app3] ------");
                    Console.WriteLine(se);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            Console.WriteLine("[app3] Exit");
        }
    }
}

親プロセスで子プロセスの標準出力を受けとるには、ProcessStartInfoRedirectStandardOutput フィールドを true にセットします。

ここでは標準エラーも取得するので RedirectStandardErrortrue にセットしています。

そして、process.StandardOutput.ReadToEnd メソッドで標準出力を「最後まで」読み取ります。「最後まで」というのは、「子プロセスが終了するまで」ということです。

ReadToEnd メソッドは同期呼び出しです。メソッドが終わるまで、親プロセス側はブロックします。

ここでは親プロセス(呼出側)もコンソールプログラムで、呼び出しがブロックすることはあまり問題になりませんが、 呼出元が GUI をもつウィンドウであると、ハングアップしたようになってしまうので、非同期版の呼び出しを使うか、 ワーカースレッドを作ってそちらで処理をする等の対策が必要です。

上記のプログラム app3.exe の実行結果は次の通りです。

> app3
[app3] Start # ここで app1.exe の終了を待つ
[app1] Hello! 1
[app1] Hello! 2
[app1] Hello! 3
[app1] Hello! 4
[app1] Hello! 5

[app3] ------
System.Exception: [app1] Hello! Exception!
   at app1.Program.Main(String[] args)
   at app1.Program.Main(String[] args)

[app3] Exit

確かに標準出力及び標準エラーからの出力を受け取れていることがわかります。

以上、子プロセスの標準出力(及び標準エラー)の内容を親プロセスで受け取る方法を示しました。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 C# 入門