2012年1月30日月曜日

◆コンソール出力に下線(アンダーライン)を引く

以下を参考にアンダーラインを引くサンプルを作って見ました。
固定ピッチフォントの環境じゃないとずれてしまうような気はしますが・・・。

Multiply PowerShell Strings to Customize Text Underlines - Hey, Scripting Guy! Blog - Site Home - TechNet Blogs

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022

Function New-Underline
{
 
[CmdletBinding()]
  param
(
     
[Parameter(Mandatory = $true,Position = 0,valueFromPipeline=$true)]
      [string]
      $stringIN,
      [string]
      $char = " ̄"
  )
 
$encSjis = [System.Text.Encoding]::GetEncoding("Shift-JIS"
)
 
$x = $encSjis.GetByteCount($stringIN
)
 
$y = $encSjis.GetByteCount($char
)
 
$underLine = $char * ([Math]::Ceiling($x / $y
))

 
Write-Host $stringIn
  Write-Host $underLine -ForegroundColor Red
} 

New-Underline "abcd" -char "-"
New-Underline 
"原辰徳"
"AAAAA"
 | New-Underline

image

2012年1月20日金曜日

◆CSVの項目を数値としてソートする

こんなCSVファイルが有ったとして、

PS>Import-Csv test.txt

id     name    
--     ----    
3      sato    
5      suzuki  
13     yosida  
1      hara    
11     kubo    

これをidでソートすると、

PS>import-csv test.txt | sort id

id    name
--    ----
1     hara
11    kubo
13    yosida
3     sato
5     suzuki

文字列として解釈されているので、ちょっと残念な結果になってしまう。

こういう時は、PowerShell: ◆ソートのカスタマイズで使ったのと同じ方法で集計プロパティを使うと良い。

PS>Import-Csv test.txt | sort @{e={$_.id -as [int]}}

id    name   
--    ----   
1     hara   
3     sato   
5     suzuki 
11    kubo   
13    yosida 

2012年1月18日水曜日

◆グループ化して明細出力

グループ化というとどうしてもGroup-Objectコマンドレットを使ってしまいがちだが、

001
002
003
004

gsv | group status | %{
 
Write-host $_.name ("-" * 20) -ForegroundColor Green
  $_.group | select -expandprop name
}

image

Format-Tableコマンドレットなどにもグループ化機能があるので、そちらを使ったほうが便利な時もありそうだ。

001
gsv | sort status,name | ft name -GroupBy status

image

2012年1月6日金曜日

◆イメージファイルを変換する

イメージファイルのフォーマットを変換する関数を作って見ました。

とりあえず、bmp、jpg、pngに対応していますが、以下の種類に対応可能なようなので、
ImageFormat メンバ
必要に応じてパラメータを増やすか、上記メンバーを直接指定するように変更すると良いでしょう。

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030

function Convert-Image
{
 
[CmdletBinding(DefaultParametersetName="png")]
 
 
param
(
   
[parameter(Mandatory=$true)]
    [ValidateScript({Test-Path $_})]
    $path,
    [Parameter(ParameterSetName="bmp")]
 
   
[switch]$ToBmp,
    [Parameter(ParameterSetName="jpeg")]
 
   
[switch]$ToJpg,
    [Parameter(ParameterSetName="png")]
 
   
[switch]$ToPng
  )
 
 
$toimage = [System.Drawing.Imaging.ImageFormat]::($PsCmdlet.
ParameterSetName)
 
dir $path | %
{
   
$fname = $_.
fullname
   
$newExtension = "." + $PsCmdlet.
ParameterSetName
   
if($_.Extension -eq $newExtension){"not target:$($_.Extension)";exit
}
   
$newFileName =
 
     
$fname -replace $_.Extension , $newExtension
    $img = new-object system.drawing.bitmap $fname
    $img.save($newFileName,$toimage
)
   
$img.
Dispose()
   
Remove-Item $fname -Confirm
  }
}


Convert-Image  -path d:\Desktop\*.png -ToBmp

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をオーバーライドしたりするとスマートかなと思って書き始めたのだが、そちらはうまく行かなかった。

2011年12月29日木曜日

◆正規表現の基礎<アトミックゼロ幅アサーション>

<アトミックゼロ幅アサーション>

なんか難しい言葉ですが、要はパターンがどのような場所で一致するかを表す指定のようです。


^

文字列の先頭。複数行オプションが有効の場合は行の先頭。

PS>"abc`ndef" -match '^ab'
True
PS>"abc`ndef" -match '^de'
False
PS>"abc`ndef" -match '(?m:^de)'
True


$

文字列の末尾。複数行オプションが有効の場合は行の末尾。

PS>"abc`ndef" -match 'ef$'
True
PS>"abc`ndef" -match 'bc$'
False
PS>"abc`ndef" -match '(?m:bc$)'
True


\A

文字列の先頭でマッチ。 

PS>"abc`ndef" -match '\Aab'
True
PS>"abc`ndef" -match '(?m:\Ade)'
False

 


\Z

文字列の末尾または末尾の改行の前でマッチ。

