特集
» 2007年08月30日 00時00分 公開

Windows PowerShellコマンド&スクリプティング入門:PowerShellの基本(前編) (4/5)

[山田祥寛,著]

 PowerShellのコマンドレットは、戻り値として「.NET Frameworkのオブジェクト(以降「.NETオブジェクト」と呼ぶ)」を返す。これはともすると、そのまま軽く通り過ぎてしまいそうな事実であるが、PowerShellを使いこなすうえで重要なポイントを含んでいる。そのポイントについては、これから追って解説していくものとして、ここではまず、実際にコマンドレットが.NETオブジェクトを返すことを確認してみよう。

※コマンドレットの戻り値型を確認するコード

PS > $result = Get-Date
PS > $result.GetType()

IsPublic IsSerial Name      BaseType
-------- -------- ----      --------
True     True     DateTime  System.ValueType

 Get-Dateは、システムの現在日時を取得するためのコマンドレットだ。ただ単に「Get-Date」として呼び出すと「2007年8月8日 15:01:53」のような日付時刻の文字列を返すが、ここではいったん変数$resultに格納しておくことにしよう(ちなみにPowerShellの変数は、1文字目は「$」、2文字目以降は「英字、数字、アンダースコア」とする必要がある)。

 そして、次はこの変数$resultを介して、GetTypeメソッドを呼び出してみる。GetTypeメソッドは.NET Frameworkの全オブジェクトの基底クラスとなるObjectクラスのメンバーであり、現在のオブジェクトの型情報を返す。コマンドレットが.NETオブジェクトを返すならば、GetTypeメソッドは呼び出し可能であるはずだ。

 果たして、GetTypeメソッドが変数$result(戻り値)の型情報を返すことを確認できた。上のリストの結果から、Get-Dateコマンドレットが戻り値としてSystem.DateTimeオブジェクトを返すことが分かったはずだ。

 ここではただ単に戻り値の型情報を出力しているだけだが、オブジェクトを取得しているということは、System.DateTimeオブジェクトで公開されているメンバーにもアクセスできるということだ。例えば、現在日時の曜日だけを取り出したいならば、先ほど取得した変数$result(DateTimeオブジェクト)を介して、DayOfWeekプロパティを呼び出せばよい。

PS > $result.DayOfWeek
Wednesday

パイプラインを流れる.NETオブジェクト

 以上でコマンドレットが.NETオブジェクトを返すことが確認できた。次は本題の、PowerShellを利用するうえで、この事実がどんな恩恵をもたらしているのかを見てみることにしよう。

 ここで紹介するパイプライン処理(以降「パイプ処理」)とは、従来のコマンド・プロンプトでもおなじみの機能で、あるコマンドによる処理結果を、異なるほかのコマンドの入力として引き渡す機能のこと。例えば、コマンド・プロンプト上で以下のような記述を利用したことがある方は少なくないのではないだろうか。

※コマンド・プロンプト上でファイルの一覧を表示し、1ページずつ表示させる例

> dir | more

 これがまさにパイプの代表的な一例で、ここではdirコマンドで取得したファイル一覧を、1画面ごとに入力の内容を出力するmoreコマンドに引き渡すことで、ファイル・リストをページ単位に区切って表示しているわけだ。パイプ処理を利用することで、複数のコマンドを組み合わせた処理を簡潔に記述することができることからも、パイプをいかに使いこなすかがシェル上の作業効率を左右するといってもよいだろう。

 もっとも、従来のコマンド・プロンプトやUNIXシェルでは、コマンドの結果はテキスト・データであった。つまり、コマンドの結果を受け取ってパイプ処理を行う場合にも、いったん入力テキストを解析し、処理に必要な情報を取り出す必要があったわけだ。しかし、このようなテキスト解析は煩雑でもあり、入力するコマンドによっては正しい結果を取り出せなかったり、そもそも適切な正規表現を記述するには、それなりのスキルが要求されたりすることとなる。

 しかし、PowerShellのコマンドレットは(繰り返しであるが)オブジェクトを返す。つまり、パイプ処理に際しても、後続のコマンドに対してオブジェクトを引き渡すことができるので、こうした煩雑な解析は不要になるというわけだ。パイプの先のコマンドでは、オブジェクトがもともと公開しているプロパティを介して、必要な情報を取り出すことができる。

 もっとも、「パイプをオブジェクトが流れる」といっても、なかなかイメージが分かりにくいかもしれないので、以下では、パイプ処理で利用されるコマンドレットの中でも特に重要ないくつかについて、具体的な使用例を挙げてみることにしよう。

