2016/12/20
AST Visitorを使った静的解析
この記事はPowerShell Advent Calendar 2016の20日目です。
はじめに
前々回はASTの概要について述べ、最後にAST.FindAllメソッドを使って、ASTから指定のASTノードを検索する方法について説明しました。
前回はASTを再帰的に検索して、木構造を視覚化してみました。
今回もASTを検索する話なのですが、静的解析機能を実装するためのAST Visitorを用いる方法について説明します。が、あらかじめお断りしておきますが、静的解析の実装までは今回はたどり着きません。静的解析ツールをどう作るかorどう作られているか、ということを雰囲気で味わっていただければと。
Visitorパターン
AST Visitorの説明をする前に、まず、Visitorパターンについて簡単に。
Visitorパターン[Wikipedia]というのは、オブジェクト指向言語におけるデザインパターンの1つで、対象オブジェクトを巡回する「訪問者」クラスを定義するものです。Visitorクラスでは、対象クラスごとに行う処理を、個別にvisitメソッドをオーバーロードさせることで定義します。共通のVisitor抽象クラスを継承することで、異なる機能を持ったVisitorクラスを作ることができます。
一方、処理対象クラスには、Visitorオブジェクトを引数に受け取る、acceptメソッドを定義します。acceptメソッドでは、引数として受け取ったVisitorオブジェクトのvisitメソッドを呼ぶことで、処理を実行させます。
なお、処理対象クラスが子要素クラスを持つ場合には、acceptメソッド内で、子要素クラスのacceptメソッドを呼ぶようにします。こうしておくことで、Visitorは処理対象を再帰的に巡回できるようになります。
このように処理対象クラスから、実際に処理を行う機能をVisitorクラスとして分離することで、処理対象クラスに手を加えることなく、Visitorクラスを追加して、処理内容を増やしたりすることが可能になります。
AST Visitorの呼び出し
Visitorパターンを念頭において、AST Visitorの呼び出し方を見ていきましょう。Ast抽象クラスには、以下の2つのVisitメソッドが定義されています。
説明に入る前に注意点。メソッド名は"Visit"となっていますが、Visitorパターンでいうところの"accept"メソッドのことです。なぜメソッド名がAcceptじゃないのかは不明ですが…。
ともかく、AstクラスのVisitメソッドは、AstVisitor抽象クラスを継承したクラスのオブジェクトか、ICustomAstVisitorインターフェースを実装したクラスのいずれかを引数に取ることで、ASTに対する処理を実施します。
AstVisitor抽象クラスを継承、もしくはICustomAstVisitorインターフェースを実装することで、ASTの種類に応じた巡回処理を行うクラスを、自分で定義していきます。
AstVisitor抽象クラス
AstVisitor抽象クラスは、Visitorとしての基本的な機能があらかじめ実装されています。具体的には既に以下の機能は用意されています。
- ASTの種類に応じたVisitメソッドの定義
すべての種類のASTに対応するVisitメソッド(50個以上)がVirtualメソッドとして定義されています(※)。例えば、IfStatementAstに対する処理を行うための、VisitIfStatementメソッドがあります。※一般的なVisitorパターンでは、Visitメソッドを対象クラス分オーバーロードさせますが、PowerShellのAstVisitorは対象クラスに応じた別名のメソッドを定義する方式です。これも理由は分かりませんが、オーバーロードにするには多すぎるからかもしれません。
- 子ノードの再帰的な巡回
各Visitメソッドには、ASTの子ノードに対し、再帰的にVisitメソッドを呼ぶ仕掛けがあらかじめ備わっています。 - ノード巡回の停止
各VisitメソッドはAstVisitAction列挙型を返却します。以下のように返却する値によって、ノード巡回の継続、停止を制御できます。- Continue:ノード巡回を継続(デフォルト)
- SkipChildren:子ノードの巡回を行わない
- StopVisit:巡回を終了する
カスタムAstVisitorクラスを作成する
以上の基本的な機能を踏まえて、AstVisitor抽象クラスを実装したカスタムVisitorクラスを作ります。C#で書くのが一般的ですが、せっかくなのでPowerShell v5で追加された、クラス構文を使って書いてみましょう。
例えば、「利用しているコマンドのリストを取得する。ただし、コマンドのパラメータ内で別コマンドを呼び出している場合は除く。」というお題を解くことを考えます。
ASTのFindAllメソッドだと、配下に含まれるすべてのCommandAstを取得してしまうので、単純にはいきません。そこでカスタムAstVisitorクラスの出番です。
このお題を実現するVisitorクラスは以下のようになるでしょう。
using namespace System.Management.Automation.Language class GetCommandNamesVisitor : AstVisitor { [string[]]$CommandNames = @() [AstVisitAction]VisitCommand([CommandAst]$commandAst) { $this.CommandNames += $commandAst.CommandElements[0].Extent.Text return [AstVisitAction]::SkipChildren } }
PowerShellのクラス構文において、Virtualメソッドのオーバーライドは、単に同名のメソッドを定義するだけですので、ここではVisitCommandメソッドをオーバーライドします。
プロパティとフィールドの区別はないので、コマンド名の一覧を格納するCommandNamesプロパティは上記のような定義になります。メソッド内でクラスメンバを参照する際には$thisを用います。
作成したGetCommandNamesVisitorクラスをインスタンス化し、解析対象スクリプトブロックのASTのVisitメソッドに引数として渡します。
$scriptBlock = { $files = Get-ChildItem -Path (Get-Location | Split-Path -Parent) -File $files | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 5 } $visitor = New-Object GetCommandNamesVisitor $scriptBlock.Ast.Visit($visitor) $visitor.CommandNames
実行すると、結果は
Get-ChildItem Sort-Object Select-Object
のようになるかと思います。
AstVisitorクラスの具体的な実装については、PSReadLineやPowerShellEditorServicesにありますので、参考にしてみてください。
ICustomAstVisitorの実装
前項で述べた、AstVisitor抽象クラスを継承したカスタムAstVisitorクラスの場合、基本的な処理を実装する必要はないですし、目的とするASTクラスに対するVisitメソッドだけオーバーライドすればいいので、非常に簡便です。
ただ、本格的にPowerShellの構文解析を行いたい場合、ノードの巡回順だとか、その他もろもろをもっと細かく自分で実装したいケースが出てきます。
そういった場合にはICustomAstVisitorインターフェースを実装したクラスを作って対応します。ICustomAstVisitorインターフェースも、AstVisitor抽象クラス同様、各ASTクラスに応じたVisitメソッドが定義されているのですが、各VisitメソッドはAstVisitAction列挙体ではなく、object型のオブジェクトを返します。つまり、自分で好きなオブジェクトを返すように定義できるわけです。
Ast.Visit(ICustomAstVisitor)はAstVisitor抽象クラスを引数に取る場合と異なり、objectを返却するのですが、このとき返却されるのは、最初に実行されたVisitメソッドの戻り値になります。
ICustomAstVisitorはインターフェースですので、処理はすべて自分で定義しなくてはなりません(※)。ノードの再帰的探索も、必要ならもちろん自前で実装する必要があります(前回紹介した、JSON化スクリプトのような処理になるかと思います)。
※ISEだとインターフェースの実装を一発で行うリファクタリング機能はないので、今回みたく実装すべきメンバがたくさんある場合は、こんな感じのひな形を作るスクリプトを使うと良いでしょう。
今回はICustomAstVisitorインターフェースを実装したクラスの実例まではご紹介できませんでしたが、興味のある方は、PSScriptAnalyzerで用いられているので参考にしてみてください。
まとめ
PowerShellのASTについてきちんと解説している記事が英語圏を含めてもあまりないようでしたので、3回に渡って、一通りの基礎知識をまとめてみました。
普通にPowerShellを使っている分には、滅多に使うことはないと思いますが、たとえばPSScriptAnalyzerのカスタムルールを自分で作る場合には、ASTの知識は必須になってきますので、必要に応じて参考にしていただければ幸いです。
2015/10/18
JapanesePhoneticAnalyzerを使ってPowerShellで形態素解析(中編)
前回の続きです。今回は、前回作ったGet-JpWord関数を使って取得した、読み仮名を利用する話になります。
文章の読み仮名を表示する
単に指定文章の読み仮名を表示するという用途には、前回作製したGet-JpWord関数はそのままだと使いにくいので、さらにラップした関数を用意しました。
function Get-JpYomi { param( [parameter(ValueFromPipeline=$true)] [PSObject[]] $InputObject, [string] $Separator = "", [ValidateSet("YomiOnly", "Furigana", "RubyTag")] [string] $Format = "YomiOnly" ) process { foreach($o in $InputObject) { $phenemes = if($o -is [Windows.Globalization.JapanesePhoneme]) { @($o) } else { $o.ToString() | Get-JpWord -MonoRuby:$($Format -eq "RubyTag") } ($phenemes | foreach { if($Format -eq "YomiOnly") { $_.YomiText } else { if($_.DisplayText -match "\p{IsCJKUnifiedIdeographs}") { if($Format -eq "RubyTag") { "<ruby><rb>$($_.DisplayText)</rb><rt>$($_.YomiText)</rt></ruby>" } elseif($Format -eq "Furigana") { "$($_.DisplayText)($($_.YomiText))" } } else { $_.DisplayText } } }) -join $Separator } } }
Get-JpYomi "読み仮名を表示したい文章をここに書きます。"
のようにすると、以下のような結果が表示されます。
よみがなをひょうじしたいぶんしょうをここにかきます。
また、Separatorパラメータを指定すると、読み仮名に区切り文字を挿入することもできます。例えば、
Get-JpYomi "読み仮名を表示したい文章をここに書きます。" -Separator " "
とすると結果は、
よ み がな を ひょうじ し たい ぶんしょう を ここ に か き ます 。
のようになります。
単に読み仮名を知りたい時に便利かと思います。漢字の読み方を知りたい時にさくっと使うのもいいかも。(読み方が複数あっても1つしか表示されませんが)
文章に振り仮名を付けたい時には、以下のようにします。
Get-JpYomi "読み仮名を表示したい文章をここに書きます。" -Format Furigana
結果はこうなります。
読(よ)み仮名(がな)を表示(ひょうじ)したい文章(ぶんしょう)をここに書(か)きます。
ここでポイントとなるのは、漢字部分は振り仮名を付けるが、それ以外には必要ないので付けないという処理です。それには正規表現"\p{IsCJKUnifiedIdeographs}"を用いています。\p{}は正規表現の「名前付き文字クラス」と呼ばれるもので、IsCJKUnifiedIdeographsというのはUnicodeにおける漢字を表すブロック名になります。ちなみにIsHiraganaでひらがな、IsKatakanaでカタカナにマッチさせることもできます。
Formatパラメータは、デフォルトのYomiOnly(読み仮名のみ出力)、Furigana(入力に振り仮名を付けて出力)の他に、RubyTag(HTMLのrubyタグを生成して出力)というのを用意しています。これを使うと、
<ruby><rb>読</rb><rt>よ</rt></ruby>み<ruby><rb>仮</rb><rt>が</rt></ruby><ruby><rb>名</rb><rt>な</rt></ruby>を<ruby><rb>表</rb><rt>ひょう</rt></ruby><ruby><rb>示</rb><rt>じ</rt></ruby>したい<ruby><rb>文</rb><rt>ぶん</rt></ruby><ruby><rb>章</rb><rt>しょう</rt></ruby>をここに<ruby><rb>書</rb><rt>か</rt></ruby>きます。
という出力が得られます。実際にHTMLとして表示させると以下のようになります。
50音順ソート&グループ化
Get-JpYomi関数の応用例的なものになります。漢字の読みが分かるということは、50音順にソートしたり、頭文字でグループ化できるようになるということですね。やってみましょう。
ディレクトリ名を50音順でソートするには、
dir -Directory | sort {$_.Name | Get-JpYomi}
のようにします。例えば私のミュージックフォルダ(アーティスト名ごとにフォルダが作られている)に対して実行すると以下のようになります。
うーん、アニソンばっかりだな…。それはともかく、ちゃんと50音順ソートされていることがお分かりいただけるかと思います。中には読み方間違ってるやつもありますが。
頭文字でグループ化して表示するというのもやってみましょう。
dir -Directory | select Name, @{Name = "NameYomi"; Expression = {$_.Name | Get-JpYomi}}, @{Name = "FolderCount"; Expression = {@(dir $_.FullName -Directory).Length}} | group {$_.NameYomi.Substring(0,1)}| sort Name | foreach { Write-Host "【$($_.Name)】($($_.Count))" $_.Group|%{ Write-Host "$($_.Name)($($_.NameYomi)) $($_.FolderCount)" } Write-Host }
まずファイル名、読み、サブフォルダの数(当方環境ではアルバム数に対応)をそれぞれプロパティとして抽出し、読みの頭文字(SubString(0,1)で取得)でグループ化し、頭文字グループを50音順ソートし、結果を表示しています。
結果はこんな感じです。
まあ頑張って読んでいる方だと思います。むらかわなしぎぬ…。
また長くなったので次回に続きます。次回は「分かち書き」の話。
2014/05/02
今日のワンライナー その1:配列から最も要素数の多い要素を取りだす
また続くかどうか不明の新シリーズ。今日書いたワンライナーを記録していきます。
今回は、配列に含まれる要素のうち、もっとも出現頻度の多いものを調べる方法です。たとえば、0, 0, 0, 1, 0, 3, 0, 2, 0, 2という配列がある場合、「0」は6個含まれており最も要素数が多いので、この場合「0」を出力します。
ワンライナーは以下のようになります。(配列変数の宣言を合わせると2ラインですけど)
$array = 0, 0, 0, 1, 0, 3, 0, 2, 0, 2 $array | group | sort Count -Descending | select -First 1 | select -Expand Name
今回のケースのように、「配列内に同一の要素が含まれており、要素ごとに纏める」という手順が必要なときはGroup-Objectコマンドレット(エイリアス:group)の出番です。
$array | group とすると、
Count Name Group ----- ---- ----- 6 0 {0, 0, 0, 0...} 1 1 {1} 1 3 {3} 2 2 {2, 2}
のような出力が得られます。Count=出現回数、Name=要素、Group=該当する要素のリスト です。なのでこの出力だけでお題の解答である「0」が確認できます。あとはこの出力から求める値を抽出するだけです。
Group-Objectコマンドレットの出力はGroupInfoというオブジェクトです。そう、これもオブジェクトなので、他のオブジェクト同様、プロパティ情報を保ったまま後続パイプラインに渡すことができます。
まずSort-Objectコマンドレット(エイリアス:sort)を用いてGroupInfoのCountプロパティの値で降順ソート(-Descendingパラメータ使用)をかけます。するとCountプロパティが一番大きいGroupInfoオブジェクトが一番最初の要素となります。(今回の場合はたまたまソート前後で最初の要素が同じでしたが)
次にSelect-Objectコマンドレット(エイリアス:select)に-First 1と指定することで一番最初のGroupInfoオブジェクトのみ取得します。最後に、GroupInfoオブジェクトのNameプロパティの値だけを取りだすのにSelect-Objectコマンドレットに -ExpandPropertyパラメータを指定します。
ところでGroupInfoオブジェクトのGroupプロパティ、どうせNameと同じものがCount分列挙されるだけじゃないか何の意味が?と思われるかも知れないので補足です。Group-Objectコマンドレットには-Propertyパラメータ(位置パラメータなのでパラメータ名は省略可能)が定義されており、指定すると任意のプロパティ値に基づいてグループ化してくれます。
たとえば
dir | group Extension
とすると、カレントディレクトリに含まれるファイルが拡張子別にグループ化され、以下のような出力が得られます。
Count Name Group ----- ---- ----- 1 .gadget {twitterpost.gadget} 59 .vbs {7zip_fix_archive.vbs, 7zip_store_each.vbs...} 72 .ps1 {cddrive.ps1, clipboard.ps1, cmdlets.ps1...}
このようにGroupプロパティの中身は、指定プロパティ値を持つ要素のグループとなっていることが分かると思います。
-Propertyパラメータには集計プロパティを指定することもできるので、
1..10 | group {if($_ % 2 -eq 0){"偶数"}else{"奇数"}}
Count Name Group ----- ---- ----- 5 奇数 {1, 3, 5, 7...} 5 偶数 {2, 4, 6, 8...}
みたいなこともできたりします。
ちなみにGroup情報が不要であるときは、Group-Objectコマンドレットに -NoElementパラメータを付与すると出力を抑制できます。(この場合、出力はGroupInfoオブジェクトではなく、GroupInfoNoElementオブジェクトとなる)
なんか後半はGroup-Objectコマンドレット特集みたくなってしまいました。ではまた次回。
2014/04/20
集計プロパティの簡易指定
Select-Object、Format-Table、Sort-Objectなど、-Propertyパラメータを持つコマンドレットには、プロパティ名を指定する以外にも「集計プロパティ」という連想配列を指定することができます。
集計プロパティを利用すると、オブジェクトが持っていないプロパティを動的に作成し、その値を表示することができるようになります。
たとえば、dir (Get-ChildItem)の出力するファイルリストに、テキストファイルとして開いた場合の1行目の記述(Textプロパティとする)も合わせて表示したい場合は以下のようにします。
dir | select Name, Extension, @{Name="Text"; Expression={$_|gc|select -First 1}}
出力は以下のようになります。
Name Extension Text ---- --------- ---- test1.ps1 .ps1 param([Parameter(ValueFro... test2.ps1 .ps1 $x=1231 test3.ps1 .ps1 Start-Transcript test4.ps1 .ps1 Get-Content .\gacha_log.t...
ここで、@{Name="Text"; Expression={$_|gc|select -First 1}}と指定している部分が集計プロパティです。連想配列のキーとしてプロパティ名を表すNameと、スクリプトブロックを指定するExpressionを含めます。なお、キー名のNameはN、ExpressionはEと省略表記できるので、
dir | select Name, Extension, @{N="Text"; E={$_|gc|select -First 1}}
と書くこともできます。
ここまではヘルプにも載っていることなのでご存知の方も多いと思います。ただ、連想配列を指定してごにょごにょ、というのは(特にインタラクティブ実行時には)正直だるいです。そこでもっと楽な書き方はないものかと思って何気なく、
dir | select Name, Extension, {$_|gc|select -First 1}
とプロパティとしてスクリプトブロックをそのまま書いたら、普通に通りました。これ、知ってました? 私は知らなかったです。しかしv2環境で実行しても動いたので前からあったんですね。なぜヘルプに載ってないのか…。それともどこかに載ってるんでしょうか…。
ただしこの方法だと、集計プロパティでプロパティ名(Nameキー)を指定しない場合と同様、以下のようにプロパティ名がスクリプトブロックの定義そのままになります。
Name Extension $_|gc|select -First 1 ---- --------- --------------------- test1.ps1 .ps1 param([Parameter(ValueFro...
そのため、コマンドの出力を変数に入れて利用する場合などはこの方法は不適です。ですがインタラクティブ実行時で、結果表示の見た目よりも効率を重視する場合なら、この方法はお勧めです。
2013/03/29
Twitterの「全ツイート履歴」を分析してみよう
はじめに
Twitterブログ: 日本の皆さんにも「全ツイート履歴」が使えるようになりました の記事のとおり、自分の全ツイートデータをダウンロードする機能がTwitterで利用可能になっています。
ダウンロードされるzipファイルには、ツイートを表示するためのHTML、JavaScriptファイルのほか、CSV形式のデータ(tweets.csv)も含まれています。CSVファイルの処理といえばPowerShellが得意とするところです。このファイルを読み込んで、PowerShellで自分のツイートを分析してみましょう。
準備
具体的にダウンロードする方法は上記記事を参考にしていただいて、まずはダウンロードしたzipファイルからtweets.csvを解凍し、PowerShellのカレントディレクトリをtweets.csvのあるフォルダに移動させておいてください。
毎回CSVを読み込むと時間がかかるので、まず以下のようにしてImport-CsvコマンドレットによりCSVファイルを読み込み、変数にオブジェクトとして入れておきます。
$tweets = Import-Csv tweets.csv
なお私の総ツイート数は4万ほどで、tweets.csvは10MB程です。これくらいの容量だとそのままでもまずまずまともな速度で分析が可能ですが、何十万ツイートもしていらっしゃるTwitter廃人マニアの方は、適宜ファイルを分割するなどして対処願います。
CSVファイルのヘッダ行は
"tweet_id","in_reply_to_status_id","in_reply_to_user_id","retweeted_status_id","retweeted_status_user_id","timestamp","source","text","expanded_urls"
となっています。Import-Csvコマンドレットはデフォルトでは1行目を出力オブジェクトのプロパティ名とするので、データ行の1行がtweet_idプロパティ等を持つオブジェクトとして読み込まれ、$tweets変数にはそのオブジェクトの配列が格納されることになります。
ツイート抽出/検索
一番最初のツイートを表示
PS> $tweets | select -Last 1 tweet_id : 948090786 in_reply_to_status_id : in_reply_to_user_id : retweeted_status_id : retweeted_status_user_id : timestamp : 2008-10-06 10:54:10 +0000 source : web text : はぐれメタルがあらわれた! expanded_urls :
Select-Objectコマンドレット(エイリアスselect)はオブジェクトの絞り込みに使います。このCSVファイルではツイートの並び順がタイムスタンプの降順なので、最初のツイートは一番最後の行となります。
直近5ツイート表示
PS> $tweets | select -First 5 | fl timestamp,text timestamp : 2013-03-21 17:02:23 +0000 text : Need for Speedがなんか懐かしい。初めて買ったPCに体験版がバンドルさ れてた記憶がある。 timestamp : 2013-03-21 17:01:23 +0000 text : そいえばEAのシムシティ不具合お詫び無料DL特典、何選ぼうかなあ。シム シティ4あるけど英語版という噂だし2013やった後につらいもんがありそう 。 timestamp : 2013-03-21 16:45:09 +0000 text : というわけでシムシティ大好きなんで、私の街を返してください… ...
Format-Listコマンドレット(エイリアスfl)を使うと必要なプロパティ値のみ抽出してリスト形式で表示できます。
文字列で検索
PS> $tweets | where {$_.text -match "眠い"} | fl timestamp,text timestamp : 2013-03-05 10:46:39 +0000 text : 眠いのってもしかしてアレルギールの副作用かも。蕁麻疹がひどいときし か飲んでないんだけどねえ timestamp : 2013-03-05 05:42:18 +0000 text : なんでこんなに眠いのかな timestamp : 2013-03-04 07:44:18 +0000 text : 眠いなあ ...
Where-Objectコマンドレット(エイリアスwhere)を使うとオブジェクト配列のうち特定条件のもののみ抽出できます。ここではツイート本文(textプロパティ)に"眠い"という文字列が含まれているものを抽出しています。どんだけ眠いんですか私は…
2009年のツイートのみ表示
PS> $tweets | select @{L = "timestamp"; E = {Get-Date $_.timestamp}},text | where {$_.timestamp.Year -eq 2009} | sort timestamp | fl timestamp,text timestamp : 2009/01/01 0:01:08 text : あけおめ! timestamp : 2009/01/01 0:16:31 text : 2chとついったー強いなーmixiしんでた timestamp : 2009/01/01 13:37:50 text : 家族でおせちをたべた。おいしかった ...
もちろん本文に含まれる文字列以外にも、timestamp(ツイート時刻)で抽出するなどもできます。ここではtimestampがGMTで分かりづらく、かつ文字列のため扱いづらいので、Select-Objectに集計プロパティを指定してDateTime型に変換しています。Format-ListやSelect-Objectに指定する集計プロパティの書式は、@{L="ラベル";E={値を返すスクリプトブロック}}のように連想配列で指定します。LはLabel、EはExpressionのように省略せずに指定してもOKです。
集計プロパティはあんまり解説を見かけないですけども、オブジェクトを処理するコマンドレットの多くで利用可能できわめて重要なので覚えておくと良いと思います。
よるほ成功ツイート
PS> $tweets | where {(Get-Date $_.timestamp).ToString("HH:mm:ss") -eq "00:00:00"} | fl @{L = "timestamp"; E = {Get-Date $_.timestamp}},text
応用でこんなんもできます。0:00:00ちょうどのツイートを抽出します。私はかつてよるほ成功したことがないので結果は何も返ってきませんけど。
ツイート中のURLリストを作る
PS> $tweets | where {$_.expanded_urls} | select -expand expanded_urls http://ja.wikipedia.org/wiki/%E5%B2%A1%E7%B4%A0%E4%B8%96 http://htn.to/4oxXDN http://guitarrapc.wordpress.com ...
whereによる抽出を応用するとこういうこともできます。なお、expanded_urls列は本文中のURLが複数含まれているとそれらは,で区切られるため、可変長の行となります。Import-Csvコマンドレットはこのような可変長なCSVに対応していないので、複数URLがあっても最初の1つのみ取得します。それとexpanded_urlsが追加されたのはt.coによるURL短縮が始まってからなので、昔のツイートにこの値は含まれていません。
ツイート数統計
月別ツイート数表示
PS> $tweets | group @{E = {(Get-Date $_.timestamp).ToString("yyyy/MM")}} -NoElement Count Name ----- ---- 432 2013/03 413 2013/02 248 2013/01 741 2012/12 497 2012/11 791 2012/10 659 2012/09 ...
ツイート分析と言えばやはりツイート数統計を取ることから始まるでしょう。統計を取るにはGroup-Objectコマンドレット(エイリアスgroup)が使えます。ここでもグループ化キーとして集計プロパティを指定してやります。ツイートの「年/月」を文字列化し、それが同じツイートでグループ化することで、月別ツイート数の統計が表示できるわけです。
時間帯別ツイート数表示
PS> $tweets | group @{E = {Get-Date $_.timestamp | select -expand Hour}} -NoElement | sort @{E = {[int]$_.Name}} Count Name ----- ---- 2369 0 1630 1 1137 2 ... 2270 23
やり方としては先ほどのとほぼ同じです。Select-Object -ExpandPropertyはパイプライン入力でオブジェクトのプロパティ値を取得できるのでよく使います。ちなみにPowerShell 3.0だと「$obj|foreach プロパティ名」でも取れますね。
Sort-Objectコマンドレット(エイリアスsort)でもソートキーとして集計プロパティを指定できます。ここではNameプロパティ(グループ化キーの値)をintに変換したものをキーにソートしています。
曜日別ツイート数表示
PS> $tweets | group @{E = {Get-Date $_.timestamp | select -expand DayOfWeek}} -NoElement | sort @{E = {[DayOfWeek]$_.Name}} Count Name ----- ---- 4939 Sunday 5164 Monday 5463 Tuesday 5164 Wednesday 5563 Thursday 5992 Friday 6331 Saturday
これもやり方としてはほぼ同じ。ソートキーはDayOfWeek列挙体にキャストしてちゃんと曜日順に並ぶようにしてます。
ツイート数計測
総ツイート数
PS> $tweets | measure Count : 38616 Average : Sum : Maximum : Minimum : Property :
ここからはツイート数の計測をしていきます。単純にツイート総数を取るだけならMeasure-Objectコマンドレット(エイリアスmeasure)を使うだけでOKです。Averageなどは対応するスイッチパラメータ(-Averageなど)を指定すると計測されますが、この場合は元オブジェクトが数値ではないのでエラーになります。
ツイート文字数分析
PS> Add-Type -AssemblyName System.Web PS> $tweets | where {!$_.retweeted_status_id} | select @{L = "TextLength"; E = { [System.Web.HttpUtility]::HtmlDecode($_.text).Length}} | measure -Sum -Maximum -Minimum -Average -Property TextLength Count : 37718 Average : 48.7322233416406 Sum : 1838082 Maximum : 140 Minimum : 1 Property : TextLength
ツイート文字数を計測するとき、元のオブジェクトにはツイート文字数を返すプロパティはないので、Select-ObjectコマンドレットにTextLengthという集計プロパティを指定して新たに作ってしまいます。
Measure-Objectコマンドレットは-Propertyパラメータにより対象オブジェクトのどのプロパティ値を計測するか指定できます。そしてスイッチパラメータを全部有効にすることで、平均、合計、最大、最小値を計測しています。私の総ツイート文字数は183万です。
なお、リツイートの場合はretweeted_status_idにリツイート元のツイートIDが入るので、このIDがあるものはWhere-Objectで除外してます。またツイート本文の<や&などはHTMLエンコードされたものがtext列に格納されているので、HttpUtilityを使ってデコードしてから文字数をカウントしています。
通常ツイートとRTの比率
PS> $tweets | foreach { $TweetCount = 0; $RTCount = 0 } { if($_.retweeted_status_id){ $RTCount++ }else{ $TweetCount++ } } { New-Object psobject @{ AllCount = $tweets.Length; TweetCount = $TweetCount; RTCount = $RTCount; RTRatio = $RTCount/$tweets.Length } } Name Value ---- ----- RTCount 898 TweetCount 37718 AllCount 38616 RTRatio 0.023254609488295
Measure-Objectコマンドレットは計測方法を指定することはできないので、独自の計測を行う場合はこんな感じでコードめいたものを書く必要が出てくるかと思います。RT率たったの2%か…ゴミめ…
ForEach-Object(エイリアスforeach)は1個のスクリプトブロックをパラメータに指定するとprocessブロック相当の列挙部分を実行しますが、このように3個指定すると、それぞれbegin(初期化処理)、process、end(終了処理)ブロックに割り振られます。
ここではbeginブロックで変数初期化、processブロックで通常ツイートとリツイートを加算、endブロックで計測値をPSObjectに格納して出力してます。ちなみにPowerShell 3.0ではカスタムオブジェクトを作る場合は「[pscustomobject]@{連想配列}」で書くほうが楽です。
お前は今まで寒いと言った回数を覚えているのか
PS> $tweets | foreach {$count = 0} { $count += ($_.text -split "寒い").Length - 1} {$count} 137
覚えてないから数えます。137回か。
数値だけを出力するならこんな感じでシンプルに書けますね。
ランキング
クライアントランキング
PS> $tweets | group @{E = {$_.source -replace "<.+?>"}} -NoElement | sort Count -Descending Count Name ----- ---- 12927 Janetter 11333 web 5230 Azurea for Windows 3060 TweetDeck 1694 Hatena 866 twicca 667 twigadge ...
ここからはいろんなランキングを取得してみます。まずはツイートに使ったTwitterクライアントのランキング。ここでもGroup-Objectを使っています。クライアント名はクライアント配布URLがaタグで含まれているのでそれを-replace演算子で削ったものをグループ化キーとしています。ランキングなので最後はCountで降順ソート。
リプライしたユーザーランキング
PS> $tweets | where {$_.in_reply_to_user_id} | select @{L = "user"; E = {if($_.text -match "^(@[a-zA-Z0-9_]+)"){$matches[1]}}} | group user -NoElement | sort Count -Descending Count Name ----- ---- 807 @xxxxxxxxxx 417 @xxxxxxxxxx 333 @xxxxxxxxxx ...
ランキング系はどれもgroup→sort Countのパターンになるかと思います。リプライツイートはin_reply_to_user_id列にリプライしたユーザーIDが含まれるのでまずはそれでフィルタし、ユーザー名はツイート本文から取ります。ユーザー名は-match演算子を使って正規表現で抽出します。$matches自動変数は連想配列で、[0]にマッチ全体が、[1],[2],...にはサブ式のキャプチャが入ります。ちなみにサブ式に名前を付けてるとキー名が数値ではなくサブ式名となります。
ハッシュタグランキング
PS> $tweets | foreach {[regex]::Matches($_.text, "(#\S+)") | % {$_.Captures} |% {$_.Value}} | group -NoElement | where Count -gt 1 | sort Count -Descending Count Name ----- ---- 199 #zanmai 75 #nowplaying 68 #nhk 63 #techedj2009 ...
ハッシュタグも同様のアプローチで取れますが、ハッシュタグは1ツイートに複数あることがあり、-match演算子だと複数のマッチは取れないので[regex]を使って取得しています。
おわりに
PowerShellのオブジェクト処理用コマンドレットを用いると、CSVデータの分析ができます。普通はログファイル等を解析するのに使うわけですが、こういう身近なデータを扱ってみるのも面白いんじゃないでしょうか。きっとPowerShellの勉強にもなると思います。
2012/08/01
「シェル操作課題」をPowerShellでやってみた
シェル操作課題 (cut, sort, uniq などで集計を行う) 設問編 - Yamashiro0217の日記
ログファイルをコマンド使って解析するというこの課題。
え。Windows?まさか???cygwin入れたり、vmでやってみたり、開発サーバーで作業すればいいんじゃないっすか(ホジホジ)
いやいやWindowsでシェルと言えばPowerShellでしょう!というわけでやってみました。出力形式に関しては本筋とは関係ないと思うので、PowerShellのデフォルトのままです。
準備
$logs = Import-Csv hoge.log -Header ServerName,UnixTime,UserId,AccessUrl
問1 このファイルを表示しろ
$logs
問2 このファイルからサーバー名とアクセス先だけ表示しろ
$logs|select ServerName,AccessUrl
問3 このファイルからserver4の行だけ表示しろ
$logs|where {$_.ServerName -eq "server4"}
問4 このファイルの行数を表示しろ
$logs|measure
問5 このファイルをサーバー名、ユーザーIDの昇順で5行だけ表示しろ
$logs|sort ServerName,{[int]$_.UserId}|select -First 5
問6 このファイルには重複行がある。重複行はまとめて数え行数を表示しろ
$logs|sort * -Unique|measure
問7 このログのUU(ユニークユーザー)数を表示しろ
$logs|sort UserId -Unique|measure
問8 このログのアクセス先ごとにアクセス数を数え上位1つを表示しろ
$logs|group AccessUrl -NoElement|sort Count -Descending|select -First 1
問9 このログのserverという文字列をxxxという文字列に変え、サーバー毎のアクセス数を表示しろ
$logs|group {$_.ServerName -replace "server","xxx"} -NoElement|sort Count -Descending
問10 このログのユーザーIDが10以上の人のユニークなユーザーIDをユーザーIDでソートして表示しろ
$logs|where {[int]$_.UserId -ge 10}|select -Expand UserId|sort -Unique
PowerShellはテキスト情報をテキストのまま扱うのではなく、オブジェクトとして扱うところが特徴で、CSVファイルもImport-Csvコマンドレットを用いることで、ヘッダ文字列をプロパティ名として各行をオブジェクトとして読み込んでくれます。
あとは各種オブジェクト処理のコマンドレットに、パイプライン経由でオブジェクトを渡していくだけです。コマンドが何をやっているのかも、列番号ではなくプロパティ名を指定してやるので分かりやすいと思います。この課題に寄せられた回答の中では、シェル操作課題 SQLによる解答例が近いかと思います。
今回はCSVから情報を読み込んでいますが、ソースは別になんであってもいいわけです。たとえばGet-EventLogコマンドレットだとイベントログから持ってくることもできますし、XMLなら$xml=[xml](Get-Content file.xml)みたいな感じです。一旦オブジェクトとして取得できてしまえば、あとの処理方法は基本的に同じなので悩む必要がないのが良いところです。
そしてPowerShellの良いところはやはり、外部処理コマンドを使わずとも、シェル言語と組み込みコマンド(コマンドレット)のみで処理が完結するところです。たとえばbashだけでやるとtsekine's miscellaneous thoughts: シェル操作課題への回答のような大変なことになるようです。sortなど基本的なコマンドは使用するにしても、複雑なフィルタ処理などはawkを組みあわせる等しないと、特に後半の処理は辛いでしょう。PowerShellなら異なる処理系を持ち出す必要がなく、PowerShellのみですべて行えます。
もちろん問題点も色々あります。まず第一に、今回の課題のように、ヘッダ文字列がないCSVの場合は例のようにヘッダー文字列(=プロパティ名)を自分で定義してやる必要が出てきます。そのため可読性はともかく、若干、冗長なところがでてきてます。まあ例のように一旦オブジェクトを作って変数に入れてしまえばあとは使いまわせますが、そこはちょっと厳しい点かもしれません。
あとImport-CsvコマンドレットはCSVの各エントリをすべて文字列型のプロパティとしてオブジェクトに格納するところがちょっと微妙な気がしました。数値は数値型で入れてほしいですよね。おかげで何か所か、[int]にキャストせざるを得ない部分が出てきました。
そして処理速度の問題。おそらくログファイルが巨大だとかなりリソースを食って時間もかかると思います。処理を分ける、バックグラウンドジョブやワークフローで動かす、そもそもログファイルを分割保存するようにしておく、等、実運用上では工夫が必要かと思います。
(5:14追記)
既出でした>< [Power Shell] シェル操作課題への回答 - Pastebin.com(by @usamin5885さん)
2010/02/22
[PSv2]PowerShell 2.0で新しく追加されたコマンドレットとパラメータの列挙
PowerShell 2.0では標準コマンドレットの数が1.0の129個から236個へと、107個増えています。また、既存のコマンドレットの一部にパラメータが増えています。そこで、2.0で新しく加わったコマンドレットと、新しく追加されたパラメータを列挙するスクリプトを作ってみました。
まず、1.0がインストールされている環境で、コマンドレットのリストをXMLに書き出します。次のコマンドを実行してください。
get-command -type cmdlet|export-clixml cmdlets1.xml -depth 3
同様に、2.0の環境でも実行してください。
get-command -type cmdlet|export-clixml cmdlets2.xml -depth 3
出来上がった二つのxmlファイルをカレントディレクトリに置いて、次のスクリプトを実行すると、新しく追加されたコマンドレットとパラメータが列挙されます。(必要なら適宜リダイレクトするなどしてファイルに落とし込んでください)
function Get-CmdletHash { param([string]$path) $cmdlets = Import-Clixml $path $cmdletsHash = @{} $cmdlets| %{ $parameters = @() $_.ParameterSets| %{ $_.Parameters| %{ $parameters += $_.Name } } $parameters = $parameters|Sort-Object|Get-Unique|?{"WarningAction","WarningVariable" -notcontains $_} $cmdletsHash.Add($_.Name,$parameters) } return $cmdletsHash } $ver1 = Get-CmdletHash cmdlets1.xml $ver2 = Get-CmdletHash cmdlets2.xml $ver2.Keys| %{ if($ver1.ContainsKey($_)) { $result = Compare-Object $ver1[$_] $ver2[$_] if($result) { "[Update] $_" $result | Format-Table } } else { "[New] $_ `r`n" } }
出力結果を置いておきます。こちら。
このスクリプトではGet-CommandコマンドレットがSystem.Management.Automation.CmdletInfoというコマンドレットの情報を格納したオブジェクトを返すことを利用し、Export-Clixmlコマンドレットでシリアライズ化してXMLファイルに落とし込むことにより異なるバージョンのPowerShell同士を比較しています。ここで-depthパラメータを3にしているのは、パラメータ情報を格納するParametersプロパティがルートから3階層下にあるためです。(デフォルトは2階層下までを出力。PS2.0ではCmdletInfoにParametersプロパティというのがあって2階層でたどれるのですが1.0にこのプロパティはないのでParameterSetsプロパティからたどる必要があります)
また、WarningActionとWarningVariableという共通パラメータが増えているので、これらはすべてのコマンドレットに共通して追加されているパラメータなので除外しておきます。
あとはパラメータをコマンドレットごとに配列化して、Compare-Objectで比較しています。新しく追加されたコマンドレットは[New]のマークをつけ、既存のコマンドレットでパラメータに変化があるものは[Update]をつけて追加されたパラメータを列挙するようにしています。
かなりたくさんのコマンドレットでパラメータが増えたことが分かりますね。また、Get-CommandコマンドレットのPSSnapinパラメータが廃止され、Moduleパラメータに変更されたことなどが分かったりします。
1.0ユーザーだった方にお勧めのスクリプトです。
元記事:http://blogs.wankuma.com/mutaguchi/archive/2010/02/22/186334.aspx2010/02/13
PowerShell基礎文法最速マスター
PowerShellは.NET Framework 2.0を利用するWindowsのシステム管理用シェルである。シェルであるためコンソールで対話的にコマンドを実行することができるのはもちろん、スクリプトファイル(*.ps1)を記述しバッチ的に実行することも可能である。ここではPowerShellスクリプトで(コンソールでも使用は可能だが)用いることのできる基礎文法を紹介する。なお、PowerShellでは文法上、大文字小文字を区別しない。
※(★2.0)の注釈があるものはPowerShell 2.0で新たに追加された要素である。
1.基礎
表示
コンソールに文字列を表示。
"Hello world"
コマンドレット(後述)を使用した場合。
Write-Host "Hello world"
コマンドレット
PowerShellはコマンドレットと呼ばれる100種類以上のコマンドライン・ツール群を単独で、あるいはパイプライン(後述)で連結して使用するのが基本となる。コマンドレットは原則verb-nounという命名規則にしたがっている。パラメータをつける場合は「-パラメータ名」あるいは「-パラメータ名 パラメータ値」を指定する。
# コマンドレットの一覧表示 Get-Command # サービスの一覧を表示 Get-Service # アプリケーション イベントログの最新15個のエントリを表示 Get-EventLog -logName Application -newest 15
パイプライン
コマンドレットが値を返却する場合、.NET Frameworkのオブジェクトが含まれる配列であることが多い。このオブジェクト配列がパイプラインを渡って後続のコマンドレットに入力される。
# プロセスのリスト(System.Diagnostics.Processオブジェクトの配列)を取得し、 # Where-Objectコマンドレットでハンドル数(handlesプロパティ)の値が500より大きいものだけを取り出し # Select-Objectコマンドレットで最初の5つのオブジェクトだけを切りだして表示 Get-Process | Where-Object {$_.handles -gt 500} | Select-Object -first 5 # C:\Windows 配下のフォルダ、ファイルの一覧(System.IO.DirectoryInfo,System.IO.FileInfoオブジェクトの配列)を取得し、 # ForEach-Objectコマンドレットで配列を列挙しすべてのオブジェクトのFullNameプロパティ(フルパス)の値を表示 Get-ChildItem C:\Windows | ForEach-Object {$_.FullName} # 通常の配列に関してもパイプラインを使用可能。 # 重複を取り除き、ソートをかける @(3,5,10,1,2,1,1,1,2,6,4,4)|Sort-Object|Get-Unique
コメント
# コメント
マルチラインコメント(★2.0)
<# 複数行に渡る コメントです #>
変数の宣言
PowerShellは変数の宣言をしなくても変数を使用可能。以下のようにするとどのような型でも代入可能な変数が作られる。
$a = 1 $a = $b = $c = 1 #複数変数に一度に同じ値を代入する場合 $items = Get-ChildItem # コマンドレットの戻り値を格納
変数の型を指定することは可能。以下のようにするとint型のみ格納可能な変数が作られる。
[int]$a = 1
あるいは、コマンドレットを用いて$aという変数を宣言することもできる。この場合変数の型は指定できない。
New-Variable -name a
変数のスコープ
# どのスコープからも読み書き可能 $global:a = 1 # 現在のスコープからのみ読み書き可能 $private:a = 1 # 現在のスクリプトからのみ読み書き可能 $script:a = 1
文法チェック
以下を実行することで未定義の変数を参照するとエラーが出るようになる。
Set-PSDebug -strict
スクリプトの実行
デフォルトの実行ポリシーではスクリプトの実行は不許可であるため、以下のようにポリシーを変更しておく。(RemoteSignedはローカルにあるスクリプトファイルは無条件で実行可、リモートにあるスクリプトファイルは署名付きのもののみ実行可)
Set-ExecutionPolicy RemoteSigned
スクリプト/コマンドを実行するにはコマンドラインで次のようにする。
コマンドを実行する
powershell -command {Get-ChildItem C:\}
ファイルを実行する
powershell .\script.ps1
ドットソース(スクリプトの内容をグローバルスコープに読み込む)
powershell . .\script.ps1
ファイルを実行する(★2.0)
powershell -file script.ps1
PowerShellスクリプトから別のスクリプトを実行する場合(関数のインクルードにも用いられる)
.\script.ps1 . .\script.ps1 # ドットソース
デバッガの起動
Set-PSDebug -trace 2
ステップ実行
Set-PSDebug -step
2.数値
数値の表現
PowerShellにおける数値は.NET Frameworkの数値を表す構造体のインスタンスである。数値には整数、浮動小数点があり、変数に代入した段階で適切な型が設定される。
# int型(System.Int32型) $int = 1 # System.Double型 $double = 1.001
四則演算
# 足し算 $i = 1 + 1 # 引き算 $i = 1 - 1 # 掛け算 $i = 1 * 1 # 割り算 $i = 1 / 1
余りと商の求め方
# 割り算の余り $mod = 7 % 3 # 上記の場合の商 $div = (7 - 7 % 3) / 3
べき乗
# 2の8乗 $i = [math]::Pow(2,8)
インクリメントとデクリメント
# インクリメント $i++ # デクリメント $i--
3.文字列
PowerShellにおける文字列は.NET Frameworkの System.Stringクラスのインスタンスである。
文字列の表現
文字列はシングルクォーテーションかダブルクォーテーションで囲む。ダブルクォーテーションの中では`t(タブ)や`r`n(改行)などの特殊文字が使用でき、変数が展開される。
$str1 = 'abc' $str2 = "def" $str3 = "a`tbc`r`n" #変数展開(結果は abc def) $str4 = "$str1 def"
文字列操作
各種文字列操作
# 結合 $join1 = "aaa" + "bbb" $join2 = [string]::Join(",",@("aaa","bbb","ccc") ) # 結合(★2.0) $join2 = @("aaa","bbb","ccc") -join "," # 分割 $record1 = "aaa,bbb,ccc".Split(",") # 分割(★2.0) $record2 = "aaa,bbb,ccc" -split "," # 長さ $length = "abcdef".Length # 切り出し $substr = "abcd".SubString(0,2) # ab
正規表現検索
# hitした場合はTrue,しなかった場合はFalse $result = "abcd" -match "cd" # 最初に見つかった文字列。添え字の1,2…には()内のサブ式にhitした文字列が格納。 $matches[0]
正規表現置換
$result = "abc" -replace "c","d"
4.配列
PowerShellにおける配列は.NET Frameworkの System.Arrayクラスのインスタンスである。
配列の参照と代入
# 5個の要素を持つ配列宣言と代入 $arr1 = @(1,3,5,7,9) # 以下のようにも記述できる $arr1 = 1,3,5,7,9 # 型指定する場合 [int[]]$arr1 = @(1,3,5,7,9) # 1〜10までの要素を持つ配列宣言と代入 $arr2 = @(1..10) # 1要素の配列宣言と代入 $arr3 = @(1) $arr3 = ,1 # 空の配列宣言と代入 $arr4 = @()
配列の要素の参照と代入
# 4番目の要素を参照 $ret = $arr2[3] # 6〜9番目の要素を含んだ配列を参照 $ret = $arr2[5..8] # 1〜4番目と8番目の要素を含んだ配列を参照 $ret = $arr2[0..3+7] # 配列の末尾の要素を取り出す $ret = $arr2[-1] # 5番目の要素に値を代入 $arr2[4] = 11 # 3より小さな要素を含んだ配列を返す $ret = $arr2 -lt 3
配列の個数
$arr1_num = $arr1.Length
配列の操作
$arr1 = @(1,3,5,7,9) $arr2 = @(1..10) # 配列の末尾に要素を加える(push) $arr2 += 50 # 配列を結合し新しい配列を作成 $arr5 = $arr1 + $arr2 # 配列にある要素が含まれるかどうか(ここではTrue) $arr2 -contains 2
5.ハッシュ
PowerShellにおけるハッシュは.NET Frameworkの System.Collections.Hashtableクラスのインスタンスである。
ハッシュ変数の宣言と代入
# 3つの要素を持つハッシュの宣言と代入 $hash1 = @{a=1;b=2;c=3} # 空のハッシュの宣言と代入 $hash2 = @{}
ハッシュの要素の参照と代入
# 要素の参照 $hash1.a $hash1["a"] #要素の代入 $hash1.b = 5 $hash1["b"] = 5
ハッシュの操作
# ハッシュに要素を追加 $hash1.d = 4 $hash1.Add("e",5) # ハッシュの要素の削除 $hash1.Remove("a") # ハッシュのキーの取得 $keys = $hash1.Keys # ハッシュの値の取得 $values = $hash1.Values # ハッシュの要素を列挙 foreach ($key in $hash1.Keys) { $key + ":" + $hash1[$key] } # キーの存在確認 $hash1.Contains("b")
6.制御文
if文
if (条件) { }
if 〜 else文
if (条件) { } else{ }
if 〜 elsif 文
if (条件) { } elseif (条件) { }
while/do文
while (条件) { } do { } while (条件)
for文
for ($i = 0; $i -lt 5; $i++) { }
foreach文
foreach ($item in $items) { }
switch文
case を書かないのが特徴的。またスクリプトブロックを条件文に記述できる。
switch ($i) { 1 {"1";break} 2 {"2";break} {$_ -lt 5} {"5より小さい";break} default {"default句";break} } # ここで$iに配列を指定すると配列要素すべてに対してswitch文が実行される。
比較演算子
比較演算子の一覧。PowerShellではPerlの文字列比較演算子のような記述をおこなうが、Perlとは異なり文字列も数値も同じ書式である。
$num1 -eq $num2 # $num1は$num2と等しい $num1 -ne $num2 # $num1は$num2は等しくない $num1 -lt $num2 # $num1は$num2より小さい $num1 -gt $num2 # $num1は$num2より大きい $num1 -le $num2 # $num1は$num2以下 $num1 -ge $num2 # $num1は$num2以上
論理演算子
# 論理否定 $ret = -not $true $ret = !$true # 論理積 $ret = $true -and $false # 論理和 $ret = $true -or $false # 排他的論理和 $ret = $true -xor $false
ビット演算子
# ビット単位の否定 $ret = -bnot 0x14F4 # ビット単位の積 $ret = 0x14F4 -band 0xFF00 # 上記結果を16進数で表示する場合 $ret = (0x14F4 -band 0xFF00).ToString("X") # ビット単位の和 $ret = 0x14F4 -bor 0xFF00 # ビット単位の排他的論理和 $ret = 0x14F4 -bxor 0xFF00
7.サブルーチン
PowerShellのサブルーチンには関数とフィルタがある。関数とフィルタは呼び出し行の前で宣言する必要がある。 filter構文もfunction構文と並んで独自関数を記述するものだが、filter構文はパイプラインに渡されたオブジェクトをフィルタするのに用いる。 functionとの違いは、パイプラインに渡した配列を一度に処理するか(function)個別に処理するか(filter)
# 関数宣言の基本 function Get-Test { return "test" } # 注:returnを付けなくても関数内で出力された値はすべて呼び出し元に返却される。返却したくない場合は出力値をを[void]にキャストするか|Out-Nullに渡す。 # 引数を指定する場合 function Get-Test { param($param1,$param2) return $param1 + $param2 } # 引数を指定する場合の簡易的な記述法 function Get-Test($param1,$param2) { return $param1 + $param2 } # 引数の型を指定する場合 function Get-Test { param([string]$param1,[string]$param2) return $param1 + $param2 } # 関数の呼び出し方(,区切りではなくスペース区切りであることに注意) Get-Test "引数1" "引数2" # 引数の順序はパラメータ名(引数名)を指定すると自由に指定可能 Get-Test -param2 "引数2" -param1 "引数1" # フィルタ宣言の基本 filter Get-Odd { if($_ % 2 -eq 1){ return $_ }else{ return } } # フィルタの使用 @(1..10) | Get-Odd
8.テキストファイル入出力
コマンドレットで可能。エンコーディングは日本語環境のデフォルトではShift-JIS。コマンドレット出力のテキストファイルへの書き出しに関してはリダイレクトも可能。この場合エンコーディングはUnicode。
$str1 = "testテスト" Set-Content test.txt $str1 # 書き込み Add-Content test.txt "追記" # 追記 $str2 = Get-Content test.txt # 読み込み Set-Content test.txt $str1 -encoding UTF8 # UTF-8で書き込み # リダイレクト Get-Process > test.txt # 書き込み Get-Process >> test.txt # 追記 Get-Process | Out-File test.txt -encoding UTF8 # エンコーディングを指定する場合
9.例外
PowerShellで例外が発生すると、デフォルトではエラーメッセージを表示し次の行を実行する(シェル変数$ErrorActionPreferenceの設定により挙動の変更可能)。VBでいうとOn Error Resume Nextに近い。エラーが発生すると$Errorにエラー情報の配列が格納され、$?にFalseが格納される。エラーをトラップするには次の構文を使用する。VBでいうとOn Error Goto lineに近い。
# すべてのエラーをトラップ trap { } # エラーの型名を指定してトラップ trap [System.Management.Automation.CommandNotFoundException] { } # エラーを発生させる throw "エラー" throw New-Object NullReferenceException
構造化例外処理(★2.0)
# 基本 try{ } catch{ } finally{ } # エラーの型を指定してcatch try{ } catch [System.Net.WebException],[System.IO.IOException]{ }
10.知っておいたほうがよい文法
行継続文字
1行にすると長いコードを複数行に書くには行継続文字`を用いる。VBの_。
$items = Get-ChildItem a*,b*,c*,d*,e* ` -force -recurce
ただし以下のような場合は`を使用しなくてもよい
$items = Get-ChildItem a*,b*,c*,d*,e* -force –recurse Get-Process | Where-Object {$_.handles -gt 500} | Select-Object -first 5
ステートメント分割
ステートメントを分割するには改行コードもしくは;を使用する。VBの:。JavaScriptと同様、文末に;はつけてもつけなくてもよい。
$i = 1; $j = 5; $k = $i + $j
ヒア文字列
複数行の文字列を記述する方法。
$str = @" aaaaaa bbbbb cccc ddd ee "@
.NET Frameworkクラスの利用
.NET Frameworkに含まれているクラスのプロパティやメソッドを使用できる。基本的に完全修飾名を指定しなければいけないが、"System."は省略可能。また、intなど型エイリアスがいくつか定義されている。
# スタティックメンバの使用 [System.Math]::Pow(2,8) # インスタンスの生成とメソッドの実行 $arrayList = New-Object System.Collections.ArrayList $arrayList.Add("a") # コンストラクタがある場合。複数ある場合は配列として指定 $message = New-Object System.Net.Mail.Message from@example.com,to@example.com # COMオブジェクトの生成 $wshShell = New-Object -com WScript.Shell # デフォルトで読み込まれていないアセンブリを読み込む [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") [System.Windows.Forms.MessageBox]::Show("hello!") # クラスにどんなメンバがあるかの確認 # インスタンスメンバ Get-ChildItem | Get-Member # スタティックメンバ [math] | Get-Member -static
キャスト
-asを使った場合はキャスト失敗時もエラーにならずNullが格納される。
$dt = [System.DateTime]"2010/02/13" $dt = "2010/02/13" -as [System.DateTime]
ユーザー定義オブジェクト
PowerShellにはクラスを定義する構文はないが、空のオブジェクト(PSObject)を生成し、任意のプロパティ(ノートプロパティ)を付加することができる。
$obj = New-Object PSObject $property = New-Object System.Management.Automation.PSNoteProperty "Name","名前" $obj.PSObject.Members.Add($property)
シェル変数
あらかじめ定義されている変数。シェル変数には自動変数(変更不可能)とユーザー定義変数(変更すると挙動を変更することができる)がある。自動変数の例を挙げる。
$_ :現在パイプラインにわたっているオブジェクト
$args :関数やスクリプトに与えられたパラメータの配列
$pshome :PowerShellがインストールされているフォルダのフルパス
$MyInvocation :スクリプトの実行情報。$myInvocation.ScriptNameでスクリプトのフルパス取得(★2.0)。$myInvocation.MyCommand.Path(1.0の場合)
$true :true。
$false :false。
$null :null。
サブ式
$()内には複数行のコードが記述できる。
$arr = $(1;2;1+4)
式モードとコマンドモード
PowerShellの構文解析は式モードとコマンドモードがある。式モードは通常のモード。コマンドモードは引用符がなくても文字列を文字列として扱う。コマンドレットのパラメータなどはコマンドモードで扱われる。ただしコマンドモードになるところでも()もしくは$()もしくは@()をつけるとその中身は式モードとして解釈、実行される。
$i = 1 + 1 # 式モード Write-Host aaa # コマンドモード(表示:aaa) Write-Host aaa bbb # コマンドモード(表示:aaa bbb) Write-Host 1+1 # コマンドモード(表示:1+1) Write-Host (1+1) # 式モード(表示:2) $itemCount = @(Get-ChildItem).Length # 式モード
実行演算子とスクリプトブロック
&演算子を用いるとスクリプトブロック{}の内容を実行できる。この場合、スクリプトブロック内のコードは別スコープになる。
$script = {$i = 1+6; Write-Host $i} &$script & 'C:\Program Files\Internet Explorer\iexplore.exe' # パスにスペースの含まれるファイルを実行したりするのにも使える
フォーマット演算子
-f演算子を使うと、.NET Frameworkのカスタム書式が使用可能。
"{0:#,##0}Bytes" -f 38731362 # 表示:38,731,362Bytes
バイト数の簡易表記
$i = 1KB # 1024が代入される $i = 1MB # 1048576が代入される $i = 1GB # 1073741824が代入される
そのほかの基礎文法最速マスターへのリンク
プログラミング基礎文法最速マスターまとめ - ネットサービス研究室
http://d.hatena.ne.jp/seikenn/20100203/programmingMaster
PowerShellの詳しい機能解説についてはこちらの記事を参照してください。
PowerShell的システム管理入門 ―― PowerShell 2.0で始める、これからのWindowsシステム管理術 ―― ─ @IT
進化したPowerShell 2.0 ─ @IT
文法や機能について詳しく学びたい方には書籍もあります
Windows PowerShellポケットリファレンス
PowerShellによるWindowsサーバ管理術
2007/07/19
コマンドレットのパラメータを列挙してみる。
共通パラメータ以外にも共通のパラメータって結構ありますよね。それを列挙してみました。
get-command -type cmdlet|%{$_.parametersets| %{$_.parameters}}|group name|sort count -des
結果
Count Name Group ----- ---- ----- 206 ErrorAction {ErrorAction, ErrorAction, ErrorAction, ErrorAction...} 206 Debug {Debug, Debug, Debug, Debug...} 206 Verbose {Verbose, Verbose, Verbose, Verbose...} 206 OutBuffer {OutBuffer, OutBuffer, OutBuffer, OutBuffer...} 206 OutVariable {OutVariable, OutVariable, OutVariable, OutVariable...} 206 ErrorVariable {ErrorVariable, ErrorVariable, ErrorVariable, ErrorVariable...} 80 WhatIf {WhatIf, WhatIf, WhatIf, WhatIf...} 80 Confirm {Confirm, Confirm, Confirm, Confirm...} 72 Force {Force, Force, Force, Force...} 71 Exclude {Exclude, Exclude, Exclude, Exclude...} 70 Include {Include, Include, Include, Include...} 61 PassThru {PassThru, PassThru, Passthru, PassThru...} 59 Credential {Credential, Credential, Credential, Credential...} 58 Name {Name, Name, Name, Name...} 47 Filter {Filter, Filter, Filter, Filter...} 46 Path {Path, Path, Path, Path...} 42 InputObject {InputObject, InputObject, InputObject, InputObject...} 31 LiteralPath {LiteralPath, LiteralPath, LiteralPath, LiteralPath...} 17 Value {Value, Value, Value, Value...} 15 Scope {Scope, Scope, Scope, Scope...} 12 Property {Property, Property, Property, Property...} 9 Encoding {Encoding, Encoding, Encoding, Encoding...} 8 Destination {Destination, Destination, Destination, Destination...} 8 FilePath {FilePath, FilePath, FilePath, FilePath...} 8 Description {Description, Description, Description, Description...} 8 DisplayName {DisplayName, DisplayName, DisplayName, DisplayName...} 7 PSProvider {PSProvider, PSProvider, PSProvider, PSProvider...} 7 Option {Option, Option, Option, Option...} 6 Recurse {Recurse, Recurse, Recurse, Recurse...} 6 NoClobber {NoClobber, NoClobber, NoClobber, NoClobber...} 6 Category {Category, Category, Category, Category...} 6 Resolve {Resolve, Resolve, Resolve, Resolve...} 5 Message {Message, Message, Message, Message...} 5 CaseSensitive {CaseSensitive, CaseSensitive, CaseSensitive, CaseSensitive...} 5 StackName {StackName, StackName, StackName, StackName...} 5 Id {Id, Id, Id, Id...} (5未満は略)
あれなんで200以上あるのかな、まあいいや細かいことは気にしない。31 LiteralPathくらいまでが重要そうです。
元記事:http://blogs.wankuma.com/mutaguchi/archive/2007/07/19/86015.aspx2007/06/17
コマンドレット動詞ランキング
ダッチさんのメソッド名ランキングにインスパイアされてPowerShellのコマンドレットではどうなのかというのをやってみました。
get-command |group-object verb|sort count -descending
結果
Count Name Group ----- ---- ----- 29 Get {Get-Acl, Get-Alias, Get-AuthenticodeSignatu... 13 Set {Set-Acl, Set-Alias, Set-AuthenticodeSignatu... 8 New {New-Alias, New-Item, New-ItemProperty, New-... 7 Write {Write-Debug, Write-Error, Write-Host, Write... 6 Out {Out-Default, Out-File, Out-Host, Out-Null...} 5 Remove {Remove-Item, Remove-ItemProperty, Remove-PS... 4 Add {Add-Content, Add-History, Add-Member, Add-P... 4 Format {Format-Custom, Format-List, Format-Table, F... 4 Clear {Clear-Content, Clear-Item, Clear-ItemProper... 4 Export {Export-Alias, Export-Clixml, Export-Console... 3 Invoke {Invoke-Expression, Invoke-History, Invoke-I... 3 Start {Start-Service, Start-Sleep, Start-Transcript} 3 Stop {Stop-Process, Stop-Service, Stop-Transcript} 3 Import {Import-Alias, Import-Clixml, Import-Csv} 2 ConvertTo {ConvertTo-Html, ConvertTo-SecureString} 2 Copy {Copy-Item, Copy-ItemProperty} 2 Update {Update-FormatData, Update-TypeData} 2 Select {Select-Object, Select-String} 2 Rename {Rename-Item, Rename-ItemProperty} 2 Move {Move-Item, Move-ItemProperty} 2 Measure {Measure-Command, Measure-Object} 1 Sort {Sort-Object} 1 Where {Where-Object} 1 Tee {Tee-Object} 1 Split {Split-Path} 1 Test {Test-Path} 1 Trace {Trace-Command} 1 Suspend {Suspend-Service} 1 ForEach {ForEach-Object} 1 Group {Group-Object} 1 Join {Join-Path} 1 Compare {Compare-Object} 1 ConvertFrom {ConvertFrom-SecureString} 1 Convert {Convert-Path} 1 Resolve {Resolve-Path} 1 Restart {Restart-Service} 1 Resume {Resume-Service} 1 Pop {Pop-Location} 1 Push {Push-Location} 1 Read {Read-Host}
やはりGetが多いですね。対応してSetも多いです。結果は普通ですが一行のコマンドでこんなことが分かるのが面白いでしょ?
元記事:http://blogs.wankuma.com/mutaguchi/archive/2007/06/17/81041.aspxCopyright © 2005-2018 Daisuke Mutaguchi All rights reserved
mailto: mutaguchi at roy.hi-ho.ne.jp
プライバシーポリシー