新年一発目のエントリなのでPowerShellの基本的なことについて書きます。
PowerShellの基本的なことがわかってなかったシリーズ第8弾でもあります。
このエントリを書く動機
Google等でWrite-Host Write-Output 違いなワードで検索すると非常にアレな感じだったのでもう少しまともにしたいというのが動機です。
私自身PowerShell勉強中の身なので大したことは書けませんが、それでもすこしは現状をマシにできると思っています。(本当はもっと詳しい人にこのエントリを書いてもらった方がうれしいのですが...)
前提となる基本
Write-HostとWrite-Outputの違いについて触れるまえにPowerShellの重要な基本に触れる必要があります。
それは、
PowerShellはオブジェクトを扱うシェルである。
という点です。
これがどう重要かというと、コマンドプロンプト(cmd.exe)やbash等のUnix系シェルはテキストベースのシェルであり、標準・エラー出力に出力されるテキストとコンソール表示される内容は基本的に同一となります。*1
対してPowerShellでは標準・エラー出力に相当するStandard output stream(1>)・Error output stream(2>)にはオブジェクトが出力され、最終的にコンソールにはオブジェクト毎に定められた書式のテキストが表示されます。
このため、PowerShellでは出力と表示が基本的に一致せず、
(各ストリームへの)出力と(コンソールの)表示は常に分けて考える必要がある。
という事になります。
これは、PowerShellに慣れている人にとっては当たり前に感じると思いますが、慣れてない人にとっては意外と意識されていない様に見受けられます。*2
Write-HostとWrite-Outputの違い
この基本を踏まえてWrite-HostとWrite-Outputの違いについて触れていきます。
まずは各コマンドレットがどういうものかについて説明します。
Write-Hostついて
Get-Help Write-Hostを実行してヘルプを見ると、概要は、
カスタマイズした出力をホストに書き込みます。
となっています。
ここでいう"ホスト"はPowerShellの実行環境(PowerShellコンソールやPowerShell ISE等)であり、ホスト≒コンソールと考えて差し支えありません。
Write-Hostはコンソールに文字を表示させるだけのコマンドレットです。Write-Hostを実行してもStandard output streamへの出力はありません。*3
このため出力結果を変数に設定することもできません。
Write-Outputについて
Get-Help Write-Outputを実行してヘルプを見ると、概要には、
指定されたオブジェクトをパイプラインの次のコマンドに送信します。 そのコマンドがパイプラインの最後のコマンドである場合、オブジェクトはコンソールに表示されます。
と書いてあり、説明に、
Write-Output は、"出力ストリーム" や "正常終了パイプライン" とも呼ばれるプライマリ パイプラインにオブジェクトを送信します。 エラー オブジェクトをエラー パイプラインを送信するには、Write-Error を使用します。
と書いてあります。
Write-Outputは、Standard output stream(="出力ストリーム")に指定のオブジェクトを出力するコマンドレットです。
出力の型がSystem.Management.Automation.PSObjectになっており、文字列に限らずあらゆるオブジェクトを扱います。
Standard output Streamに出力されているので結果を変数に設定することができます。
そして、重要なことなのですが、Write-Output自体はコンソールへの表示を行う機能は持っていません。
ただし、
そのコマンドがパイプラインの最後のコマンドである場合、オブジェクトはコンソールに表示されます。
とある様に、パイプラインの最後*4でWrite-Outputを実行すると最終的にはコンソールにオブジェクトに応じた文字列が表示されます。
こちらについて詳細は後述します。
Write-HostとWrite-Outputの違い
ここまでの内容をまとめると、
Write-Hostはコンソールに文字を表示し、Standard output streamへの出力はしない。Write-OutputはStandard output streamにオブジェクトを出力し、コンソールへの表示は行わない。
(ただし、後述の仕組みにより最終的にはコンソールに文字が表示される)
となり、表示と出力で明確に目的が分かれていることがわかります。
このため、表示を目的としたWrite-Hostでは-ForegroundColorや-BackgroundColorによる色指定や、-NoNewlineによる改行の有無を指定できるものの結果を変数に設定することができず、出力を目的としたWrite-Outputでは結果を変数に設定できますがWrite-Hostの様なパラメーターはありません。
単純に利用する分には似た動作に見えるWrite-HostとWrite-Outputですが、その実全くの別物であることがわかります。
既定の出力、Out-Defaultについて
ここからは補足的な内容となり、若干PowerShellのコアな部分について触れていきます。
Write-Outputにはコンソールに文字を表示する機能がないにもかかわらず最終的にはコンソールに文字列が出力されます。
この仕組みについてはTechNetマガジンの以下の記事に詳しく記載されています。
Windows PoweShell: 出力に関するオプション
この記事を読んでもらえば私から説明することは無い気もしますが、かいつまんで説明していきます。
PowerShellの出力の基本
上記の記事にある通り、PowerShellでオブジェクトは必ず原則*5、
Format-*コマンドレットにより表示の書式を設定Out-*コマンドレットにより最終出力
の手順を踏み何らかの表示や出力がなされます。
Format-*なコマンドレットは、
| 名称 | 表示形式 | 特記事項 |
|---|---|---|
| Format-List | リスト形式 | |
| Format-Table | 表形式 | |
| Format-Wide | 複数列表示 | |
| Format-Custom | 独自の表示形式 | Update-FormatDataで設定 |
| Format-Default | 既定のフォーマット | 内部用。詳細は後述 |
Out-*なコマンドレットは、
| 名称 | 出力先 | 特記事項 |
|---|---|---|
| Out-Host | ホスト | PowerShellコンソール、PowerShell ISEなど |
| Out-File | ファイル | |
| Out-GridView | グリッド形式のウィンドウ | |
| Out-Null | 出力なし | >/dev/nullに相当 |
| Out-Printer | プリンタ | |
| Out-String | 書式設定されたオブジェクトを文字列に変換し出力 | このコマンドレットだけは例外的に後続にパイプすることが可能 |
| Out-Default | 既定の出力 | ≒Out-Host。詳細は後述 |
があります。
既定の出力、Out-Default
上記の原則によれば、オブジェクトをコンソールに表示するにはOut-Hostコマンドレットを呼び出す必要がありますが、実際には呼び出さなくてもコンソールに文字列が表示されるのは前述のとおりです。
これは、明示的にOut-*を呼ばない場合は既定の出力Out-Defaultが暗黙的に呼ばれているためです。
Out-Defaultとは一体何なのか?というと、私もまだ勉強中でわからない部分が非常に多いです。 ただ、上記の記事によればOut-DefaultはOut-Hostにオブジェクトを転送している*6そうなのでOut-Default≒Out-Hostとみなして問題ないでしょう。
このため、Out-*を明示せず単純にWrite-Outputを実行した場合、以下の様な流れで最終的にコンソールに文字が表示されることになります。
# 単純なWrite-Outputの呼び出し PS C:\> Write-Output "Hello World." ↓ # Out-*を明示しない場合はOut-Defaultが暗黙的に呼ばれてる PS C:\> Write-Output "Hello World." | Out-Default ↓ # Out-Default≒Out-Hostなので最終的にコンソールに文字が表示される PS C:\> Write-Output "Hello World." | Out-Host Hello World.
Out-Defaultの確認
暗黙のOut-Defaultの呼び出しを確認するために、以下のファンクションを定義します。
こちらは、Windows PowerShell in Actionに記載されているコードを参考に*7しており、Out-Defaultの呼び出しがあった時にパイプラインへの入力をフックして独自のコードを実行させるものになります。
| <# | |
| Out-Defaultをフックしてパイプラインに渡されたオブジェクトの型と値をホストに表示する。 | |
| Windows PowerShell in Actionのコードを参考。 | |
| #> | |
| function Out-Default | |
| { | |
| [CmdletBinding(ConfirmImpact="Medium")] | |
| param | |
| ( | |
| [Parameter(ValueFromPipeline=$true)] | |
| [System.Management.Automation.PSObject] $InputObject | |
| ) | |
| begin | |
| { | |
| $wrappedCmdlet = $ExecutionContext.InvokeCommand.GetCmdlet("Out-Default") | |
| $sb = { & $wrappedCmdlet @PSBoundParameters } | |
| $__sp = $sb.GetSteppablePipeline() | |
| $__sp.Begin($pscmdlet) | |
| } | |
| process | |
| { | |
| # パイプラインに渡されたオブジェクトの型と値をホストに表示 | |
| Write-Host "Called Out-Default | Value Type = $($_.GetType())" | |
| Write-Host "Called Out-Default | Input value = $($_)" | |
| # パイプライン継続 | |
| $__sp.Process($_) | |
| } | |
| end | |
| { | |
| $__sp.End() | |
| } | |
| } |
Out-Defaultをフックしてパイプラインに渡されたオブジェクトの型と値をホストに表示するサンプル
独自のコードは、
# パイプラインに渡されたオブジェクトの型と値をホストに表示 Write-Host "Called Out-Default | Value Type = $($_.GetType())" Write-Host "Called Out-Default | Input value = $($_)"
の部分で、Out-Defaultが呼び出されたらコンソールにパイプラインから渡された値とその型を表示する様にしています。
そして、このファンクションを定義した上でWrite-Outputを実行すると以下の様な結果となります。
PS C:\> Write-Output "Hello World." Called Out-Default | Value Type = string Called Out-Default | Input value = Hello World. Hello World.
この結果からOut-Defaultが暗黙的に呼び出されていることがわかります。
既定のフォーマット、Format-Defaultについて
前項のコード例では、Format-*を明示しない例を挙げました。
では、Format-*についてもOut-*と同様に暗黙的にFormat-Defaultが呼ばれているのかというと、どうやら違う様です。
こちらについては、参考となるドキュメントは無かったのですがTrace-Commandコマンドレットを使い以下のコードを試してみたところ、
# PowerShell 5.0な環境で実施 Trace-Command -Name * –Expression { Write-Output "Hello world." | Out-Host } # および Trace-Command -Name * –Expression { Write-Output "Hello world." | Out-Default }
トレース中に、
# ※Out-Defualtの場合も同様の内容になる # Out-Hostの呼び出し開始 ParameterBinding Information: 0 : BIND NAMED cmd line args [Out-Host] (中略) # パイプラインから渡された値は "Hello world." ParameterBinding Information: 0 : BIND PIPELINE object to parameters: [Out-Host] ParameterBinding Information: 0 : PIPELINE object TYPE = [System.String] ParameterBinding Information: 0 : RESTORING pipeline parameter's original values ParameterBinding Information: 0 : Parameter [InputObject] PIPELINE INPUT ValueFromPipeline NO COERCION ParameterBinderController Information: 0 : WriteLine Adding PipelineParameter name=InputObject; value=Hello world. ParameterBinding Information: 0 : BIND arg [Hello world.] to parameter [InputObject] (中略) # Format-Defaultの呼び出し開始 ParameterBinding Information: 0 : BIND NAMED cmd line args [Format-Default] ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Format-Default] ParameterBinderController Information: 0 : WriteLine CurrentParameterSetName = __AllParameterSets ParameterBinderController Information: 0 : WriteLine CurrentParameterSetName = __AllParameterSets ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Format-Default] ParameterBinding Information: 0 : CALLING BeginProcessing # パイプラインから渡された値は "Hello world." ParameterBinding Information: 0 : BIND PIPELINE object to parameters: [Format-Default] ParameterBinding Information: 0 : PIPELINE object TYPE = [System.String] ParameterBinding Information: 0 : RESTORING pipeline parameter's original values ParameterBinding Information: 0 : Parameter [InputObject] PIPELINE INPUT ValueFromPipeline NO COERCION ParameterBinderController Information: 0 : WriteLine Adding PipelineParameter name=InputObject; value=Hello world. ParameterBinding Information: 0 : BIND arg [Hello world.] to parameter [InputObject] (後略)
といった結果が表示され、Out-*コマンドレット内部で既定のフォーマットを行うFormat-Defaultが呼び出されることがわかりました。
もちろんFormat-*を明示した場合はFormat-Defaultは呼び出されません。
なお、Format-DefaultはMSDNに記載されている様に、
This cmdlet is for internal system use only.
であり、公開されておらずユーザーが利用することはできません。
最後に
とりあえずこんな感じです。
Write-HostとWrite-Outputの違い、PowerShellのフォーマットと出力の基本についてわかる範囲で書きました。
極力間違いのない様にしていますが、現在進行形で調査中な部分もあり正直自信の無い部分もあります。
エントリの内容に間違いがあれば随時訂正していきます。
補足
以下に本エントリの内容に関連する補足資料を挙げます。
PowerShellのストリームの詳細については以下のエントリをご覧ください。
Format-*によってオブジェクトがどういう書式をとるかについては牟田口先生の以下のエントリをご覧ください。
Write-HostとWrite-Outputの表示のされ方の違いについては以下のエントリをご覧ください。
Write-Output自体について補足を追記しましたので以下のエントリもよければご覧下さい。
- 作者: Bruce Payette
- 出版社/メーカー: Manning Pubns Co
- 発売日: 2011/05/21
- メディア: ペーパーバック
- クリック: 6回
- この商品を含むブログを見る
*1:エスケープシーケンスとかは置いておいて...
*2:実際、私はPowerShellを学習しだしてからこの点を意識するのにしばらく時間がかかりました...
*3:正確な話をすると、PowerShell 5.0からWrite-Hostの結果がInformation streamに出力される様になっていますが、本エントリではわかりやすさのためにその点にはあえて触れません
*4:要は後ろにパイプラインをつなげない状態
*5:例えばOut-GridViewにFormat-*したデータを渡すとエラーになります。
*6:PowerShell 5.0の環境でILSpyでOut-Defaultの実装を見た限りだとOut-Hostとは別実装になっており、この辺の仕組みも記事公開時から変わっているっぽいです...
*7:ほぼ丸パクリですが...