2008年10月05日

PowerShellで下層ディレクトリまでファイルを探す

最初は再帰が必要かと思ったのですが目的のファイルは「index.html」と決まっているし、階層も判っているのでもっと簡単ではと予想。

【注意】前回の記事に引き続き「PS >」となっているのは、PowerShell 上で入力したコマンドであることを示しています。「PS >」自体は入力してはいけない。

PowerShell上にて「dir」を実行するとカレントディレクトリ(フォルダ)のファイル一覧が表示されるのはコマンドプロンプトと同じ。しかし、ここで実行されるのは PowerShell のコマンドレット Get-ChildItem のエイリアスです。だから、最初に

ディレクトリ: Microsoft.PowerShell.Core\FileSystem::C:\Documents and Settings\n_shimizu


のような表示があります(XP の場合。Vista だと最後は、C:\users\ユーザ名 になる)。
というわけで PowerShell のコマンドレットとして調べなければならない。さて、ここで方針を考えてみます。

1.サーバ上のユーザディレクトリは「sthome」以下にある。
2.生徒のユーザディレクトリ名は生徒ユーザ名と同じにしてある。(例)「s1101」、「s1102」。……、「s1839」、「s1840」
3.生徒のユーザディレクトリ直下に「index.html」ファイルがあるとする。
4.すべてのユーザディレクトリ内を調べ、index.html を探しだし、次の操作をする
   (1)親ディレクトリ名からユーザ名を特定する
   (2)index.html を正規表現による置換でHTMLタグを取り除き純粋な文字数をカウントする
5.上記の結果からユーザ名−文字数のセットになったCSVファイルを作成する、または作成しやすい形にする


さて、ディレクトリ関係を図にすると以下のようになります。

sthome┬s1101─index.html
     ├s1102─index.html
     ├s1103─index.html
     〜
     ├s1839─index.html
     └s1840─index.html


たまに欠番やその時欠席をしていて「index.html」自体が存在しないディレクトリ(フォルダ)もあります。それは除外するつもり。

まず、サブディレクトリ(フォルダ)まで対象にするにはコマンドプロンプトの dir コマンドでは「/S」を付けるが PowerShell 上ではどうするかヘルプで調べてみると「Recurse パラメータを使用して、すべての子コンテナの項目を取得できます。」とあるので「-Recurse」を付ければ良いことが分かります。

試してみる。

PS > dir -Recurse
(結果省略)


おー下位ディレクトリまでどんどん出てくる。じゃあコマンドプロンプトのように index.html だけを表示させようと次のように打ってみる。

PS > dir -Recurse index.html


すると「パス '〜sthome\index.html' が存在しないため検出できません。」と出ました。あれれ?

しばらく悩むも、index.html と書いたものが「パス」として判断されている(ディレクトリかファイルかという区別がなく、またカレントディレクトリも敢えて指定しなければいけない?)のではと気づき、カレントディレクトリを指定してみます。

PS > dir ./ -Recurse index.html
index.html
index.html
index.html
index.html
index.html




おー今度はちゃんと表示されました。

さて、続いて「親ディレクトリ名からユーザ名を取り出す」作業。

実は、ループを使わずに上記 dir(Get-ChildItem) コマンドレットを使ったのにはわけがあります。オブジェクト指向っぽいやり方をやってみたかったのです(笑)。

dir の結果表示は文字列だが、実行結果の実体はプロパティやメソッドを持ったオブジェクトである、というのが PowerShell の大きな特徴です。つまり「index.html」ファイルを探し当てたらそこから逆にフルパスを求めるプロパティがあるんじゃないかと思ったのです。この辺の感覚は Ruby を勉強した時に身に付いたこと。

さて、オブジェクトに対してどんなプロパティやメソッドがあるかを調べるのは Get-Member コマンドレットを使うらしい。

(参考)オブジェクト指向なコマンド環境「Powershell」を試してみた - てっく煮ブログ

さて、dir の結果を渡すのだから試しに次のようにやってみました。

PS > Get-member (dir)


すると「get-member にオブジェクトが指定されていません。」という返事。Get-Member にはパイプで渡さなければ行けないようです。この辺の判断がまだよく分かってないなぁ。
そこで、

PS > dir | get-member


としてみる。最初に

TypeName: System.IO.DirectoryInfo


という表示と共にたくさんのメソッドやプロパティがずらーっと表示されます。

次に

PS > dir ./ -Recurse index.html | get-member


としてみると、今度は……ん?表示されるプロパティやメソッドが違うぞ?それに

TypeName: System.IO.FileInfo


と表示されている。

少し悩んで、判りました。「sthome」ディレクトリ(フォルダ)内にあるのはユーザディレクトリのみ。つまりすべてディレクトリです。だから「IO.DirectoryInfo」になり、「index.html」を探った結果は当然「index.html」というファイルだけの集まりだから「IO.FileInfo」になるわけです。なるほど〜。

さて、FileInfoオブジェクトを丹念に見ていくと、「get_parent」「get_directoryname」「get_name」といったメソッドや「PSParentPath」というプロパティがあります。片っ端から試してみる。いかにも「親ディレクトリ」という感じがするんだけど……。どうも直接親ディレクトリ名だけを取り出すことはできないらしい。

PowerShellでパス文字列を操作する − @IT

を参考にチャレンジ。「Split-Path」コマンドレットでパスの一部を取り出せるとか。「-Leaf」パラメータを付ければその末端の要素を取り出せるそうです。でもなかなかうまくいかない。

#余談だけど、ツリーの一番最後が Leaf(葉)というのはよく言われる言い方なのか、Microsoft だけなのか。しゃれが効いている。

ん? たくさん出てくるものはFileInfoオブジェクトの更に配列オブジェクトになっているのかな?

そこでそれぞれの要素について実行する方法を捜す。先ほどの「てっく煮ブログ」さんのところに「foreach で全ての要素に対して実行」というものが書いてある。

コマンドレット | foreach { }


という書き方をすれば、コマンドレットの実行結果の配列になったオブジェクトの要素を一つずつとりだし、それを $_ として{ }の中で扱うことができるらしい。Ruby のブロックと同じと考えればいい。ちなみに foreachForEach-Object のコマンドレットのエイリアスです。

さて色々試行錯誤して次のように打てばうまく行くことが判りました。

PS > dir ./ index.html -recurse | foreach {split-path $_.PSParentPath -leaf}
s1101
s1102
s1103
s1104
s1105
s1106
s1107
s1108
s1109
s1110
s1111
s1112


s1839
s1840


うーん。うまくいきました。4−(1)まで進んだことになりますけど今日はここまで。
posted by n_shimizu at 10:15| Comment(0) | TrackBack(0) | PowerShell
この記事へのコメント
コメントを書く
お名前: [必須入力]

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/20578818
※言及リンクのないトラックバックは受信されません。

この記事へのトラックバック