しばたテックブログ

気分で書いている技術ブログです。

PowerShellのストリームと標準入出力、リダイレクトとパイプラインの話

PowerShellの基本的なところがわかってなかったシリーズ第二弾です。
一部わからないままの部分がありますのでご存知の方いらっしゃいましたらご指摘ください。

最初にまとめ

今回のエントリの内容をまとめて図に表すと以下の様な感じになります。

f:id:stknohg:20160413182313p:plain

ちょっとごちゃごちゃしてますが、この図を踏まえて以下の内容をご覧ください。

PowerShellのストリームとリダイレクト、パイプライン

PowerShellのストリームとリダイレクト

基本的にはこのエントリabout_Redirectionのはなしになります。

PowerShellではデータの入出力はストリームを介して行われます。
標準出力に相当するStandard output streamと標準エラー出力に相当するError output streamがあり、Standard output streamは通常のファンクションやコマンドレットの実行結果およびWrite-Outputの結果などが出力され、Error output streamはTrapした例外やWrite-Errorの結果が出力されます。 そしてStandard output stream1>で、Error output stream2>でリダイレクトすることができます。

また、PowerShell 3.0からWrite-Warningの結果が出力されるWarning output stream(3>)、Write-Verboseの結果が出力されるVerbose output stream(4>)、Write-Debugの結果が出力されるDebug output stream(5>)が追加され、全部で5つのストリームが存在します。 ています。

---- 2015/06/29追記 ----
上記ストリームに加え、Write-Progressの結果が出力されるストリーム(こちらはPowerShellの言語仕様書に名称が明記されていなかったのでProgress streamとしておきます)と、標準入力に相当するストリーム(こちらはInput Streamとしておきます)の計7つのストリームが存在します。
ちなみにProgress Streamはリダイレクトすることができません。
---- 追記ここまで ----

---- 2015/07/05追記 ----
さらに、PowerShell 5.0から新たにInforation Stream(6>)が増え、これまでストリームに乗ることのなかったWrite-Hostの結果をストリームに乗せることができる様になります。
---- 追記ここまで ----

Input Streamを除いた分をまとめると以下の様になります。

ストリーム 出力されるオブジェクトの型 リダイレクト方法 出力方法 特記事項
Standard output stream Objectおよびその派生型 (正確にはPSObjectらしい) >(>>) 1>(1>>) Write-Outputなど 標準出力に相当
Error output stream ErrorRecord 2>(2>>) Write-Errorなど 標準エラー出力に相当
Warning output stream WarningRecord 3>(3>>) Write-Warningのみ PowerShell 3.0以上
Verbose output stream VerboseRecord 4>(4>>) Write-Verboseのみ PowerShell 3.0以上
Debug output stream DebugRecord 5>(5>>) Write-Debugのみ PowerShell 3.0以上
Progress stream ProgressRecord リダイレクト不可 Write-Progressのみ
Information stream InformationRecord 6>(6>>) Write-Host、Write-Informationのみ PowerShell 5.0以上

補足として、すべてのストリームに対してリダイレクトする場合は*>(*>>)を記述することもできます。*1
そしてStandard output Stream以外のストリームは>&1とすることでStandard output Streamへさらにリダイレクトすることができます。

PowerShellのストリームとパイプライン

PowerShellのパイプラインはStandard output Streamでのみ可能であり、その他のストリームではパイプラインを繋げることはできません。

Write-Hostについて

Write-Hostの結果は上記のどのストリームにも乗らずただコンソール画面に表示されて終わりとなります。

Microsoft Connect is Retired - Collaborate | Microsoft Docs では当初Host Stream(6>)が提案されていた様 ですが採用には至っていません。
ただ、現時点で6>は予約済みとなっておりいずれは導入される可能性があるのかもしれません。

---- 2015/07/05追記 ----
PowerShell 5.0からInformation stream(6>)が増え、Write-Hostの結果や新設のWrite-Informationの結果を乗せることができる様になります。
詳細については、

Weekend Scripter: Welcome to the PowerShell Information Stream – Hey, Scripting Guy! Blog

で確認できます。
---- 追記ここまで ----

PowerShellのストリームと標準入出力

ここまでは純粋にPowerShell内部のはなしになるのですが、ここからはPowerShellから外部のプロセス(.exe)を呼び出した場合について触れていきます。

PowerShell外部のプロセスにおいて入出力は標準入力(StdIn)標準出力(StdOut)標準エラー出力(StdErr)を介して行われます。
PowerShellではないのでデータに型はなく生のバイナリ、またはテキストデータが流れます。

