Processクラスを使うことで子プロセスを起動することができ、起動した子プロセスの標準ストリーム(standard streams)を扱うこともできます。 子プロセスの標準入力(standard input, stdin
)や標準出力(standard output, stdout
)などの標準ストリームを扱う場合は、ProcessStartInfoクラスで標準ストリームをリダイレクトするためのオプションを指定して子プロセスを起動します。
具体的には、RedirectStandardInput/RedirectStandardOutputなどのプロパティをtrue
にすることで、子プロセスの標準ストリームをリダイレクトさせます。 これにより、親プロセス側から子プロセスの標準入力へ書き込む/標準出力を読み込むことができます。 .NET Frameworkでの場合、標準ストリームをリダイレクトするにはデフォルトでtrue
になっているUseShellExecuteプロパティもfalse
にする必要があります。
リダイレクトした標準ストリームは、起動した子プロセス(Process)のStandardInput/StandardOutputなどのプロパティを介して操作することができます。 StandardInput/StandardOutputプロパティはStreamWriter/StreamReaderとなっているため、WriteLine/ReadLineといったメソッドにより標準入出力への読み書きを行います。 (§.標準入力への書き込み、§.標準出力・標準エラーからの読み込み)
また、標準出力と同様にRedirectStandardErrorで標準エラー(standard error, stderr
)をリダイレクトすることにより、StandardErrorプロパティから子プロセスの標準エラーを読み取ることもできます。
ここでは、子プロセスを起動し、標準ストリームを扱う方法について解説します。 子プロセスの起動方法、および起動に際した標準ストリーム以外のオプションについてはプロセス §.子プロセスの起動オプションを、特に子プロセスのコマンドライン引数についてはプロセス §.子プロセスのコマンドライン引数を参照してください。 自プロセスの標準ストリームについては自プロセスの標準入出力を参照してください。
標準入力への書き込み
子プロセスの標準入力への書き込みを行うには、ProcessStartInfoのRedirectStandardInputプロパティをtrue
にした上で子プロセスを起動します。 次に、起動した子プロセスのStandardInputプロパティに設定されるStreamWriterを取得します。 これに対して書き込みを行うことにより、子プロセスの標準入力へ書き込むことができます。
例として、親プロセスparent.exeから子プロセスchild.exeを起動し、子プロセスの標準入力にテキストを書き込む場合は次のようになります。
using System;
using System.Diagnostics;
class Sample {
static void Main()
{
// 子プロセスchild.exeの起動オプション
var psi = new ProcessStartInfo("child.exe") {
// シェルを使用せず子プロセスを起動する
// (リダイレクトするために必要)
// ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっている
UseShellExecute = false,
// 起動した子プロセスの標準入力をリダイレクトする
RedirectStandardInput = true,
};
// 子プロセスを起動する
using (var child = Process.Start(psi)) {
// リダイレクトされた子プロセスの標準入力を取得する
using (var stdin = child.StandardInput) {
// 標準入力にテキストを書き込む
stdin.WriteLine("line1");
stdin.WriteLine("line2");
stdin.WriteLine("line3");
} // 標準入力を閉じて書き込みを終了する
// 子プロセスの終了を待機する
child.WaitForExit();
}
}
}
using System;
class Sample {
static void Main()
{
var lineNumber = 0;
while (true) {
// 標準入力から1行読み込む
var line = Console.ReadLine();
if (line == null)
break; // これ以上読み込めない場合は終了
// 読み込んだ行に行番号を付けて標準出力に書き込む
Console.WriteLine($"{++lineNumber}: {line}");
}
}
}
Imports System
Imports System.Diagnostics
Imports System.IO
Class Sample
Shared Sub Main()
' 子プロセスchild.exeの起動オプション
Dim psi As New ProcessStartInfo("child.exe")
' シェルを使用せず子プロセスを起動する
' (リダイレクトするために必要)
' ⚠.NET Core/.NET 5以降ではデフォルトでFalseとなっている
psi.UseShellExecute = False
' 起動した子プロセスの標準入力をリダイレクトする
psi.RedirectStandardInput = True
' 子プロセスを起動する
Using child As Process = Process.Start(psi)
' リダイレクトされた子プロセスの標準入力を取得する
Using stdin As StreamWriter = child.StandardInput
' 標準入力にテキストを書き込む
stdin.WriteLine("line1")
stdin.WriteLine("line2")
stdin.WriteLine("line3")
End Using ' 標準入力を閉じて書き込みを終了する
' 子プロセスの終了を待機する
child.WaitForExit()
End Using
End Sub
End Class
Imports System
Class Sample
Shared Sub Main()
Dim lineNumber As Integer = 0
Do
' 標準入力から1行読み込む
Dim line As String = Console.ReadLine()
' これ以上読み込めない場合は終了
If line Is Nothing Then Exit Do
' 読み込んだ行に行番号を付けて標準出力に書き込む
lineNumber += 1
Console.WriteLine($"{lineNumber}: {line}")
Loop
End Sub
End Class
>parent.exe
1: line1
2: line2
3: line3
このようにして子プロセスの標準入力に書き込みを行うことができます。 子プロセスの標準入力はStreamWriterとして取得できるため、ファイル等への書き込みと同様にStreamWriterのメソッドを使って書き込みを行うことができます。
なお、この例でのchild.exeは標準入力から読み込めなくなるまで読み込みを続けるようになっています。 標準入力の末尾(EOF)に到達するまで読み込み続けるようなプロセスを起動する場合、親プロセスは子プロセスの終了を待機する一方、子プロセスは標準入力が閉じられるまで読み込みを続けようとし、互いに待機状態となったまま処理が進行しないという状況が起こり得ます。 このため親プロセス側では、子プロセスの終了を待機する前にusingステートメントやCloseメソッドの呼び出しによって標準入力を閉じる必要があります。
標準入力を含め標準ストリームをリダイレクトするためにはProcessStartInfo.UseShellExecuteがfalse
である必要があります。 .NET Core/.NET 5以降ではデフォルトでfalse
となっている一方、.NET Frameworkではtrue
となっているため、明示的に指定する必要があります。
子プロセスを起動する際にコマンドライン引数を指定する方法についてはプロセス §.子プロセスのコマンドライン引数を、その他起動時のオプションについてはプロセス §.子プロセスの起動およびプロセス §.子プロセスの起動オプションを参照してください。
標準出力・標準エラーからの読み込み
子プロセスの標準出力からの読み込みを行う場合は、ProcessStartInfoのRedirectStandardOutpoutプロパティをtrue
にして子プロセスを起動します。 次に、起動した子プロセスのStandardOutputプロパティに設定されるStreamReaderを取得します。 これに対して読み込みを行うことにより、子プロセスの標準出力を読み込むことができます。
標準エラーの場合も同様に、RedirectStandardErrorプロパティをtrue
にし、StandardErrorプロパティのStreamReaderから読み込みます。
例として、親プロセスparent.exeから子プロセスchild.exeを起動し、子プロセスの標準出力・標準エラーからテキストを読み込む場合は次のようになります。
using System;
using System.Diagnostics;
class Sample {
static void Main()
{
// 子プロセスchild.exeの起動オプション
var psi = new ProcessStartInfo("child.exe") {
// シェルを使用せず子プロセスを起動する
// (リダイレクトするために必要)
// ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっている
UseShellExecute = false,
// 起動した子プロセスの標準出力・標準エラーをリダイレクトする
RedirectStandardOutput = true,
RedirectStandardError = true,
};
// 子プロセスを起動する
using (var child = Process.Start(psi)) {
string output = null;
string error = null;
// リダイレクトされた子プロセスの標準出力を取得する
using (var stdout = child.StandardOutput)
using (var stderr = child.StandardError) {
// 標準出力に書き込まれたテキストをすべて読み込む
output = stdout.ReadToEnd();
// 標準エラーに書き込まれたテキストをすべて読み込む
error = stderr.ReadToEnd();
} // 標準出力・標準エラーを閉じて読み込みを終了する
// 子プロセスの終了を待機する
child.WaitForExit();
// 読み込んだテキストを表示する
Console.WriteLine($"output: \"{output}\"");
Console.WriteLine($"error: \"{error}\"");
}
}
}
using System;
class Sample {
static void Main()
{
// 標準出力・標準エラーにテキストを書き込む
Console.Write("Hello, ");
Console.WriteLine("stdout!");
Console.Error.WriteLine("Hello, stderr!");
Console.WriteLine("Hello, stdout!");
}
}
Imports System
Imports System.Diagnostics
Imports System.IO
Class Sample
Shared Sub Main()
' 子プロセスchild.exeの起動オプション
Dim psi As New ProcessStartInfo("child.exe")
' シェルを使用せず子プロセスを起動する
' (リダイレクトするために必要)
' ⚠.NET Core/.NET 5以降ではデフォルトでFalseとなっている
psi.UseShellExecute = False
' 起動した子プロセスの標準出力・標準エラーをリダイレクトする
psi.RedirectStandardOutput = True
psi.RedirectStandardError = True
' 子プロセスを起動する
Using child As Process = Process.Start(psi)
Dim output As String = Nothing
Dim err As String = Nothing
' リダイレクトされた子プロセスの標準出力を取得する
Using stdout As StreamReader = child.StandardOutput, _
stderr As StreamReader = child.StandardError
' 標準出力に書き込まれたテキストをすべて読み込む
output = stdout.ReadToEnd()
' 標準エラーに書き込まれたテキストをすべて読み込む
err = stderr.ReadToEnd()
End Using ' 標準出力・標準エラーを閉じて読み込みを終了する
' 子プロセスの終了を待機する
child.WaitForExit()
' 読み込んだテキストを表示する
Console.WriteLine($"output: ""{output}""")
Console.WriteLine($"err: ""{err}""")
End Using
End Sub
End Class
Imports System
Class Sample
Shared Sub Main()
' 標準出力・標準エラーにテキストを書き込む
Console.Write("Hello, ")
Console.WriteLine("stdout!")
Console.Error.WriteLine("Hello, stderr!")
Console.WriteLine("Hello, stdout!")
End Sub
End Class
>parent.exe
output: "Hello, stdout!
Hello, stdout!
"
err: "Hello, stderr!
"
このようにして子プロセスの標準出力・標準エラーから読み込みを行うことができます。 子プロセスの標準出力・標準エラーはStreamReaderとして取得できるため、ファイル等からの読み込みと同様にStreamReaderのメソッドを使って読み込みを行うことができます。
標準出力・標準エラーを含め標準ストリームをリダイレクトするためにはProcessStartInfo.UseShellExecuteがfalse
である必要があります。 .NET Core/.NET 5以降ではデフォルトでfalse
となっている一方、.NET Frameworkではtrue
となっているため、明示的に指定する必要があります。
子プロセスを起動する際にコマンドライン引数を指定する方法についてはプロセス §.子プロセスのコマンドライン引数を、その他起動時のオプションについてはプロセス §.子プロセスの起動およびプロセス §.子プロセスの起動オプションを参照してください。
標準入出力への読み書き
子プロセスの標準入力のリダイレクトと標準出力・標準エラーのリダイレクトは同時に組み合わせることもできます。 これにより、子プロセスの標準入力に書き込み、子プロセスで何らかの処理を行った結果を標準出力から読み込む、さらに子プロセスの標準エラー出力も読み込む、といったことができます。
using System;
using System.Diagnostics;
class Sample {
static void Main()
{
// 子プロセスchild.exeの起動オプション
var psi = new ProcessStartInfo("child.exe") {
// シェルを使用せず子プロセスを起動する
// (リダイレクトするために必要)
// ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっている
UseShellExecute = false,
// 起動した子プロセスの標準入出力をリダイレクトする
RedirectStandardInput = true,
RedirectStandardOutput = true,
};
// 子プロセスを起動する
using (var child = Process.Start(psi)) {
string output = null;
// リダイレクトされた子プロセスの標準入出力を取得する
using (var stdin = child.StandardInput)
using (var stdout = child.StandardOutput) {
// 子プロセスの標準入力に書き込む
stdin.WriteLine("hello, world!");
// 子プロセスの標準出力から読み込む
output = stdout.ReadLine();
} // 子プロセスの標準入出力を閉じて読み書きを終了する
// 子プロセスの終了を待機する
child.WaitForExit();
// 読み込んだテキストを表示する
Console.WriteLine($"output: \"{output}\"");
}
}
}
using System;
class Sample {
static void Main()
{
// 標準入力から読み込む
var line = Console.ReadLine();
// 標準出力へ書き込む
Console.WriteLine(line.ToUpper()); // 入力を大文字化して出力
}
}
Imports System
Imports System.Diagnostics
Imports System.IO
Class Sample
Shared Sub Main()
' 子プロセスchild.exeの起動オプション
Dim psi As New ProcessStartInfo("child.exe")
' シェルを使用せず子プロセスを起動する
' (リダイレクトするために必要)
' ⚠.NET Core/.NET 5以降ではデフォルトでFalseとなっている
psi.UseShellExecute = False
' 起動した子プロセスの標準入出力をリダイレクトする
psi.RedirectStandardInput = True
psi.RedirectStandardOutput = True
' 子プロセスを起動する
Using child As Process = Process.Start(psi)
Dim output As String = Nothing
' リダイレクトされた子プロセスの標準入出力を取得する
Using stdin As StreamWriter = child.StandardInput, _
stdout As StreamReader = child.StandardOutput
' 子プロセスの標準入力に書き込む
stdin.WriteLine("hello, world!")
' 子プロセスの標準出力から読み込む
output = stdout.ReadLine()
End Using ' 子プロセスの標準入出力を閉じて読み書きを終了する
' 子プロセスの終了を待機する
child.WaitForExit()
' 読み込んだテキストを表示する
Console.WriteLine($"output: ""{output}""")
End Using
End Sub
End Class
Imports System
Class Sample
Shared Sub Main()
' 標準入力から読み込む
Dim line As String = Console.ReadLine()
' 標準出力へ書き込む
Console.WriteLine(line.ToUpper()) ' 入力を大文字化して出力
End Sub
End Class
>parent.exe
output: "HELLO, WORLD!"
さらに、自プロセスの標準ストリームの入出力先を子プロセスの標準ストリームへリダイレクトすることにより、子プロセスの標準ストリームとパイプすることもできます。
自プロセスと子プロセスの標準ストリームをパイプする
ConsoleクラスのSetIn・SetOut・SetErrorの各メソッドを使うことにより、自プロセスの標準ストリームをリダイレクトすることができます。 (自プロセスの標準入出力 §.標準ストリームのリダイレクト) これと子プロセスの標準ストリームのリダイレクトを組み合わせることにより、自プロセスと子プロセスの標準ストリームをパイプ(pipe)することができます。
標準ストリームをパイプするとは、自プロセスの標準出力の出力先を子プロセスの標準入力の入力元とする、また逆に子プロセスの標準出力の出力先を自プロセスの標準入力の入力元とすることで、これによりプロセス間で標準ストリームを介したやりとりをすることができます。
自プロセス | ⇄ | 子プロセス |
---|---|---|
標準出力 Console.Out |
→ | 標準入力 Console.In |
標準入力 Console.In |
← | 標準出力 Console.Out |
using System;
using System.Diagnostics;
class Sample {
static void Main()
{
// 子プロセスchild.exeの起動オプション
var psi = new ProcessStartInfo("child.exe") {
// シェルを使用せず子プロセスを起動する
// (リダイレクトするために必要)
// ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっている
UseShellExecute = false,
// 起動した子プロセスの標準入出力をリダイレクトする
RedirectStandardInput = true,
RedirectStandardOutput = true,
};
// 子プロセスを起動する
using (var child = Process.Start(psi)) {
string output = null;
// 初期状態での親プロセスの標準入出力を退避しておく
var initialIn = Console.In;
var initialOut = Console.Out;
// リダイレクトされた子プロセスの標準入出力を取得する
using (var stdin = child.StandardInput)
using (var stdout = child.StandardOutput) {
// 親プロセスの標準出力を子プロセスの標準入力へリダイレクトする
Console.SetOut(stdin);
// 親プロセスの標準入力を子プロセスの標準出力へリダイレクトする
Console.SetIn(stdout);
// 親プロセスの標準出力へ書き込む
// →リダイレクトした子プロセスの標準入力に書き込まれる
Console.WriteLine("hello, world!");
// 親プロセスの標準入力から読み込む
// →リダイレクトした子プロセスの標準出力から読み込まれる
output = Console.ReadLine();
// 親プロセスの標準入出力を初期状態に元に戻す
Console.SetIn(initialIn);
Console.SetOut(initialOut);
} // 子プロセスの標準入出力を閉じて読み書きを終了する
// 子プロセスの終了を待機する
child.WaitForExit();
// 読み込んだテキストを表示する
// (初期状態に戻した標準出力へ書き込む)
Console.WriteLine($"output: \"{output}\"");
}
}
}
using System;
class Sample {
static void Main()
{
// 標準入力から読み込む
// →親プロセスの標準出力に書き込まれた内容が読み込まれる
var line = Console.ReadLine();
// 標準出力へ書き込む
// →親プロセスの標準入力で読み込まれる内容として書き込まれる
Console.WriteLine(line.ToUpper()); // 入力を大文字化して出力
}
}
Imports System
Imports System.Diagnostics
Imports System.IO
Class Sample
Shared Sub Main()
' 子プロセスchild.exeの起動オプション
Dim psi As New ProcessStartInfo("child.exe")
' シェルを使用せず子プロセスを起動する
' (リダイレクトするために必要)
' ⚠.NET Core/.NET 5以降ではデフォルトでFalseとなっている
psi.UseShellExecute = False
' 起動した子プロセスの標準入出力をリダイレクトする
psi.RedirectStandardInput = True
psi.RedirectStandardOutput = True
' 子プロセスを起動する
Using child As Process = Process.Start(psi)
Dim output As String = Nothing
' 初期状態での親プロセスの標準入出力を退避しておく
Dim initialIn As TextReader = Console.In
Dim initialOut As TextWriter = Console.Out
' リダイレクトされた子プロセスの標準入出力を取得する
Using stdin As StreamWriter = child.StandardInput, _
stdout As StreamReader = child.StandardOutput
' 親プロセスの標準出力を子プロセスの標準入力へリダイレクトする
Console.SetOut(stdin)
' 親プロセスの標準入力を子プロセスの標準出力へリダイレクトする
Console.SetIn(stdout)
' 親プロセスの標準出力へ書き込む
' →リダイレクトした子プロセスの標準入力に書き込まれる
Console.WriteLine("hello, world!")
' 親プロセスの標準入力から読み込む
' →リダイレクトした子プロセスの標準出力から読み込まれる
output = Console.ReadLine()
' 親プロセスの標準入出力を初期状態に元に戻す
Console.SetIn(initialIn)
Console.SetOut(initialOut)
End Using ' 子プロセスの標準入出力を閉じて読み書きを終了する
' 子プロセスの終了を待機する
child.WaitForExit()
' 読み込んだテキストを表示する
' (初期状態に戻した標準出力へ書き込む)
Console.WriteLine($"output: ""{output}""")
End Using
End Sub
End Class
Imports System
Class Sample
Shared Sub Main()
' 標準入力から読み込む
' →親プロセスの標準出力に書き込まれた内容が読み込まれる
Dim line As String = Console.ReadLine()
' 標準出力へ書き込む
' →親プロセスの標準入力で読み込まれる内容として書き込まれる
Console.WriteLine(line.ToUpper()) ' 入力を大文字化して出力
End Sub
End Class
>parent.exe
output: "HELLO, WORLD!"
Console.SetIn/SetOut/SetErrorの各メソッドを使って自プロセスの標準ストリームをリダイレクトすると、Consoleクラスでの入出力先はリダイレクト先へと変更されます。 Consoleクラスのデフォルトの入出力ストリームは、プロセスを起動したターミナルやコマンドプロンプト、IDEの出力ウィンドウなどに接続されているため、標準出力・標準エラーをリダイレクトすると以降そこには一切出力されなくなります。
イベントハンドラを使った標準出力の非同期読み込み
Process.BeginOutputReadLine/BeginErrorReadLineメソッドを使うと、標準出力・標準エラーに出力されるたびにイベントを発生させることができ、イベントハンドラによって非同期的に読み込むことができます。
このメソッドを呼び出すと、標準出力・標準エラーに書き込まれた内容はOutputDataReceivedイベント/ErrorDataReceivedイベントとして通知されます。 このイベントは、開始メソッドの名称Begin*ReadLineが示すように、標準出力・標準エラーから1行読み込まれるごとに(改行文字が出力されるたびに)発生します。 また、Processクラスのイベントであるため、ProcessStartInfoクラスで起動オプションと同時にイベントハンドラを設定することはできません。
イベントハンドラとなるDataReceivedEventHandlerデリゲート型では、DataReceivedEventArgs.Dataプロパティを参照することにより、標準出力・標準エラーに出力された内容を得ることができます。 イベントは1行ごとに発生するため、DataReceivedEventArgs.Dataプロパティからは改行文字を除いた1行分の内容が取得されます。
子プロセスが終了して標準出力・標準エラーが閉じられた場合は、Dataプロパティにnull
/Nothing
が設定された状態でイベントが発生するため、これを元にデータの終端あるいは子プロセスの終了を検知することができます。 なお、イベントハンドラの引数sender
には子プロセスのProcessが渡されます。
BeginOutputReadLine/BeginErrorReadLineを使った非同期読み込みは、同期的に読み込む場合と同様にProcessStartInfo.RedirectStandardOutput/ RedirectStandardErrorにtrue
を指定して子プロセスを起動します。 また、子プロセスを起動したあと、読み込みを開始する時点でProcess.BeginOutputReadLine/BeginErrorReadLineメソッドを呼び出します。
一度非同期的な読み込みを開始すると、以降はStandardOutput/StandardErrorを使った同期的な読み込みは例外InvalidOperationExceptionをスローするようになり、一切できなくなります。
using System;
using System.Diagnostics;
class Sample {
static void Main()
{
// 子プロセスを起動するためのProcessを作成
using (var child = new Process()) {
// child.exeを子プロセスとして起動する
child.StartInfo.FileName = "child.exe";
// シェルを使用せず子プロセスを起動する
// (リダイレクトするために必要)
// ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっている
child.StartInfo.UseShellExecute = false;
// 起動した子プロセスの標準出力・標準エラーをリダイレクトする
child.StartInfo.RedirectStandardOutput = true;
child.StartInfo.RedirectStandardError = true;
// リダイレクトした標準出力の内容を受信するイベントハンドラを設定する
child.OutputDataReceived += (sender, e) => {
// 子プロセスの標準出力から受信した内容を自プロセスの標準出力に書き込む
Console.WriteLine($"<stdout> {(e.Data ?? "(end of stream)")}");
};
// リダイレクトした標準エラーの内容を受信するイベントハンドラを設定する
child.ErrorDataReceived += (sender, e) => {
// 子プロセスの標準エラーから受信した内容を自プロセスの標準エラーに書き込む
Console.WriteLine($"<stderr> {(e.Data ?? "(end of stream)")}");
};
// プロセスを起動する
child.Start();
// 標準出力・標準エラーの非同期読み込みを開始する
child.BeginOutputReadLine();
child.BeginErrorReadLine();
// 子プロセスの終了を最大250ミリ秒待機する
while (!child.WaitForExit(250)) {
// 終了していない場合は、待機中のメッセージを表示する
Console.WriteLine("(waiting)");
}
}
}
}
using System;
class Sample {
static void Main()
{
// 100ミリ秒おきに、標準出力と標準エラーに出力する
for (var i = 0; i < 6; i++) {
if (i % 2 == 0)
Console.Error.WriteLine("Hello, stderr!");
else
Console.WriteLine("Hello, stdout!");
System.Threading.Thread.Sleep(100);
}
}
}
Option Infer On
Imports System
Imports System.Diagnostics
Imports System.IO
Class Sample
Shared Sub Main()
' 子プロセスを起動するためのProcessを作成
Using child As New Process
' child.exeを子プロセスとして起動する
child.StartInfo.FileName = "child.exe"
' シェルを使用せず子プロセスを起動する
' (リダイレクトするために必要)
' ⚠.NET Core/.NET 5以降ではデフォルトでFalseとなっている
child.StartInfo.UseShellExecute = False
' 起動した子プロセスの標準出力・標準エラーをリダイレクトする
child.StartInfo.RedirectStandardOutput = True
child.StartInfo.RedirectStandardError = True
' リダイレクトした標準出力の内容を受信するイベントハンドラを設定する
AddHandler child.OutputDataReceived, Sub(sender, e)
' 子プロセスの標準出力から受信した内容を自プロセスの標準出力に書き込む
Console.WriteLine($"<stdout> {If(e.Data Is Nothing, "(end of stream)", e.Data)}")
End Sub
' リダイレクトした標準エラーの内容を受信するイベントハンドラを設定する
AddHandler child.ErrorDataReceived, Sub(sender, e)
' 子プロセスの標準出力から受信した内容を自プロセスの標準出力に書き込む
Console.WriteLine($"<stderr> {If(e.Data Is Nothing, "(end of stream)", e.Data)}")
End Sub
' プロセスを起動する
child.Start()
' 標準出力・標準エラーの非同期読み込みを開始する
child.BeginOutputReadLine()
child.BeginErrorReadLine()
' 子プロセスの終了を待機する
Do Until child.WaitForExit(250)
' 終了するまでの間、250ミリ秒毎に待機中のメッセージを表示する
Console.WriteLine("(waiting)")
Loop
End Using
End Sub
End Class
Imports System
Class Sample
Shared Sub Main()
' 100ミリ秒おきに、標準出力と標準エラーに出力する
For i As Integer = 0 To 5
If i Mod 2 = 0 Then
Console.Error.WriteLine("Hello, stderr!")
Else
Console.WriteLine("Hello, stdout!")
End If
System.Threading.Thread.Sleep(100)
Next
End Sub
End Class
>parent.exe
<stderr> Hello, stderr!
<stdout> Hello, stdout!
<stderr> Hello, stderr!
(waiting)
<stdout> Hello, stdout!
<stderr> Hello, stderr!
(waiting)
<stdout> Hello, stdout!
<stdout> (end of stream)
<stderr> (end of stream)
一度開始した非同期的に読み込みは、CancelOutputRead/CancelErrorReadメソッドを呼び出すことで中止することができます。 また、中止したあとに再度BeginOutputReadLine/BeginErrorReadLineメソッドを呼び出すことで読み込みを再開することもできます。
CancelOutputRead/CancelErrorReadメソッドを呼び出すとイベントの発生が中止され、再度BeginOutputReadLine/BeginErrorReadLineメソッドを呼び出すとイベントの発生が再開されます。 ただし、中断している間も標準出力・標準エラーの非同期的な読み込み自体は継続して行われているため、再開後は再開以降に標準出力・標準エラーへ書き込まれた分のイベントが発生します。 (再開までの間に書き込まれた分のイベントは発生しない)
using System;
using System.Diagnostics;
class Sample {
static void Main()
{
// 子プロセスを起動するためのProcessを作成
using (var child = new Process()) {
// child.exeを子プロセスとして起動する
child.StartInfo.FileName = "child.exe";
// シェルを使用せず子プロセスを起動する
// (リダイレクトするために必要)
// ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっている
child.StartInfo.UseShellExecute = false;
// 起動した子プロセスの標準出力をリダイレクトする
child.StartInfo.RedirectStandardOutput = true;
// リダイレクトした標準出力の内容を受信するイベントハンドラを設定する
child.OutputDataReceived += (sender, e) => {
// 子プロセスの標準出力から受信した内容を自プロセスの標準出力に書き込む
Console.WriteLine($"<stdout> {(e.Data ?? "(end of stream)")}");
};
// プロセスを起動する
child.Start();
// 標準出力の非同期読み込みを開始する
child.BeginOutputReadLine();
Console.WriteLine("(begin)");
// 500ミリ秒待機する
System.Threading.Thread.Sleep(500);
// 標準出力の非同期読み込みを中断する
child.CancelOutputRead();
Console.WriteLine("(cancel)");
// 500ミリ秒待機する (この間も標準出力の非同期読み込みは継続する)
System.Threading.Thread.Sleep(500);
// 標準出力の非同期読み込みを再度開始する
child.BeginOutputReadLine();
Console.WriteLine("(begin)");
// 子プロセスの終了を待機する
child.WaitForExit();
}
}
}
using System;
class Sample {
static void Main()
{
// 100ミリ秒おきにカウントアップして標準出力に出力する
for (var i = 0; i < 15; i++) {
Console.WriteLine(i);
System.Threading.Thread.Sleep(100);
}
}
}
Option Infer On
Imports System
Imports System.Diagnostics
Imports System.IO
Class Sample
Shared Sub Main()
' 子プロセスを起動するためのProcessを作成
Using child As New Process
' child.exeを子プロセスとして起動する
child.StartInfo.FileName = "child.exe"
' シェルを使用せず子プロセスを起動する
' (リダイレクトするために必要)
' ⚠.NET Core/.NET 5以降ではデフォルトでFalseとなっている
child.StartInfo.UseShellExecute = False
' 起動した子プロセスの標準出力をリダイレクトする
child.StartInfo.RedirectStandardOutput = True
' リダイレクトした標準出力の内容を受信するイベントハンドラを設定する
AddHandler child.OutputDataReceived, Sub(sender, e)
' 子プロセスの標準出力から受信した内容を自プロセスの標準出力に書き込む
Console.WriteLine($"<stdout> {If(e.Data Is Nothing, "(end of stream)", e.Data)}")
End Sub
' プロセスを起動する
child.Start()
' 標準出力の非同期読み込みを開始する
child.BeginOutputReadLine()
Console.WriteLine("(begin)")
' 500ミリ秒待機する
System.Threading.Thread.Sleep(500)
' 標準出力の非同期読み込みを中断する
child.CancelOutputRead()
Console.WriteLine("(cancel)")
' 500ミリ秒待機する (この間も標準出力の非同期読み込みは継続する)
System.Threading.Thread.Sleep(500)
' 標準出力の非同期読み込みを再度開始する
child.BeginOutputReadLine()
Console.WriteLine("(begin)")
' 子プロセスの終了を待機する
child.WaitForExit()
End Using
End Sub
End Class
Imports System
Class Sample
Shared Sub Main()
' 100ミリ秒おきにカウントアップして標準出力に出力する
For i As Integer = 0 To 14
Console.WriteLine(i)
System.Threading.Thread.Sleep(100)
Next
End Sub
End Class
>parent.exe
(begin)
<stdout> 0
<stdout> 1
<stdout> 2
<stdout> 3
<stdout> 4
(cancel)
(begin)
<stdout> 10
<stdout> 11
<stdout> 12
<stdout> 13
<stdout> 14
<stdout> (end of stream)
標準ストリームのエンコーディング
ProcessStartInfoでは、子プロセスの標準ストリームで使用するエンコーディングを指定することができます。 子プロセスの標準ストリームが自プロセスとは異なるエンコーディングを使用しているなど、標準ストリームでのエンコーディングを指定する必要がある場合には、以下のプロパティで指定します。
StandardOutputEncoding/StandardErrorEncodingプロパティでは、標準出力・標準エラーで用いるエンコーディングを指定できます。 .NET Standard 2.1/.NET Core 2.1以降では、StandardInputEncodingプロパティで標準入力のエンコーディングも指定できます。
using System;
using System.Diagnostics;
using System.Text;
class Sample {
static void Main()
{
// 子プロセスchild.exeの起動オプション
var psi = new ProcessStartInfo("child.exe") {
// シェルを使用せず子プロセスを起動する
// (リダイレクトするために必要)
// ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっている
UseShellExecute = false,
// 起動した子プロセスの標準入出力をリダイレクトする
RedirectStandardInput = true,
RedirectStandardOutput = true,
// 標準入力への書き込み時にShift_JISを用いる
StandardInputEncoding = shift_jis,
// 標準出力からの読み込み時にUTF-8を用いる
StandardOutputEncoding = Encoding.UTF8,
};
// 子プロセスを起動する
using (var child = Process.Start(psi)) {
string output = null;
// リダイレクトされた子プロセスの標準入出力を取得する
using (var stdin = child.StandardInput)
using (var stdout = child.StandardOutput) {
// 子プロセスの標準入力に書き込む
stdin.WriteLine("こんにちは、世界🌐");
// 子プロセスの標準出力から読み込む
output = stdout.ReadLine();
} // 子プロセスの標準入出力を閉じて読み書きを終了する
// 子プロセスの終了を待機する
child.WaitForExit();
// 読み込んだテキストを表示する
Console.WriteLine($"output: \"{output}\"");
}
}
static readonly Encoding shift_jis =
#if NETFRAMEWORK
Encoding.GetEncoding("shift_jis");
#else
// `dotnet add sample.csproj package System.Text.Encoding.CodePages`
CodePagesEncodingProvider.Instance.GetEncoding("shift_jis");
#endif
}
using System;
using System.IO;
using System.Security.Cryptography;
class Sample {
static void Main()
{
// 標準入力のエンコーディングにShift_JISを用いる
Console.InputEncoding = shift_jis;
// 標準出力のエンコーディングにUTF-8を用いる
Console.OutputEncoding = Encoding.UTF8;
// 標準入力から読み込んだ内容をそのまま標準出力に書き込む
// (Shift_JISからUTF-8へ変換される)
Console.WriteLine(Console.ReadLine());
}
static readonly Encoding shift_jis =
#if NETFRAMEWORK
Encoding.GetEncoding("shift_jis");
#else
// `dotnet add sample.csproj package System.Text.Encoding.CodePages`
CodePagesEncodingProvider.Instance.GetEncoding("shift_jis");
#endif
}
Imports System
Imports System.Diagnostics
Imports System.IO
Imports System.Text
Class Sample
Shared Sub Main()
' 子プロセスchild.exeの起動オプション
Dim psi As New ProcessStartInfo("child.exe")
' シェルを使用せず子プロセスを起動する
' (リダイレクトするために必要)
' ⚠.NET Core/.NET 5以降ではデフォルトでFalseとなっている
psi.UseShellExecute = False
' 起動した子プロセスの標準入出力をリダイレクトする
psi.RedirectStandardInput = True
psi.RedirectStandardOutput = True
' 標準入力への書き込み時にShift_JISを用いる
psi.StandardInputEncoding = shift_jis
' 標準出力からの読み込み時にUTF-8を用いる
psi.StandardOutputEncoding = Encoding.UTF8
' 子プロセスを起動する
Using child As Process = Process.Start(psi)
Dim output As String = Nothing
' リダイレクトされた子プロセスの標準入出力を取得する
Using stdin As StreamWriter = child.StandardInput, _
stdout As StreamReader = child.StandardOutput
' 子プロセスの標準入力に書き込む
stdin.WriteLine("こんにちは、世界🌐")
' 子プロセスの標準出力から読み込む
output = stdout.ReadLine()
End Using ' 子プロセスの標準入出力を閉じて読み書きを終了する
' 子プロセスの終了を待機する
child.WaitForExit()
' 読み込んだテキストを表示する
Console.WriteLine($"output: ""{output}""")
End Using
End Sub
#If NETFRAMEWORK Then
Shared ReadOnly shift_jis As Encoding = Encoding.GetEncoding("shift_jis")
#Else
' `dotnet add sample.vbproj package System.Text.Encoding.CodePages`
Shared ReadOnly shift_jis As Encoding = CodePagesEncodingProvider.Instance.GetEncoding("shift_jis")
#End If
End Class
Imports System
Imports System.IO
Imports System.Security.Cryptography
Class Sample
Shared Sub Main()
' 標準入力のエンコーディングにShift_JISを用いる
Console.InputEncoding = shift_jis
' 標準出力のエンコーディングにUTF-8を用いる
Console.OutputEncoding = Encoding.UTF8
' 標準入力から読み込んだ内容をそのまま標準出力に書き込む
' (Shift_JISからUTF-8へ変換される)
Console.WriteLine(Console.ReadLine())
End Sub
#If NETFRAMEWORK Then
Shared ReadOnly shift_jis As Encoding = Encoding.GetEncoding("shift_jis")
#Else
' `dotnet add sample.vbproj package System.Text.Encoding.CodePages`
Shared ReadOnly shift_jis As Encoding = CodePagesEncodingProvider.Instance.GetEncoding("shift_jis")
#End If
End Class
>parent.exe
output: "こんにちは、世界??"
StandardInputEncodingプロパティにUTF-8などのエンコーディングを指定すると、子プロセスの標準入力にBOM(Byte Order Mark)が出力される場合があります。 この動作はランタイムの種類や実行環境によって異なります。 詳細についてはEncodingクラスとBOMありなしの制御 §.標準ストリームを参照してください。
標準ストリームでのバイナリデータの読み書き
子プロセスの標準ストリームとして取得できるオブジェクトはStreamWriter/StreamReaderであるため、バイナリ形式での読み書きには向いていません。 また、非同期読み込みでのイベント引数DataReceivedEventArgsも文字列型(≒テキスト形式)として受信されるようになっています。
子プロセスの標準ストリームに対してバイナリ形式の読み書きを行う場合は、まず標準ストリームのStreamWriter/StreamReaderからBaseStreamプロパティを参照することにより、標準ストリームのStreamを取得します。
次に、BaseStreamプロパティから取得したStreamに対して直接読み書きを行うか、StreamからBinaryWriter/BinaryReaderなどを作成して読み書きすることにより、標準ストリームでバイナリ形式のデータを扱うことができるようになります。
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
class Sample {
static void Main()
{
// 子プロセスchild.exeの起動オプション
var psi = new ProcessStartInfo("child.exe") {
// シェルを使用せず子プロセスを起動する
// (リダイレクトするために必要)
// ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっている
UseShellExecute = false,
// 起動した子プロセスの標準入出力をリダイレクトする
RedirectStandardInput = true,
RedirectStandardOutput = true,
};
// 子プロセスを起動する
using (var child = Process.Start(psi)) {
// リダイレクトされた子プロセスの標準入出力を取得する
var stdin = child.StandardInput;
var stdout = child.StandardOutput;
// BaseStreamを参照して標準入出力のStreamを取得し、
// BinaryWriter/BinaryReaderを作成する
using (var stdinWriter = new BinaryWriter(stdin.BaseStream)) {
// 6バイトのバイナリデータを標準入力へ書き込む
stdinWriter.Write(new byte[] {0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x21});
} // 子プロセスの標準入力を閉じて書き込みを終了する
byte[] output = null;
using (var stdoutReader = new BinaryReader(stdout.BaseStream)) {
// 標準出力から最大16バイトのバイナリデータを読み込む
output = stdoutReader.ReadBytes(16);
}
// 子プロセスの終了を待機する
child.WaitForExit();
// 読み込んだバイナリデータ文字列に変換して表示する
Console.WriteLine(Encoding.ASCII.GetString(output));
}
}
}
using System;
using System.IO;
using System.Security.Cryptography;
class Sample {
static void Main()
{
// 標準入力のStreamを取得
var input = Console.OpenStandardInput();
// 標準出力のStreamをベースに、書き込まれる内容を
// BASE64エンコードするCryptoStreamを作成する
using (var output = new CryptoStream(
Console.OpenStandardOutput(),
new ToBase64Transform(),
CryptoStreamMode.Write,
leaveOpen: true
)) {
// 入力ストリームの内容を出力ストリームに書き込む
// (BASE64エンコードされた上で書き込まれる)
input.CopyTo(output);
}
}
}
Imports System
Imports System.Diagnostics
Imports System.IO
Imports System.Text
Class Sample
Shared Sub Main()
' 子プロセスchild.exeの起動オプション
Dim psi As New ProcessStartInfo("child.exe")
' シェルを使用せず子プロセスを起動する
' (リダイレクトするために必要)
' ⚠.NET Core/.NET 5以降ではデフォルトでFalseとなっている
psi.UseShellExecute = False
' 起動した子プロセスの標準入出力をリダイレクトする
psi.RedirectStandardInput = True
psi.RedirectStandardOutput = True
' 子プロセスを起動する
Using child As Process = Process.Start(psi)
' リダイレクトされた子プロセスの標準入出力を取得する
Dim stdin As StreamWriter = child.StandardInput
Dim stdout As StreamReader = child.StandardOutput
' BaseStreamを参照して標準入出力のStreamを取得し、
' BinaryWriter/BinaryReaderを作成する
Using stdinWriter As New BinaryWriter(stdin.BaseStream)
' 6バイトのバイナリデータを標準入力へ書き込む
stdinWriter.Write(New Byte() {&H48, &H65, &H6C, &H6C, &H6F, &H21})
End Using ' 子プロセスの標準入力を閉じて書き込みを終了する
Dim output As Byte()
Using stdoutReader As New BinaryReader(stdout.BaseStream)
' 標準出力から最大16バイトのバイナリデータを読み込む
output = stdoutReader.ReadBytes(16)
End Using
' 子プロセスの終了を待機する
child.WaitForExit()
' 読み込んだバイナリデータ文字列に変換して表示する
Console.WriteLine(Encoding.ASCII.GetString(output))
End Using
End Sub
End Class
Imports System
Imports System.IO
Imports System.Security.Cryptography
Class Sample
Shared Sub Main()
' 標準入力のStreamを取得
Dim input As Stream = Console.OpenStandardInput()
' 標準出力のStreamをベースに、書き込まれる内容を
' BASE64エンコードするCryptoStreamを作成する
Using output As New CryptoStream(
Console.OpenStandardOutput(),
New ToBase64Transform(),
CryptoStreamMode.Write,
leaveOpen := True
)
' 入力ストリームの内容を出力ストリームに書き込む
' (BASE64エンコードされた上で書き込まれる)
input.CopyTo(output)
End Using
End Sub
End Class
>parent.exe
SGVsbG8h
StandardInput/StandardOutput/StandardErrorで取得できるStreamWriter/StreamReaderは内部でバッファリングが行われます。 このため、StandardInput/StandardOutput/StandardErrorを使った読み書きと、BaseStreamから取得したStreamへの読み書きを混在させると入出力内容に不整合が起こる(入出力されるデータの一部の順序が前後する)可能性があり、両者の使用は排他的とする必要があります。
つまり、BaseStreamから取得したStreamで読み書きを行う場合は、それのみを使って読み書きを行う、あるいはどちらか一方をFlushするまではもう一方を使って読み書きしないようにします。
Windows上では、BaseStreamプロパティから取得したStreamを閉じたあとにStandardOutput/StandardErrorを閉じると、StreamWriterがFlushしようとして例外ObjectDisposedExceptionがスローされます。 そのため、StreamWriterとBaseStreamプロパティから取得したStreamの両方をCloseしないように(二重にCloseする動作とならないように)注意する必要があります。
自プロセスの標準ストリームのStreamを取得する方法については自プロセスの標準入出力 §.標準ストリームの取得を参照してください。
Streamクラスを使った読み書きの方法についてはストリームの基本とStreamクラス、BinaryWriterクラス/BinaryReaderクラスを使った読み書きの方法についてはBinaryReaderクラス・BinaryWriterクラスを参照してください。