2011年12月31日土曜日

◆Write-Hostを使ってみる

言わずと知れたホストに出力するコマンドレット。
簡単に使えて、何の変哲もないといえば無いのだが、実はなんとなく良く分からないコマンド。

コンソールに色つきで出力したい、なんて時はこのコマンドの出番なのだが意外と色々戸惑うことも有ったので少し纏めておく。

PS>Write-Host A
A
PS>Write-Host A B
A B

いままで特に不思議に思わず普通に使ってきたのだが、2つめのコマンドはなぜこんな結果になるのだろう。

通常、「コマンドレット A  B」という形式は位置パラメータの1番目に”A” 、2番目に”B”と解釈されるものと思う。
なので、位置パレメータを1つしか持たない「Write-Host」はエラーになるのが普通ではなかろうか。

実際、「Write-Warning」でためしてみると、予想通りのエラーになる。
image

パラメータの型が<Object>で位置パラメータが1つ、と言ったあたりに何か理由があるのかとも思って調べてみたが結局良く判らなかった。

Trace-Commandで見てみると、パラメータがArrayListとして解釈されているっぽい感じではあるが、それがなぜかまでは判らない。
image

すべてのコマンドを検証したわけではないが、このような動作をするのは他に「Read-Host」くらいしか見つけられなかった。
とりあえず現時点ではそういうものだと思うことにしよう。(^^;

次に、連想配列を「Write-Host」に渡した時の動作を見てみる。

PS>$array = @{a=1;b=2;c=3}
PS>$array

Name                           Value                                     
----                           -----                                     
a                              1                                         
b                              2                                         
c                              3                                         


PS>Write-Host $array
System.Collections.DictionaryEntry System.Collections.DictionaryEntry System.Collections.DictionaryEntry

そのまま出力すれば内容が表示されるのだが、こいつに色を指定しましょうなどと思って「Write-Host」を使うと残念な結果になる。

まずはこの時、「Write-Host」がどのような動作をしているのかを確認してみる。
「DictionaryEntry」が3つ出力されているので中身をループしながらToStringって感じだろうか。

PS>$array.GetEnumerator() | %{$_.ToString()}
System.Collections.DictionaryEntry
System.Collections.DictionaryEntry
System.Collections.DictionaryEntry

内容的には合っているが表示形式がちょっと違う。

こうか。

PS>$array.GetEnumerator() -join " "
System.Collections.DictionaryEntry System.Collections.DictionaryEntry Syst
em.Collections.DictionaryEntry

実際にはこんな感じの条件がついているのかな。

PS>if($array -is [System.collections.IEnumerable]) {$array.GetEnumerator() -join " "}
System.Collections.DictionaryEntry System.Collections.DictionaryEntry Syst
em.Collections.DictionaryEntry

さて、実はここからが本題で、これまでの話とはそれほど関係ないのだが、以下の記事でハッシュテーブルに色をつけて表示するにはどうすればよいかという話が有った。
Using Write-Host to display a hashtable
全体に色をつければ良いようなのでOut-Stringして結果をWrite-Hostするだけの事なのだが通常は特定の行に色を付けたいよなと思って考え始めたところWrite-Hostの仕様がいまひとつ判らなかったという事で前段の調査になった。

以前似たようなことをやった事があるPowerShell: ◆特定のプロセスに色をつけて表示するのだが、若干うまく行かないケースがあるので少しだけ違った方法でやってみた。

今回も今ひとつスマートな方法は思いつかず、以下のような若干ベタな感じ。

001
002
003
004
005
006
007
008

$array = @{a=1;b=2;c=3}
$array.GetEnumerator() | %
{ 
     
Write-host "key`tvalue`r---`t-----"
  }{
     
$para =
 @{}
     
if($_.key -eq "b"){$para.Add("foreground","red"
)}
     
Write-Host $_.key `t $_.value @para
  }

image

やっぱり素直にOut-Stringでやったほうが良さそうかな。

001
002
003
004
005
006

$array = @{a=1;b=2;c=3}
$array | Out-String -Stream | %
{
 
$para =
 @{}
 
if($_ -match '^b\b'){$para.Add("foreground","red"
)}
 
Write-Host $_ @para
}

image

本当はToStringをオーバーライドしたりするとスマートかなと思って書き始めたのだが、そちらはうまく行かなかった。

2 件のコメント:

  1. あまり深く考えたことはなかったのですが、たしかにWrite-Hostの挙動は珍しいですね。

    おそらくですが、コマンドレット内部では-objectパラメータの値(WriteHostCommand.Object)を見るのと同時に、$argsに相当する値(WriteHostCommand.MyInvocation.UnboundArguments)も見ているんだと思います。

    複数の名前なしパラメータが渡されると、パラメータセットが解決できないのでUnboundArgumentsに値が渡ることになると思います。
    おそらくそういう動作になるように、-object以外のパラメータが位置=namedで必須=falseになってるんでしょう。
    (二つ以上の名前なしパラメータを渡すと自動的に、パラメータセットが解決できない状態になるようにしている)

    ハッシュテーブルの色変更ですが、hashtableクラスに対応するformat.ps1xmlのスクリプトを書いてUpdate-FormatData(上書き)するのがPowerShell的なアプローチになるのかなと思いました。

    返信削除
  2. 正月に休暇をとっていたので気づくのが遅くなりました。
    年末までご苦労様です。

    ハッシュテーブルの色変更については私もなんとなくすっきりしないのでご指摘の案も含めて検討してみたいと思います。

    今年もよろしくお願いいたします。

    返信削除