コマンドレットのパラメータを列挙してみる。2015の15日目の記事です。はじめに前々回と前回は、PowerShellによるWebスクレイピングの具体的手法についてまとめました。APIを用いるのが本道かと思います。APIを用いるお話です。APIというのは、その名の通り、プログラムからWeb上のデータを取得したり、何らかのサービスの機能を実行したりするための、呼び出し方式を定めた規約です。APIでは、HTTPリクエストに呼び出したい機能の内容を指定し、結果をHTTPレスポンスとして受け取るというのが一連の流れになります。APIの主な実装方式としてはSOAPとRESTがありますが、このうち、XMLでリクエストを組み立てるSOAPは最近は廃れてきた感じです。APIはNew-WebServiceProxyコマンドレットで対応しています。APIを指すことが殆どです。DELETE:データの削除)、操作に対するパラメータ=クエリストリング(GETの場合)もしくはリクエストボディ(POSTの場合)、結果の返却=HTTPレスポンス(JSON、XML等)となるのが基本です。RESTの呼び出しは基本的にステートレスなものとなります。要はセッション情報を持たない≒cookieを使わない、ってことです。APIを簡便に利用するためにInvoke-RestMethodコマンドレットが用意されています。3.0から)Invoke-RestMethodコマンドレットのパラメータ指定Invoke-RestMethodコマンドレットのパラメータについては、実は前々回に取り上げたInvoke-WebRequestコマンドレットと同じです(IEのパーサーを使うことはないので、-UseBasicParsingも無いですが)。APIの形式は前述の通りなので、利用するパラメータは限られてきます。パラメータ(連想配列あるいはJSONやXML等)となるかと思います。@{Authorization="認証情報"}のような指定も必要になることがあります。Invoke-RestMethodコマンドレットのレスポンスInvoke-RestMethodコマンドレットがInvoke-WebRequestコマンドレットと異なる最大のポイントは、レスポンス文字列の種類によって、自動的に出力オブジェクトの型が切り替わるところです。私の調べた限りでは以下のような対応になっているようです。APIでAEDの所在地情報を検索し、JSONで結果を得ることができます。-AutoSizeのようにします。ここで$responseには、JSON形式のデータをパースしてPSCustomObject化したデータが格納されるので、あとはFormat-Tableコマンドレットで見やすい形で出力してあげれば良いでしょう。結果はこんな感じです。MAP上で表示する」なんてこともできます。APIにQueryStringでパラメータ(経度、緯度)情報を渡しているところと、レスポンスから生成されたオブジェクトのプロパティ値をマップ表示の際のパラメータとして利用しているところに注目してください。APIの一種と考えて良いと思います。ここではこのブログのRSSを取得する例を示します。Format-ListDescriptionの加工がやや適当(HTMLタグっぽいところを削除して50文字に切り詰めてるだけ)ですが、少し見やすくしています。結果は以下のように表示されます。RSSの結果は、1エントリがXMLElement型のオブジェクトとして出力されるので、データの取扱いが比較的楽だと思います。APIの良い例がなかったので省略してますが、基本的には前回取り上げた、XHTMLをXMLとしてパースする方法と同じやり方です。Invoke-RestMethodの場合は[xml]型アクセラレータによる変換は不要で、いきなりXmlDocumentオブジェクトが得られます。現状の問題点レスポンスがコールバック関数つきのJSONP形式であるとかで、JSON、XML、RSS/ATOMのいずれの形式にも適合しない場合はプレーンテキストとして出力されてしまいます。その場合は、出力文字列を適宜加工した後に、[xml]型アクセラレータや、ConvertFrom-Jsonコマンドレット等により手動でオブジェクト化するようにしてください。その場合は敢えてInvoke-RestMethodを使わずInvoke-WebRequestで充分ですが。APIというのは大抵(特にPOSTの場合)、認証を要するのですが、最近よくあるのはTwitter等でもおなじみのOAuth認証です。ところがOAuth認証は結構めんどくさい処理で、何らかのライブラリを使わないとしんどいです。残念ながらPowerShellの標準コマンドレットには存在しないので、自前で頑張って書くか、既存のライブラリやコマンドを利用することになるかと思います。今回そこまで説明できませんでしたが、また機会があれば。記事のとおり、自分の全ツイートデータをダウンロードする機能がTwitterで利用可能になっています。ダウンロードされるzipファイルには、ツイートを表示するためのHTML、JavaScriptファイルのほか、CSV形式のデータ(tweets.csv)も含まれています。CSVファイルの処理といえばPowerShellが得意とするところです。このファイルを読み込んで、PowerShellで自分のツイートを分析してみましょう。具体的にダウンロードする方法は上記記事を参考にしていただいて、まずはダウンロードしたzipファイルからtweets.csvを解凍し、PowerShellのカレントディレクトリをtweets.csvのあるフォルダに移動させておいてください。毎回CSVを読み込むと時間がかかるので、まず以下のようにしてImport-Csvコマンドレットにより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変数にはそのオブジェクトの配列が格納されることになります。:Select-Objectコマンドレット(エイリアスselect)はオブジェクトの絞り込みに使います。このCSVファイルではツイートの並び順がタイムスタンプの降順なので、最初のツイートは一番最後の行となります。Speedがなんか懐かしい。いえばEAのシムシティ不具合お詫び無料DL特典、何選ぼうかなあ。シティ4あるけど英語版という噂だし2013やった後につらいもんがありそう。いうわけでシムシティ大好きなんで、私の街を返してください......Format-Listコマンドレット(エイリアスfl)を使うと必要なプロパティ値のみ抽出してリスト形式で表示できます。眠いのってもしかしてアレルギールの副作用かも。眠いなあ...Where-Objectコマンドレット(エイリアスwhere)を使うとオブジェクト配列のうち特定条件のもののみ抽出できます。ここではツイート本文(textプロパティ)に"眠い"という文字列が含まれているものを抽出しています。家族でおせちをたべた。おいしかった...もちろん本文に含まれる文字列以外にも、timestamp(ツイート時刻)で抽出するなどもできます。ここではtimestampがGMTで分かりづらく、かつ文字列のため扱いづらいので、Select-Objectに集計プロパティを指定してDateTime型に変換しています。Format-ListやSelect-Objectに指定する集計プロパティの書式は、@{L="ラベル";E={値を返すスクリプトブロック}}のように連想配列で指定します。LはLabel、EはExpressionのように省略せずに指定してもOKです。集計プロパティはあんまり解説を見かけないですけども、オブジェクトを処理するコマンドレットの多くで利用可能できわめて重要なので覚えておくと良いと思います。$_.timestamp}},text応用でこんなんもできます。0:00:00ちょうどのツイートを抽出します。私はかつてよるほ成功したことがないので結果は何も返ってきませんけど。expanded_urlshttp://ja.wikipedia.org/wiki/%E5%B2%A1%E7%B4%A0%E4%B8%96http://htn.to/4oxXDNhttp://guitarrapc.wordpress.com...whereによる抽出を応用するとこういうこともできます。expanded_urls列は本文中のURLが複数含まれているとそれらは,で区切られるため、可変長の行となります。Import-Csvコマンドレットはこのような可変長なCSVに対応していないので、複数URLがあっても最初の1つのみ取得します。それとexpanded_urlsが追加されたのはt.coによるURL短縮が始まってからなので、昔のツイートにこの値は含まれていません。2012/09...ツイート分析と言えばやはりツイート数統計を取ることから始まるでしょう。統計を取るにはGroup-Objectコマンドレット(エイリアスgroup)が使えます。ここでもグループ化キーとして集計プロパティを指定してやります。ツイートの「年/月」を文字列化し、それが同じツイートでグループ化することで、月別ツイート数の統計が表示できるわけです。23やり方としては先ほどのとほぼ同じです。-ExpandPropertyはパイプライン入力でオブジェクトのプロパティ値を取得できるのでよく使います。プロパティ名」でも取れますね。Sort-Objectコマンドレット(エイリアスsort)でもソートキーとして集計プロパティを指定できます。ここではNameプロパティ(グループ化キーの値)をintに変換したものをキーにソートしています。Saturdayこれもやり方としてはほぼ同じ。ソートキーはDayOfWeek列挙体にキャストしてちゃんと曜日順に並ぶようにしてます。:ここからはツイート数の計測をしていきます。単純にツイート総数を取るだけならMeasure-Objectコマンドレット(エイリアスmeasure)を使うだけでOKです。Averageなどは対応するスイッチパラメータ(-Averageなど)を指定すると計測されますが、この場合は元オブジェクトが数値ではないのでエラーになります。TextLengthツイート文字数を計測するとき、元のオブジェクトにはツイート文字数を返すプロパティはないので、Select-ObjectコマンドレットにTextLengthという集計プロパティを指定して新たに作ってしまいます。Measure-Objectコマンドレットは-Propertyパラメータにより対象オブジェクトのどのプロパティ値を計測するか指定できます。私の総ツイート文字数は183万です。リツイートの場合はretweeted_status_idにリツイート元のツイートIDが入るので、このIDがあるものはWhere-Objectで除外してます。ツイート本文の<や&などはHTMLエンコードされたものがtext列に格納されているので、HttpUtilityを使ってデコードしてから文字数をカウントしています。0.023254609488295Measure-Objectコマンドレットは計測方法を指定することはできないので、独自の計測を行う場合はこんな感じでコードめいたものを書く必要が出てくるかと思います。RT率たったの2%か...ゴミめ...ForEach-Object(エイリアスforeach)は1個のスクリプトブロックをパラメータに指定するとprocessブロック相当の列挙部分を実行しますが、このように3個指定すると、それぞれbegin(初期化処理)、process、end(終了処理)ブロックに割り振られます。ここではbeginブロックで変数初期化、processブロックで通常ツイートとリツイートを加算、endブロックで計測値をPSObjectに格納して出力してます。3.0ではカスタムオブジェクトを作る場合は「[pscustomobject]@{連想配列}」で書くほうが楽です。{$count}137覚えてないから数えます。数値だけを出力するならこんな感じでシンプルに書けますね。twigadge...ここからはいろんなランキングを取得してみます。ツイートに使ったTwitterクライアントのランキング。ここでもGroup-Objectを使っています。クライアント名はクライアント配布URLがaタグで含まれているのでそれを-replace演算子で削ったものをグループ化キーとしています。ランキングなので最後はCountで降順ソート。Countのパターンになるかと思います。リプライツイートはin_reply_to_user_id列にリプライしたユーザーIDが含まれるのでまずはそれでフィルタし、ユーザー名はツイート本文から取ります。ユーザー名は-match演算子を使って正規表現で抽出します。$matches自動変数は連想配列で、[0]にマッチ全体が、[1],[2],...にはサブ式のキャプチャが入ります。ちなみにサブ式に名前を付けてるとキー名が数値ではなくサブ式名となります。#techedj2009...ハッシュタグも同様のアプローチで取れますが、ハッシュタグは1ツイートに複数あることがあり、-match演算子だと複数のマッチは取れないので[regex]を使って取得しています。おわりにPowerShellのオブジェクト処理用コマンドレットを用いると、CSVデータの分析ができます。普通はログファイル等を解析するのに使うわけですが、こういう身近なデータを扱ってみるのも面白いんじゃないでしょうか。PowerShellの勉強にもなると思います。2012が始まりました。初日は私が担当させていただきます。3.0の新機能!...ではなく、初心に返って、PowerShellの「関数」ってどう書くのがいいのかというお話をします。1.0から変わっていない基本の話です。これは今までずっと書きたかったネタですがなかなか書く暇がなくて放置してたものです。3.0の話はきっと他の皆さんが書いて下さるはず!私もまた順番が回ってきたら書こうと思います。PowerShellの関数は従来言語とだいぶ違うPowerShellを使いこなすようになってくると、他の言語を使う時と同じで、定型処理は関数として一つにまとめたくなってきます。ところが他の言語と同じような感覚で関数を書くと、どうもうまくいかないのです。引数にフォルダパスとフォルダ名を指定すると、指定フォルダが存在すればFalseを返し、存在しなければ作成してTrueを返す関数を書いてみました。NewFolderと呼び出せばいいんですが(まあ最初はここもつまづきポイントではありますが)、この実行結果は以下のようになります。NewFolderTrueフォルダが作成されてTrueが返却されることを想定していたのに、なんか余計な出力が混じってしまっています。なんでしょうこれは?実はPowerShell関数内で値が出力されると、returnキーワードがついてなくてもすべて呼び出し元に出力されるという仕様なのです。PowerShellにおけるreturnキーワードの効果は「後続処理を打ち切って呼び出し元に戻る。returnの後に値が指定してあればそれを最後の値として戻す」となります。そのため、呼び出し元に返したくない出力が関数内にある場合は、すべて[void]にキャストしたり|Out-Nullとしてリダイレクトするなどして出力を破棄する必要があるのです。ように破棄してやる必要があるわけです。先ほどの例を見ると、「いやいやなんでそんな訳のわからない仕様なんだよ、returnあるときだけ値返せよ」とお思いかと思います。これはPowerShellの特長の一つである、コマンドのパイプラインによる連携を行うための仕様なんです。ここでコマンドを繋ぐパイプラインがどういう動作をしてるか、おさらいします。{$_.Path}これはハンドル数が500以上のプロセスのメインモジュールファイルのパスを取得するというコマンドで、別に何の変哲もありません。稼働中のすべてのプロセスの一覧を配列として取得する。�@で取得した配列を走査して、Handlesプロパティの値を調べる。Handlesが500以上のオブジェクトだけ抽出した配列を生成する。�Aで生成した配列を列挙して、{}内のスクリプトをそれぞれ実行する。稼働中の1つのプロセスオブジェクトを取得して次のコマンドへ送る。そのプロセスのハンドル数が500以上なら、次のコマンドへ送る。そのプロセスに対して{}内のスクリプトを実行する。未取得のプロセスが残っていれば�@に戻る。いう動きをしています。パイプラインの手前で一旦すべての処理を終えてから、出力オブジェクトがまとめて配列という形で次のコマンドに送られるのではなく、オブジェクトがパイプラインの先頭から末尾に向けて1つずつ通過していき、それが先頭コマンドの出力オブジェクト数だけ繰り返される、という動作をしているのです。これがPowerShellのパイプライン処理が、従来の処理系での関数と決定的に違うところで、パイプラインによって複数のコマンドが、あたかももとからあった単一のコマンドのように密に連携するわけです。(この処理、.NETのLINQにちょっと似てると思う方もいらっしゃると思います。LINQとは全然違うものです。類似点も多いのでいずれ比較なんかを書きたいと思ってます)パイプラインで連携可能な関数の書き方さて、先ほどのパイプラインの話ではコマンドレットを連携させていました。PowerShellにおいてはコマンドレットも関数も、それが.NETのクラスかPowerShellのスクリプトなのかの違いがあるだけで、基本は同じ「コマンド」です。関数もコマンドレットと同様、適切な記述をおこなえば、パイプラインでコマンド同士を連携させることが可能です。以下に、Get-Repeatという関数の例を挙げます。この関数は-Textパラメータに文字列を指定し、-Countパラメータに回数を指定すると、指定文字列を指定回数分連結した文字列を出力する、という何の変哲もない関数です。パイプラインからの入力を受け付け、次のパイプラインへ出力することを想定した作りになっています。}}以下は実行例です。2ababcdcdこのように、パラメータに値を指定してもパイプラインから入力しても、スカラー値(配列ではない単一のオブジェクト)でも配列でも、正しく処理されています。この関数をポイントごとに見ていきましょう。PowerShellの正式な関数はparam節、beginブロック、processブロック、endブロックに分かれます。param節にはパラメータを指定します。beginブロックにはパイプラインで連携した際、最初の1回だけ実行される初期化処理、endブロックには最後の1回だけ実行される後始末処理を記述します。beginとendは今回の例では内容を省略しています。processブロックには、パイプラインから入力された1つのオブジェクトに対してその都度実行される処理を記述します。ちなみに、コマンド�@|コマンド�A|コマンド�Bとある場合、各コマンドにおけるbegin,process,endブロックは次のような順番で呼び出されます。コマンド�@begin→コマンド�Abegin→コマンド�Bbegin→{コマンド�@process→コマンド�Aprocess→コマンド�Bprocess→コマンド�@process...}→コマンド�@end→コマンド�Aend→コマンド�Bendprocessブロックでの処理は、通常はパイプラインだけではなくパラメータからも値を入力できるようにしておきます。今回のパラメータには「このパラメータは必須である」を意味するMandatory=$trueもあわせて指定しています。ようにパラメータの型を配列型にしておくことで、どちらを指定しても処理できるようにしています。processブロックではパラメータ経由で配列値が渡された場合に、各要素に対して処理を行うためforeachループを設けています。ちなみにスカラー値が渡された場合もforeachは問題なく処理します。processブロック内では、returnは記述しません。returnするとその時点で関数が終了してしまうので正しくすべての出力ができなくなってしまいます。この例の関数のように入力型と出力型が同一の場合は、processブロックでは1オブジェクトの入力に対して、1オブジェクトを出力するようにしておくと、他のコマンドと連携させやすくなります。Where-Objectコマンドレットのようにフィルタ処理を行う関数の場合は、条件によっては何も出力しないようにします(空の配列とか$nullを返すのではないことに注意)。入力オブジェクトから何らかの配列値を出力する場合もありえます。最低限、これらのポイントを押さえて関数を記述すると、他のコマンドとパイプラインで連携しやすい、PowerShellらしい関数を書くことができると思います。まとめPowerShellでは従来言語と同じ感覚で関数を書くと、うまくいかないことが多いです。処理をひとまとめにしたいというニーズだけならばそれでも問題ないのですが、関数同士を組み合わせたいときに問題が顕在化します。パイプラインの真の動作を理解し、パイプラインの中に組み込んで動作させることを想定した関数を記述すると、他のコマンドレットあるいは自作関数と連携しやすくなり、PowerShellの真の力を解放することができると思います。2012の1日目にしてはえらい固いネタかもですが、基本をおさらいするのも大事ですよね。明日は@jsakamotoさんの番です。@mutaguchiBooks書籍版。