2011年11月28日月曜日

◆正規表現の基礎<量指定子>

<量指定子>

*

0回以上のマッチ。(最長マッチ)

PS>"abc" -match "a*";$matches[0]
True
a
PS>"aaaaabc" -match "a*";$matches[0]
True
aaaaa
PS>"baaaaabc" -match "a*";$matches[0]
True

PS>"bbc" -match "a*";$matches[0]
True

*は直前文字の0回以上の繰り返しなので基本的には何でもマッチしてしまう。
これだけだと「*」の存在価値が見えてこないのでMSDNのサンプルをちょっと調べてみた。

PS>"92" -match "91*";$matches[0]
True
9
PS>"91" -match "91*\b";$matches[0]
True
91
PS>"92" -match "91*\b";$matches[0]
False

はっきり言って正規表現初心者の私には難しい内容だが、
最初の例は「1」はあってもなくても良いので「9」がマッチ。
2番目の例(\bはワード境界で終わっている指定)は「91」がマッチ。
っで、3番目だが、「1」自体はあってもなくても良いのだが無いとなると結果的に「9」が終端になっていなければいけない、という事でFalseになるという事と解釈した。

ちなみに、最長マッチというのは、”aaa” –match “a*”としたときに、「a」、「aa」、「aaa」とマッチする中の一番長い「aaa」とのマッチを採用しますってことでしょう。


1回以上のマッチ。(最長マッチ)

PS>"aabbcc" -match 'b+';$matches[0]
True
bb
PS>"aabbccbbb" -match 'b+';$matches[0]
True
bb
PS>"aabbccbbb" -match 'aab+';$matches[0]
True
aabb
PS>"aabbccbbb" -match 'd+';$matches[0]
False

 


?

0回または1回のマッチ。(最長マッチ)

PS>"ba" -match 'a?';$matches[0]
True

PS>"aa" -match 'a?';$matches[0]
True
a

PS>"第1章" -match '第\d\d?章'
True
PS>"第10章" -match '第\d\d?章'
True
PS>"第1a章" -match '第\d\d?章'
False
PS>"第章" -match '第\d\d?章'
False

?も*と同じく0回も含むので、後ろにパターンが続くときに使うケースが多いのかと思う。


{n}

n回のマッチ。

PS>"abba" -match 'ab{2}';$matches[0]
True
abb


{n,}

n回以上のマッチ。 (最長マッチ)

PS>"abbba" -match 'ab{2,}';$matches[0]
True
abbb


{n,m}

n回以上m回以下のマッチ。(最長マッチ)

PS>"abbbba" -match 'ab{1,3}';$matches[0]
True
abbb


?

これまでの量指定子の後ろに付加して最短マッチを表す。

*?,+?,??,{n,}?,{n,m}?

PS>"abbcd" -match 'ab*';$matches[0]
True
abb
PS>"abbcd" -match 'ab*?';$matches[0]
True
a

PS>"abbcd" -match 'ab+';$matches[0]
True
abb
PS>"abbcd" -match 'ab+?';$matches[0]
True
ab

PS>"abbcd" -match 'ab?';$matches[0]
True
ab
PS>"abbcd" -match 'ab??';$matches[0]
True
a

PS>"abbcd" -match 'ab{1,}';$matches[0]
True
abb
PS>"abbcd" -match 'ab{1,}?';$matches[0]
True
ab

PS>"abbbba" -match 'ab{1,3}';$matches[0]
True
abbb
PS>"abbbba" -match 'ab{1,3}?';$matches[0]
True
ab

??などは今ひとつ使い所がピンと来ないが意味合い的には難しくない。

最小マッチのサンプルとして以前、PowerShell: ◆Split演算子で書いたサンプルをそのまま載せておく。

PS>gc "C:\Documents and Settings\minminnana\デスクトップ\test.txt" |
tee-Variable str
<html>
<h1>見出し1</h1>
<h2>見出し2</h2>
</html>
PS>$str -split "<.*?>" | %{if($_ -ne ""){$_}}
見出し1
見出し2

