PowerShell を使い始めて一番戸惑ったのが、ログの特定フィールドの切り出しのようなこと。
Get-WmiObject(gwmi) コマンドと Format-List(fl)コマンドを組み合わせて切り出した値の扱い。以下のコマンドで、Win32_Processor クラスを使えば、CPU情報を表示できます。
> gwmi -ComputerName . -class win32_processor
Caption : x86 Family 6 Model 54 Stepping 1
DeviceID : CPU0
Manufacturer : GenuineIntel
MaxClockSpeed : 2127
Name : Intel(R) Atom(TM) CPU D2700 @ 2.13GHz
SocketDesignation : CPUSocket
1台のPC情報を知りたい場合はこれでいいのですが、10台以上ともなるとPCごとにチマチマコマンドを発行する気になりません。100台、1000台ともなると、絶対に嫌!例えば、社内のPC毎に
PC名, CPU, メモリ容量, HDD容量, ビデオチップ・・・・・
とCSVファイルを作って、最終的にExcel などの表計算ソフトに読み込ませたいというのは、PCメンテナンスを行う人間にとっては当然の要求でしょう。
gwmi コマンドと fl コマンドの組み合わせによって、必要な「行」だけ切り出して表示することが出来ます。
欲しいのは行全部の情報ではなく、コロン右側の値です。
> gwmi -ComputerName . -class win32_processor | fl Name
Name : Intel(R) Atom(TM) CPU D2700 @ 2.13GHz
これを、PC毎に変数に代入して、最終的に -ComputerName オプションで指定するPC名と並べて出力すれば、簡単にPCスペック一覧表が出来上がるはずと言うわけです。
こういう感じの定型フォーマットの一覧情報から値を切り出すというのは、Unix の文字処理コマンドの得意なところ。
次の3行は、Webサーバーのログファイルですが、「ホスト名、ユーザー名、クッキー、日時、URL・・・・・」という感じで並んでいます。
ec2-52-3-127-144.compute-1.amazonaws.com - - [01/Aug/2016:00:11:58 +0900] "GET /robots.txt HTTP/1.1" 200 270 "-" "ltx71 - (http://ltx71.com/)"
ec2-52-3-127-144.compute-1.amazonaws.com - - [01/Aug/2016:00:12:45 +0900] "GET /notes/2009/08/15/ HTTP/1.1" 200 50614 "-" "ltx71 - (http://ltx71.com/)"
crawl-66-249-71-161.googlebot.com - - [01/Aug/2016:00:13:06 +0900] "GET /notes/feed/ HTTP/1.1" 304 - "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
例えば、ここから 3行目の、タイムスタンプ項目だけを抽出したい場合、head, tail, awk という3つのコマンドの組み合わせで、1行で取り出せます。
> cat test.txt | head -3 | tail -1 |awk '{print $4}' [01/Aug/2016:00:13:06
カギ括弧という余分なものが頭に付いていますけど、それは、sed コマンドを入れて削除すればこのとおり。
> cat test.txt | head -3 | tail -1 | awk '{print $4}' | sed s/\\[// 01/Aug/2016:00:13:06
このタイムスタンプを、変数 $ts に入れたければ、シングルバッククォテーションで囲んで、set で変数に代入。変数に入れてしまえば、後で好きな時に取り出して使えます。
> set ts=`cat test.txt | head -3 | tail -1 | awk '{print $4}' | sed s/\\[//` > echo $ts 01/Aug/2016:00:13:06
PowerShell でもこういう流れで処理すればいいんだろう と考えていたので、大はまり。
つまり、Name : で始まるCPU情報の行を、変数 $CPU に代入し、頭に付いている “Name : ” という文字を replace コマンドで消せばいいだろうと考えたものだから、やってみて出力された “Microsoft・・・・・・” という文字の羅列に「これ何????????」と何十時間も悩んでしまいました。
> $cpu = gwmi -ComputerName . -class win32_processor | fl Name
> $cpu -replace "Name : ", ""
Microsoft.PowerShell.Commands.Internal.Format.FormatStartData
Microsoft.PowerShell.Commands.Internal.Format.GroupStartData
Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData
Microsoft.PowerShell.Commands.Internal.Format.GroupEndData
Microsoft.PowerShell.Commands.Internal.Format.FormatEndData
PowerShell のパイプ( |) はテキストフィルターじゃなく、オブジェクトフィルターだと書いてある解説を読んでもさっぱり意味不明。
次のように書けばいいという検索結果にたどり着くまでに2,3日掛かりました。
> $cpu = (gwmi -ComputerName . -class win32_processor).Name > echo $cpu Intel(R) Atom(TM) CPU D2700 @ 2.13GHz
なんじゃこりゃ!この簡単さは何!?
つまり、gwmi コマンドと、その出力結果の表左側のタイトルをピリオド(.) で結べば、右側の値が得られるって事。(Unixライクに echo コマンドを書いていますけど、echo はあってもなくても結果は同じ。)
> gwmi -ComputerName . -class win32_processor
Caption : x86 Family 6 Model 54 Stepping 1
DeviceID : CPU0
Manufacturer : GenuineIntel
MaxClockSpeed : 2127
Name : Intel(R) Atom(TM) CPU D2700 @ 2.13GHz
SocketDesignation : CPUSocket
CPUメーカーを取り込みたければ、(gwmi -ComputerName . -class win32_processor).Manufacturer と書けばいいわけです。
> (gwmi -ComputerName . -class win32_processor).Manufacturer GenuineIntel
一度、変数に取り込んだ後、その変数と. でつないでも結果は同じ。複数の値を変数に代入したい場合はこっちの方が便利。
> $manufacturer_line = gwmi -ComputerName . -class win32_processor > $manufacturer_line.Manufacturer GenuineIntel
なんというPowerShellのお手軽さ。
Unixシェルスクリプトで考えてきた私には、こういう方法がすぐにわからないのね。
余談ですが、この結論にたどり着くまでにどうしていたかというと、一度結果をファイルに出力して、再度プログラムに取り込んでいました。ファイルに吐き出してしまえば余分な属性は消えてしまうので、もう一度読み込めば引き続き処理を続けられました。