2011年11月27日日曜日

◆スクリプトブロックを使う

先日、社内でC#の講師を依頼された。
講師をするような知識など毛頭ないのだが、VBプログラマー向けの基本だけということなので承諾することとした。

基本だけと言っても久しくC#は使っていないので@ITのサイト等でおさらいした。
一応C#3.0までひと眺めしたのだが、C#3.0の機能を見ていると全てがLINQにつながっているのが判る。

LINQの拡張メソッド形式は、ラムダ式で処理や判定を加えながらパイプライン的に処理を繋げていくので、Powershellと同じ感覚で使えて非常に気持ちが良い。

その記事の著者(川俣さん)が言うにはC#3.0になってクラスの地位が後退してきているのだとか。
その代わりにメソッドが主体になってきているのだと。

確かに、匿名メソッドやラムダ式を受け渡して使う頻度が増えているように思う。
JavaScriptなどもクラスが存在せず、メソッドを受け渡す方式だったような・・・。

そこでPowershellはというと、やはりクラスは存在せず処理の受け渡しにはスクリプトブロックが使える。

私はPowershellでは長い処理を書かないので必要性に迫られたことは無いのだが、これを機にスクリプトブロックの確認してみた。

001
002
003
004
005
006
007

$script = { 
 
$st = "スクリプト"
  Write-Host $st
}

&
$script
$script
.Invoke()

image

スクリプトブロックは { } で囲んで普通に処理を記述するだけだ。
そのまま変数に入れて、呼び出し演算子もしくはInvokeメソッドで実行することができる。

スクリプトブロックは通常の関数と同様にparamで引数を受け取ることが可能だ。

001
002
003
004
005
006
007

$script = {
param($param
)
 
Write-Host $param
}

&$script "スクリプト"
$script.Invoke("パラメータ")

image

特に難しい所はない。

次に、スクリプトブロックの受け渡しを確認するためにスクリプトブロックを返す関数を書いてみた。

001
002
003
004
005
006
007
008
009
010
011

Function Get-Writer()
{
 
return
{
   
param([string]$message
)
     
Invoke-Expression "Write-Host `"$(Get-Date) : $message`""
 
  } 
}
 

$out = Get-Writer 
&$out "メッセージ"
$out.Invoke("処理完了")

image

ログを出力する関数のイメージだ。

スクリプトブロックを返すには単純に関数の中にブロックを書けば良い。(returnは無くても良さそうだが、付けたほうがなんとなくしっくりする)

これだけだとあまり意味が無いので出力方法を外から変更できるよう引数にしてみる。

001
002
003
004
005
006
007
008
009
010
011

Function Get-Writer([string]$cmd)
{
  return{
   
param([string]$message
)
     
Invoke-Expression "$cmd `"$(Get-Date) : $message`""
 
  } 
}
 

$out = Get-Writer "Write-Host"
& $out "メッセージ"
$out.Invoke("処理完了")

一見うまく行っているように見えたのだが、「Write-Host」を「Write-Warning」に変更しても結果が変わってくれない。

ん~、どうやら$cmdの引数がスクリプトブロックに渡って行かないようだ。
C#の匿名メソッドではこのスコープの変数はキャプチャしてくるのだが、Powershellではそうは行かないのだろう。

調べてみるとスクリプトブロック自体にGetNewClosureというメソッドがあって、これを呼ぶと変数をキャプチャしてくれるっぽい。

001
002
003
004
005
006
007
008
009
010
011

Function Get-Writer([string]$cmd)
{
 
return
{
   
param([string]$message
)
     
Invoke-Expression "$cmd `"$(Get-Date) : $message`""
 
  }
.
GetNewClosure() 
}
 

$out = Get-Writer "Write-Warning"
& $out "メッセージ"
$out.Invoke("処理完了")

image

出力先をログファイルに変更したくなったら、

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016

Function Get-Writer([string]$cmd)
{
 
return
{
   
param([string]$message
)
     
Invoke-Expression "$cmd `"$(Get-Date) : $message`""
 
  }
.
GetNewClosure() 
}


Function Out-Logfile($message
)
{
 
Add-Content outfile.log $message
 
}
 

$out = Get-Writer "Out-Logfile"
& $out "メッセージ"
$out.Invoke("処理完了")

こんな風に変更してあげれば良い。

出力方式はあとからいくらでも変更できるようになる。

うまく使いこなせばスクリプトブロックは便利に使えるのかもしれない。

0 件のコメント:

コメントを投稿