最短マッチの指定をしないと行の先頭の”<”と最後の”>”にマッチして行全体が区切り文字と認識されてしまう。

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("処理完了")

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

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

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

2011年11月23日水曜日

◆正規表現の基礎<文字クラス>

これまで正規表現については必要になった都度に適当に調べて適当に使っていた。
.NETではあまり文字列を扱うようなプログラムを組む機会が多くなかった。
しかし、Powershellを使うようになってから何かと必要に迫られることが多くなってきたので、ここはひとつ基本的な所は勉強してみようと思い立った。Powershellは後発の為かあまり正規表現について詳しく解説しているサイトが見当たらない。仕方がないので他の言語で説明しているサイトを参考にしながら地道に試していこうと思う。
まずは文字クラスから。

<文字クラス>


.(ピリオド)


改行文字(\n)以外の任意の一文字。(ただし [] 内ではピリオド文字。)
「.」
「」内に任意の1文字がある箇所にマッチ

PS>"Powershell" -match "Power.hell"
True
PS>"Powershell" -match "Power..hell"
False
PS>"Powershellよ" -match "Power.hell"
True
PS>"" -match ".*"
True
PS>" " -match "."
True
PS>"A.A" -match "A\.A"
True

*は直前文字の0回以上の繰り返しなので空文字にもマッチしてしまう。
ピリオド自体を表すには直前に¥をつけてエスケープする。
 


[characters]

[ ] 内の任意の文字。

PS>"abc" –match '[cde]'
True
PS>"abc" –match '[def]'
False

 


[^characters]

[ ] 内の任意の文字以外の文字。

PS>"abc" -match '[^cab]'
False
PS>"abcd" -match '[^cab]'
True

 


[開始文字-終了文字]

開始文字から終了文字の範囲内にある文字。複数のセットを繋げて指定もできる。

PS>"c" -match '[a-d]'
True
PS>"c" -match '[c-d]'
True
PS>"c" -match '[d-z]'
False
PS>"C" -match '[c-d]'
True
PS>"f" -match '[c-de-g]'
True

 


[^開始文字-終了文字]

開始文字から終了文字の範囲外にある文字。

PS>"abc" -match '[^cab]'
False
PS>"abcd" -match '[^cab]'
True

 


\p{文字クラス}

文字クラスにはUNICODEに対して付けられているカテゴリー名、もしくはグループ名を指定する。
カテゴリー名とグループ名については以下のMicrosoftのサイト(の下の方)に記載されている。

文字クラス

実際にマッチする文字は、カテゴリブロックを参照。

ちなみに、「Lu」は大文字のキャラクタと説明があるのだが、Powershell(というか.NET?)では小文字もマッチするようだ。
image

PS>"a" -match '\p{Lu}'
True
PS>"aA(カ山" -match '\p{IsHiragana}'
False
PS>"あ" -match '\p{IsHiragana}'
True

<補足・訂正>
おもいっきり間違っていました。
大文字小文字を区別する場合は-matchではなく-cmatchを使うとのご指摘を頂きました。(cが付く演算子は幾つかあるが、matchにcが付くのは知りませんでした。というかHELPには載っていないような・・・)

ということでサンプルを修正しました。

PS>"a" -cmatch '\p{Lu}'
False
PS>"A" -cmatch '\p{Lu}'
True
PS>"あア亜" -match '\p{IsHiragana}';$matches[0]
True

PS>"あア亜" -match '\p{IsKatakana}';$matches[0]
True

PS>"あア亜" -match '\p{IsCJKUnifiedIdeographs}';$matches[0]
True

 


\P{文字クラス}

\p{文字クラス}の否定形。


\w

任意のワード文字。
ワード文字とは英数字とアンダーバーを指し、[a-zA-Z_0-9]と同義。