当然これらの標準入出力とPowerShellのストリームは似て非なるものであり、PowerShellから外部のプロセスを呼び出した場合は相互にデータの変換を行う必要があります。

ここでPowerShellの仕様書などいろいろ調べてみたのですが、正直どういった変換が行われているのかわかりませんでした。

このため以下には自分で調べてわかった範囲のことを書いていきます。
内容に誤りがあるかもしれませんがその際はご指摘いただければ訂正します。

標準出力(StdOut)からStandard output streamへの変換について

外部のプロセスを呼び出し標準出力(StdOut)へ出力されたデータは最終的にString[]型に変換されてStandard output streamに出力される様です。
バイナリデータを出力した際も文字化けした状態のString[]になりました。

---- 2015/06/26追記 ----
データは[Console]::OutputEncodingで設定されているエンコーディングでエンコードされる様です。
[Console]::OutputEncodingはコンソールのコードページとも関わっているので変更するとコンソールで使えるフォントが変わってきます。
フォントにない文字の場合は文字化けしてStandard output streamに渡されます。
---- 追記ここまで ----

標準エラー出力(StdErr)からError output streamへの変換について

標準エラー出力(StdErr)出力されたデータは最終的にErrorRecord[]型に変換されてError output streamに出力されます。
変換ロジックについては検証できなかったため不明です。

---- 2015/06/28追記 ----
変換ロジックは標準出力(StdOut)からStandard output streamへの変換と同様です。
---- 追記ここまで ----

PowerShellから標準入力(StdIn)へのパイプラインについて

PowerShellのパイプライン演算子は外部のプロセスに対しても適用することができます。

"Hello!" | Hoge.exe

といったコードを書くことができ、Standard output streamから標準入力(StdIn)を経由して外部のプロセスにデータを渡せます。

ただ、Standard output streamには型付きのあらゆるオブジェクトが出力されます。
標準入力(StdIn)に渡すためにはなんらかの変換をする必要があるわけですが、私の検証した限りでは改行付きの文字列に変換したテキストデータを渡している様に見受けられました。

上記の例だとHello! + CrLfが渡される様です。
またこのテキストデータという部分について、ASCIIのみ受け付けるせいなのかコードページによるものなのか分からないのですがShift-JISの文字列を渡すと文字化けし、化けた後の"?"が外部のプロセスに渡されるといったこともありました。

---- 2015/06/26追記 ----
さらに調べてた結果、標準入力(StdIn)にはコンソールに出力される文字列を$OutputEncodingに設定されているエンコーディングでエンコードした文字列を渡している様です。

このため、通常の文字列の場合は改行コードが追加され、$OutputEncodingはデフォルトでASCIIであるためShift-JISの文字列が文字化けするといったことが起きていました。
$OutputEncodingを適宜変えてやることで文字化けせずにStandard output streamにデータを渡すことができます。

また、文字列でないオブジェクトについてはコンソールにはFormat-ListFormat-Tableした結果が出力されます。
このためオブジェクトを標準入力(StdIn)に渡すとFormat-ListFormat-Tableした結果の文字列が渡されることになります。
---- 追記ここまで ----

さらに外部のプロセスに対してパイプラインを渡すのはPowerShell で 外部コマンドをパイプで渡す時の問題について - tech.guitarrapc.cómにもある様に性能面でも問題を抱えています。

挙動の謎さと性能面の問題があることからPowerShellから外部のプロセスに対してパイプラインを渡すのはやめておいた方が賢明だと思います。

最後に

PowerShellのストリームと標準入出力の関係ついてわかる範囲で調べてみましたが、不明な点がまだまだありましたので、新しいことがわかり次第このエントリの内容は更新していきたいと思います。

ちなみにPowerShellの言語仕様書は以下からダウンロードするすることができます。

補足

[2015/06/26追記]
PowerShellにおける"ストリーム"という用語について、この用語が使われる様になったのはPowerShell 3.0からの様です。
PowerShell 2.0の言語仕様書を見るとStandard output streamError output streamはそれぞれStandard outputError outputとだけ表記されていました。
単純に標準出力、標準エラー出力と対になるものとして認識されていたという事なのでしょうか?そのへんの事情はよくわかりません。

[2015/06/28追記]
補足的なエントリ、

stknohg.hatenablog.jp

stknohg.hatenablog.jp

stknohg.hatenablog.jp

を書きましたのでよろしければこちらもご覧ください。

【改訂新版】 Windows PowerShell ポケットリファレンス

【改訂新版】 Windows PowerShell ポケットリファレンス

*1:PowerShell3.0以上