■Where-Objectコマンドレット
 Where-Objectコマンドレットは、パイプ経由で渡された入力オブジェクトを特定の条件式で比較し、合致した(Trueと判定された)オブジェクトのみを出力する。例えば、次のリストでは、Get-ChildItemコマンドレットで取得したファイル一覧から、サイズが1Mbytes以上のものだけを表示している。

※サイズが1Mbytes以上のファイルのみを表示

PS > Get-ChildItem | Where-Object {$_.Length -ge 1MB}

    ディレクトリ: Microsoft.PowerShell.Core\FileSystem::C:\WINDOWS

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        2007/08/05     14:25 2137186304 MEMORY.DMP
-ar--        2004/08/05     21:00    1555860 SET31.tmp
-a---        2004/08/18     12:02    1080708 SETUPAPI.DEL
-a---        2007/08/08     17:30    1940514 WindowsUpdate.log

 Where-Objectコマンドレットにおいて、条件式はスクリプト・ブロック({ 〜 }で囲まれた部分)に記述することができる。変数「$_」はPowerShell上で利用可能な特殊変数の1つで、パイプ経由で渡されたオブジェクトを表す。また、「-ge」は比較演算子の1つで、「以上」を表すものだ(それ以外の比較演算子は次の表を参照)。

演算子 概要 例(条件式の部分のみ)
-eq 等しい(=) $_.Name -eq "wga.log"
-ne 等しくない(≠) $_.Name -ne "wga.log"
-gt より大きい(>) $_.Length -gt 1MB
-ge 以上(≧) $_.Length -ge 1MB
-lt 未満(<) $_.Length -lt 1MB
-le 以下(≦) $_.Length -le 1MB
-like あいまい検索(ワイルドカード) $_.Name -like "*.log"
-match 正規表現検索 $_.Name -match "[a-z]{1,}\.log"
PowerShellの比較演算子
ただし、文字列比較で大文字/小文字を区別する場合には「c」、しない場合には「i」を演算子の先頭に付ける。例えば「-ieq」などとする。

 つまりここでは、Get-ChildItemコマンドレットで取得したFileInfoオブジェクト群のそれぞれのLengthプロパティ(サイズ)を確認し、その値が1Mbytes以上である場合にのみ、そのオブジェクトを出力しているというわけだ。

 同じ要領で、例えば最終更新日が6カ月以上前のファイルのみを取得したければ、次のようにする。

※最終更新日が6カ月以上前のファイルのみを取得する例

PS > Get-ChildItem | Where-Object {$_.LastWriteTime -lt (Get-Date).AddMonths(-6)}

    ディレクトリ: Microsoft.PowerShell.Core\FileSystem::C:\WINDOWS

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        2004/08/05     21:00      82944 clock.avi
-a---        2004/08/18     11:27          0 CONTROL.INI
-a---        2002/06/11      7:26     787512 DELL.BMP
-a---        2004/08/06     18:00          2 DESKTOP.INI
-a---        2005/05/31      5:33      98360 dla.exe
-a---        2004/08/05     21:00    1025536 explorer.exe
-a---        2004/08/05     21:00         80 explorer.scf
……(以下省略)……

 AddMonthsメソッドは、Get-Dateコマンドレットによって返されるDateTimeオブジェクトのメソッドで、指定された月数を現在の日付に加算するものだ。従って、ここではGet-ChildItemコマンドレットから渡されたFileInfoオブジェクト群のLastWriteTimeプロパティ(最終更新日)を6カ月前の日付と比較し、それ以前のもののみを出力することになる。

■ForEach-Objectコマンド
  ForEach-Objectコマンドレットは、パイプ経由で渡された入力オブジェクトを順番に出力する。例えば、次のリストはGet-ChildItem/Where-Objectコマンドレット経由で取得した「6カ月以上更新のないファイル」を削除するためのコマンドの例である。やや長めではあるが、上の実行結果をもとに更にパイプでつないでいるだけなので、前半部を理解していれば、さほどに難しいことはないだろう。

 なお、そろそろコマンドが長くなってきたので、次の例ではコマンドを複数行に分けている。行末を「|」(パイプ記号)で終えた場合、PowerShellではコマンドが複数行にまたがるものと見なし、次行では「>>」という続きの入力を促すプロンプト記号が表示される。パイプ処理を伴うコマンドは、往々にして長く複雑になりがちであるので、このように、適宜改行を入れておくと分かりやすいだろう。

※6カ月以上更新のないファイルを削除する