PS>"abc`ndef" -match 'ef\Z'
True
PS>"abc`ndef" -match '(?m:bc\Z)'
False

 


\z

文字列の末尾でマッチ。

PS>"abcde`n" -match 'de\Z'
True
PS>"abcde`n" -match 'de\z'
False
PS>"abcde" -match 'de\z'
True


\G

先頭から最後まで指定したパターンの連続を表す指定のようです。
マッチしない箇所が出てきたらそこで終わり。
「Match」クラスなどを使って連続してパターンを検索するのに使用する。
以下はMSDNに載っていたサンプルをPowerShellに変換したものです。

連続マッチのサンプル
  1. $str = "capybara,squirrel,chipmunk,porcupin,gopher," +
  2.          "beaver,groundhog,hamster,guinea pig,gerbil," +
  3.          "chinchilla,prairie dog,mouse,rat"
  4. $pattern = '(\G\w+\s?\w*),?'
  5. $match = [Regex]::Match($str, $pattern)
  6. while ($match.Success)
  7. {
  8.     $match.Groups[1].Value
  9.     $match = $match.NextMatch()
  10. }

0または1個のカンマで区切られたワード文字(間にスペースが1つ有っても良い)を検索するパターン。ループしてマッチするものをすべて見つける。
image

途中でマッチしない箇所があるとそこで終了します。

  1. $str = "capybara,squirrel,chipmunk,porcupin,-gopher," +

image

\Gの指定がないときはマッチしない箇所は飛ばして最後まで継続します。


\b

ワード境界でのマッチ。

PS>"Power-Shell(V2)" -match '\bShe'
True
PS>"Power-Shell(V2)" -match '\bhe'
False
PS>"Power-Shell(V2)" -match 'hell\b'
True
PS>"Power-Shell(V2)" -match '\bhell'
False


\B

ワード境界以外でのマッチ。

PS>"Power-Shell(V2)" -match '\BShe'
False
PS>"Power-Shell(V2)" -match '\Bhe'
True

◆パイプライン入力・パラメータ入力対応のGridView出力関数を作る

PowerShell: ◆$inputの使い方PowerShell: ◆関数の作り方?で悩んでいたのですが、色々と試行錯誤の末、なんとなく解決策らしき方法を見つけました。

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038

function slo {
 
[CmdletBinding()]
            
 
param
 (            
   
[parameter(ValueFromPipeline=$true)]
 
   
[object[]]$InputObject,
   
   
[parameter(Position=0)]
          
   
[string[]]$firstprop
 
  )
 
begin
  {
   
$steppablePipeline = {Out-GridView}.
GetSteppablePipeline()
   
$steppablePipeline.Begin($true
)
   
$proplist =
 @() 
  }
 
process
 { 
   
$steppablePipeline.Process((select-orderedList
)) 
  }
 
end { $steppablePipeline.
End() }
}


function select-orderedList
{
 
if($proplist.length -eq 0
) 
  {              
   
$proplist = $firstprop
 
   
$Inputobject | Get-Member -MemberType Property |
 
   
foreach
 { 
      
if($proplist -notcontains $_.
Name) 
       { 
          
$proplist += $_.
Name 
       } 
    } 
  } 
 
$InputObject | select-object -property $proplist
}

slo name -InputObject (ps)
ps  | slo name

スクリプトブロックの「GetSteppablePipeline」メソッドで「SteppablePipeline」オブジェクトを作ってあげると、あたかもパイプライン中で使用しているかのように入力オブジェクトを続けて渡せるようになるようです。
「Process」メソッドで1つずつ渡して行き、終わったら「End」メソッドを呼ぶ、といった感じのようです。

だいぶ手探りなので正解かどうかは判りませんが、とりあえずGridViewがウィルスのように湧いて出てくるのは回避できています。(^^;

ちょっと前に、PowerShell: ◆スクリプトブロックを使うを書いた時このメソッドの存在には気づいていたのですが、使い方が今ひとつ判っていませんでした。

これで今年もすっきり終われる、と思ったのですがパラメータで指定した時の結果が、こんな感じになってしまいます。
image

PS> ,(Get-Process) | Out-GridView

のように渡した状態ですね。

「SteppablePipeline」とかの使い方がおかしいのかと色々とまた悩んだのですが、何のことはない、

PS> Out-GridView  -inputobject (ps) 

とやると同じ結果になるじゃないですか・・・。

ん~、どういうことなんでしょう。バグ?

またまた、色々と調べた挙句、よくよくヘルプを見ると、
image

などときっぱり書いてある・・・。
ん~、そりゃ実質パラメータ指定には対応していないってことじゃないですかい・・・。

なんの為にこんなに苦労してきたのか(TT)

悔しいので、とりあえず「Out-File」に変更して、

  1. $steppablePipeline =
  2.     {Out-file -FilePath d:\documents\wk.txt}.GetSteppablePipeline()

こんな感じで確認したところ、一応OKっぽい感じでした。

なんとなく、今ひとつ報われない結果となりましたが、色々と助言を頂きながらパイプライン・関数・スクリプトブロックと少し理解が深まったような気がします。