PS>"()=~|{`@}*+?" -match '\w'
False
PS>"a" -match '\w'
True
PS>"A" -match '\w'
True
PS>"_" -match '\w'
True


\W

非ワード文字。


\s

空白文字。
空白文字とは、タブ、スペース(全角含む)、改行
(厳密にはまだあるみたいだが、普通に使うのはこんなところ?)

PS>" " -match '\s'
True
PS>"`t" -match '\s'
True
PS>"`r" -match '\s'
True
PS>"`n" -match '\s'
True
PS>" " -match '\s'
True
PS>"A" -match '\s'
False
PS>"aあア([\9" -match '\s'
False


\S

空白文字以外。


\d

0から9までの数字(全角を含む)

PS>@(0..9;65296..65305 | %{[char]$_};"A","B") | %{"`n対象文字 match"}{Write-Host $_ `t ($_ -match '\d')}

image


\D

数字以外。

2011年11月18日金曜日

◆SQLServer データベースの復旧モデルを更新する

SharePointを入れると様々なDBがインストールされる。
復旧モデルが「完全」になってたりするとログの容量が増え続けて困ってしまう。

評価・テスト用だったりすると「完全」である必要はないので「単純」に変更する。
評価用にスクラップ&ビルドしてたりすると何度もやるはめになるのでスクリプトにしてみた。

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

$Instance = ".\sqlexpress"
[void][Reflection.Assembly]::
LoadWithPartialName(
                                 
"Microsoft.SqlServer.Smo") 
#Full,BulkLogged,Simple
$cModel = [Microsoft.SqlServer.Management.Smo.RecoveryModel]::Simple

$server = 
New-Object Microsoft.SqlServer.Management.Smo.Server($Instance)
$server.ConnectionContext.LoginSecure = $true 
$server.Databases | 
  ?{-not $_.IsSystemObject} | 
  ?{$_.RecoveryMode -ne $cModel } | 
  %{$_.RecoveryModel = $cModel ; $_.Alter()}
"Done"

プロパティを変更した後にAlterメソッドを呼んであげないと変更が反映されないようだ。

2011年11月17日木曜日

◆プロパティのToStringメソッドをオーバーライドする

ちょっと面白いスクリプトがあったので紹介。
Changing Units - Power Tips - Powershell.com Powershell Scripts, Tips and Resources

コードは以下のとおり。

001
002
003
004
005
006
007
008
009
010
011

dir $env:windir | 
Select-Object Mode, LastWriteTime, Length, Name | 
ForEach-Object
 { 
 
if ($_.Length -ne $null
) { 
   
$_.Length = $_.Length |
 
     
Add-Member ScriptMethod ToString
 { 
          (
'{0:0.0} KB' -f ($this/1KB
)) 
      } 
-Force -pass
 
  }
 
$_
} | Sort-Object Length

ファイルのレングスをKB単位で編集したいんだけど、そこで文字列に編集しちゃうとその後ソートが上手くいかない。
なのでLengthはそのままに、LengthのToStringメソッドをオーバーライドしちゃいましょうって感じ。
ToStringのオーバーライドはC#とかであれば良くやることだと思うがPowershellでは、あまりそういう発想がなかった。

また、this自動変数なんかも、こんな時につかうのかぁって感じ。

ただ、素直にやるなら集計プロパティを使えば良いだけのような気もする。(その後にまた処理が続くのであれば上記が有効の局面もあるのでしょう)

001
002
003
004
005
006
007
008
009
010

dir $env:windir | 
Select-Object Mode, LastWriteTime, Length, Name |
sort-Object Length |
  select Mode, LastWriteTime,
 
    @{
      name
="Length"
      expression=
{
       
if($_.length){'{0:0.0} KB' -f ($_.length/1KB
)}
      }
    }
, Name

2011年11月11日金曜日

◆SharpZipLibを使ってZip圧縮

以前作ったサンプルは非同期に対応できず今ひとつだった。
(それでも今毎日動かしてはいるが)

ケースによってはもう少しまっとうに動く奴が欲しかったりするので、またちょっと調べてみたところ、SharpZipLibを使ったパターンが結構目に付く。

そこで、このdllを使ったスクリプトを試して見た。

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

#ex. dir *.txt | New-ZipFile.ps1 c:\temp\temp.zip
param
(
 
$zipPath = "D:\Desktop\temp.zip"
)
$libName = "ICSharpCode.SharpZipLib"
$namespace = "$libName.Zip.{0}"
$dllPath =
 
 
Join-Path (Split-Path $myInvocation.MyCommand.path) "$libName.dll"
[Void][Reflection.Assembly]::LoadFile($dllPath)
$zipFile =
 
 
New-Object ($namespace -f "ZipOutputStream") ([IO.File]::Create($zipPath))
[byte[]] $buffer = New-Object byte[] 4096
$input | ?{$_ -is [IO.FileInfo]} | %
{
 
$zipEntry = New-Object ($namespace -f "ZipEntry") $_.
name
 
$zipFile.PutNextEntry($zipEntry
)
 
$_.
fullname
 
$fileStream = [IO.File]::OpenRead($_.
FullName)
 
[ICSharpCode.SharpZipLib.Core.StreamUtils]::
Copy(
                                              
$fileStream,$zipFile,$buffer
)
 
$fileStream.
Close()
}

$zipFile.close()

dllはスクリプトと同じ場所にある前提。
とりあえずはファイルだけを対象にしている。

ちょっと試した感じでは良さそうだが、前回のこともあるので色々テストする必要はありそうだ。

--追記

SharpZipLibにはFastZipという簡易的なクラスも用意されているようなので、そのサンプルも載せておく。

001
002
003
004
005
006
007
008
009

#◆圧縮
$targetPath = "F:\Desktop\comp"
$outZipFile = "F:\Desktop\comp.zip"
$dllPath = "F:\Desktop\ICSharpCode.SharpZipLib.dll"
[Void][Reflection.Assembly]::LoadFile($dllPath)
$zipFile =
 
 
New-Object ICSharpCode.SharpZipLib.Zip.FastZip
$zipFile.CreateEmptyDirectories = 
$true
$zipFile
.CreateZip($outZipFile,$targetPath,$true,$null,$null)

2011年11月10日木曜日

◆システムモーダルなメッセージボックス(MessageBox)を使う

Powershellから表示しているメッセージボックスが他のウインドウに隠れて気づかないことがあるとの相談を受けた。

ん~、コマンドウインドウにフォーカスを与えれば良いのかな・・・。
それなら前にやったことがあるなぁ、と思いちょっと調べてみた。(なんか簡単に出来た記憶がある)

しかし、見つからない・・・。
自分のブログとかも見てみたが、それらしい記述は無し。
多分、ブログに書くほどでもなかったんだよなぁ・・・、と思いながらネットを調べるもAPIを使ったサンプルばかり。
それほどじゃなかったんだよな・・・。
考えていくうちに、MessagaBox自体モーダルじゃないからコマンドウインドウにフォーカスしても駄目だよなぁ、メッセージボックス自体システムモーダルとかで出してやれば話は早いと思い始めた所で思い出した。

VBの関数だ!!

そう、VBの世界ではシステムモーダルとか簡単に出来たので、なんてことのない機能と思っていたんだ。
最近、VB頭はどんどん減衰しているので以前は取るに足らない機能と思っいたにも関わらず思い出せない状態になっていた。

という事で忘れないようにメモしておく。

PS>[Void][Reflection.Assembly]::LoadWithPartialName("Microsoft.VisualBasic")
PS>[Microsoft.VisualBasic.Interaction]::MsgBox("Prompt",([Microsoft.VisualBasic.MsgBoxStyle]::Yes -bor [Microsoft.VisualBasic.MsgBoxStyle]::SystemModal),"Title")

ちなみに、最初に探していたフォーカス設定もVBの関数で可能だ。

Interaction クラス

2011年11月9日水曜日

$host.UIのプロンプト入力機能を使う2(補足)

PowerShell: ◆$host.UIのプロンプト入力機能を使う2ではジェネリックな「System.Collections.ObjectModel.Collection」を手作りしていたのだが、明示的にジェネリックなクラスを使わなくても暗黙にキャストしてくれることが判った。

今回の例で行くと、
System.Collections.ObjectModel.Collection<ChoiceDescription>を指定すべきところに、ChoiceDescription[]を指定すればよさそうである。

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

$class = "System.Management.Automation.Host.ChoiceDescription"
$dClass = [type]
$class
$dClassArray
 = [type]($class+"[]")

$questions =
 
 ((
"&1:巨人"),("読売ジャイアンツ")),(("&2:阪神"),("阪神タイガース")),
 (("&3:中日"),("中日ドラゴンズ")),(("&4:横浜"),("横浜ベイスターズ")),
 (("&5:ヤクルト"),("東京ヤクルトスワローズ")),(("&6:広島"),("広島東洋カープ"))
$descriptions = ($questions | %{New-Object $dclass $_}) -as 
$dClassArray

$caption
 = "<アンケート>" 
$message = "好きな球団はどこですか?"
 
 

$result =  $host.UI.PromptForChoice($caption, $message, $descriptions,2) 
Write-Host $descriptions[$result].Label.replace("&","") "ですね"
$winner = {Get-Random (0..($questions.Count-1) | %{$questions[$_][1]})}
"`n来年以降の優勝予想は"
1..4 | %{invoke-command $winner}
"となっています。"
$winner2 = Set-PSBreakpoint -Variable winner2 -Mode Read -Action
 { 
 
$script:winner2 = Get-Random (0..($questions.Count-1) | 
%{$questions[$_][1]})}
"`n2位予想は"
1..4 | %{$winner2}
"となっています。"

2011年11月8日火曜日

◆マイミュージック(MyMusic)の曲から詳細プロパティを取得する

娘に頼まれてWalkmanに曲を入れてやったのだが(ファイルコピーで入れれるのでWalkmanは簡単だ。iPodはソフトが重くて敵わん)、曲名が表示されないものがあるとのこと。

ん~、何でしょう。
私は音楽プレーヤーなんて使わないのでよく分からない・・・。

調べてみるとどうやら単純にファイル名を表示しているわけではなく詳細プロパティにある情報から表示しているようだ。
image

曲名が表示されないファイルを見ると、このタイトルが入っていない。
image

エクスプローラーで入力もできるのだがタイトルだけであればファイル名からコピーできれば簡単だ。

そこで、ココらへんの情報を取ってこれるのか調べてみたのだが、どうやらShell.Applicationで可能なようだ。

プロパティの値自体は取れたのだがプロパティ名称の取り方が判らずちょっと苦労した。

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

#$path = "D:\Music\Hey!Say!JUMP"
$path = "D:\Music"
$MusicItems = @()

$shell = New-Object -Com Shell.Application
dir $path -r | ?{-not $_.PsIsContainer} | %
{
 
$filename = $_.
name
 
$dirname = $_.
DirectoryName
 
$folderobj = $shell.NameSpace($dirname
)
 
$item = $folderobj.ParseName($filename
) 
 
$MusicItem = New-Object PsObject |
 
   
Add-Member NoteProperty ファイル名 $_.name -pass
  0..40 | %
{
   
$MusicItem |
 
   
Add-Member NoteProperty
 `
     
-Name $folderobj.GetDetailsOf($folderobj.Items(),$_
) `
     
-Value $folderobj.GetDetailsOf($item,$_
)
  } 
 
$MusicItems += $MusicItem
}

$MusicItems | ft ファイル名,アルバム,タイトル
<!-
image

あとは更新の仕方となるのだが、時間が無いので後で調べてみる。

プロパティ名称とかは違うだろうがMusicファイル以外にも使えそうだ・・・。