PS > Get-ChildItem |
>> Where-Object {$_.LastWriteTime -lt (Get-Date).AddMonths(-6)} |
>> ForEach-Object {$_.Delete()}

 注目していただきたいのは、太字で示した「ForEach-Object {$_.Delete()}」の部分だ。Where-Objectコマンドレットの場合と同様、パイプ経由で渡された入力オブジェクトの処理は「{ 〜 }」で囲まれたスクリプトブロックの中に記述できる。ここでは、特殊変数「$_」にはWhere-Objectコマンドレットでフィルタ済みのFileInfoオブジェクトが渡されているはずなので、Deleteメソッドによって、該当のファイルを削除している。

■Format-Listコマンドレット
  Format-Listコマンドレットは、パイプ経由で渡されたオブジェクトの内容をリスト形式で出力する。コマンドレットは、デフォルトで、その処理結果をテーブル形式で出力するが、表示すべきプロパティの数が多い場合には、テーブル形式では一部の情報が省略されてしまうなど、十分な情報が得られない。そのような場合には、Format-Listコマンドレットでリスト形式に整形すれば、必要な情報を見やすい形式で出力できる。

 次のリストは、Get-ChildItemコマンドレットで取得したファイル一覧を、Format-Listコマンドレットで整形した例だ。

※Get-ChildItemコマンドレットの処理結果をリスト形式に整形する

PS > Get-ChildItem | Format-List *

PSPath            : Microsoft.PowerShell.Core\FileSystem::C:\Documents and Settings\Yamada\Favor
PSParentPath      : Microsoft.PowerShell.Core\FileSystem::C:\Documents and Settings\Yamada
PSChildName       : Favorites
PSDrive           : C
PSProvider        : Microsoft.PowerShell.Core\FileSystem
PSIsContainer     : True
Mode              : d-r--
Name              : Favorites
  ...中略...
LastWriteTime     : 2007/07/10 11:58:02
LastWriteTimeUtc  : 2007/07/10 2:58:02
Attributes        : ReadOnly, Directory

PSPath            : Microsoft.PowerShell.Core\FileSystem::C:\Documents and Settings\Yamada\My Do
PSParentPath      : Microsoft.PowerShell.Core\FileSystem::C:\Documents and Settings\Yamada
……(以下省略)……

*)このコマンドを実行すると、カレント・フォルダの内容によっては大量の結果が表示されるはずだ。このような場合、通常は、先ほども紹介したmoreコマンドを利用して、「Get-ChildItem | Format-List * | more」のように1ページずつ表示させるとよい。

 表形式では表示されていなかった多くのプロパティ情報が表示されていることが確認できるはずだ。Format-Listコマンドレットでパラメータとして「*」を指定しているのは、入力オブジェクトで公開されているすべてのプロパティを出力するという意味である。もしも特定のプロパティ――例えば、NameプロパティとLengthプロパティだけを出力したいという場合には、

PS > Get-ChildItem | Format-List Name,Length

のように、出力したいプロパティをカンマ区切りで指定することも可能だ。

[参考]PowerShell独自のプロパティ

 Format-Listコマンドレットによる出力結果を見て、はたと違和感を覚えた方もいるかもしれない。というのも、本来のDirectoryInfo/FileInfoオブジェクトには存在しないはずの「PS〜」で始まるプロパティが多く表示されているのだ。

 これは、(名前からも容易に想像できるように)PowerShellが本来の.NETオブジェクトを拡張しているためだ。Get-ChildItemコマンドレットに限らず、PowerShellでは多くのコマンドレットが戻り値オブジェクトを内部的に拡張し、必要な情報を追加している。どのような拡張がなされているのか興味のある方は、それぞれのコマンドレットの結果をFormat-Listコマンドレットでリスト表示してみるとよいだろう。


[コラム]moreコマンドの実体

 ちなみにPowerShellでも、「dir | more」というコマンド・プロンプトふうの記述は利用可能だ。もっとも、このmoreコマンドは、厳密にはコマンド・プロンプトで提供されていたそれではなく、PowerShellの「関数」(エイリアスではない)を利用して擬似的にマッピングされているにすぎない(関数については後編で詳述の予定)。具体的な実装については、以下のリストのようにすれば確認できる。

PS > Get-Command -CommandType function "more" | Format-List

Name        : more
CommandType : Function
Definition  : param([string[]]$paths);  if(($paths -ne $null) -and ($paths.length -ne 0))  {    foreach ($local:file in $paths)    {        Get-Content $local:file | Out-Host -p    }  }  else { $input | Out-Host -p }


Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。