Tech-Ezo (Hokkaido PC and Network Users Group)
 Top Page  |  What's Tech-Ezo  |  Next Seminar  |  Seminar Log  |  Seminar Plan  |  Tips  |  life  |  Link  |
Scr.003 ステップアップ WSH - 使えそうなスクリプトを作って見よう
前ページへ   [ステップアップ WSHへ戻る]   次ページへ


■  Level.9  サブフォルダを対象にしてみる(でも1階層)
■ ■

 Level.8までに、指定のフォルダ内にある全てのテキストファイルの内容を検索できるようになりました。後は、今まで手を着けていなかったサブフォルダの処理を追加すれば、いよいよスクリプトの完成となります。

 ところで、フォルダの中にはフォルダがあって、そのフォルダの中にはまたフォルダがあって...と、フォルダは階層構造となっていますね。この階層構造をスクリプトで処理するには再帰処理を行う必要があるのですが、簡単な処理ながら、多少理解に苦しむところがあります。そこで、Level.9ではいったんテキスト検索処理部分を関数化し、サブフォルダを1階層だけ処理させるに留め、次のLevel.10で再帰構造にして、全てのサブフォルダを処理できるようにします。

再帰処理ってなに?

 でも「再帰処理」ってなんでしょうか?。再帰処理は、合わせ鏡とちょっと似ています。2つの鏡を向き合わせておくと、鏡の中に鏡があって、その鏡の中に鏡があって...と、無限の広がりを見せますね。再帰処理は、処理の中から自分自身を再度呼び出す処理です。例えば、関数aの処理のなかで、また関数aを呼び出すのです。そうすると、関数aの中から関数aを呼び出して、その関数aからさらに関数aを呼び出して...と、無限に処理を繰り返します。どうです?合わせ鏡にちょっと似ていませんか?。(えっ?そうでもない?)

 以下のスクリプトを見て下さい。

sample.vbs
Sub test_sub()
    WScript.Echo "くるくるぅ〜"
    test_sub()
End Sub
test_sub()

 この処理を実行すると、test_sub()の中からtest_sub()を呼び出しているので、無限に「くるくるぅ〜」と表示しつづけます。この例では、終了条件がありませんので無限ループに陥ってしまいますが、再帰処理は、適切な条件を加えて利用することで「フォルダの中にフォルダがありそのまた中にフォルダがあり...」などと行った階層構造や、木構造を楽に扱うことができるようになります。

サブフォルダの中まで対象にするには...

 ここで改めてLevel.8のスクリプトの構造を見てみましょう。

level8.vbsの構造
001〜018行目 ... フォルダ名、検索文字の入力処理。
019〜045行目 ... テキストファイル検索処理。指定のフォルダ内の全てのファイルを対象とする。
046〜051行目 ... サブフォルダ表示処理。指定のフォルダ内にある全てのフォルダを対象とする。

 大雑把にはこんな構造となっています。Level.9では、サブフォルダの処理部分に手を加えて、サブフォルダ内にあるテキストファイルまでを処理対象とするわけですから、修正部分は46〜51行目となります。この部分ですね。

今回の修正部分
046
047
048
049
050
051
' 指定のフォルダに格納された各サブフォルダを処理する
tmpMessage = "フォルダ一覧:" & vbNewLine
For Each FolderName In src.Subfolders
    tmpMessage = tmpMessage & FolderName & vbNewLine
Next
WScript.Echo tmpMessage

 47行目および51行目はメッセージ表示用の処理ですから削除するとして、ポイントは、指定のフォルダ下にあるサブフォルダを次々に処理している49行目をどうするか?、ということになります。

 さて、ここにどんな処理を入れましょうか?。サブフォルダ1つ1つに対して、その中に格納されたファイル一覧を取得し、テキストファイルを読み込み、内容を調査し...って、どこかで見たような処理ですね。そうです、19行目〜45行目のテキストファイル検索処理の内容そのものです。

 ということは、49行目の処理として、19行目〜45行目の内容をコピー&ペーストし、ちょこちょこっといじればなんとかなりそうです。でも、ちょっと待って下さい。それでは、冗長部分(同じロジック)が多くなり、プログラム的に美しくないです!!(笑。

 そこで、フォルダ内のテキストファイルを処理する処理部分を関数化して、必要な時に呼び出すようにします。すると、スクリプトの構造はこんな感じになります。

level9.vbsの構造
(1) テキストファイル処理関数の定義
(2) フォルダ名、検索文字の入力処理
(3) テキストファイル処理関数の呼び出し
(4) サブフォルダ処理
  → サブフォルダの処理のなかでテキストファイル処理関数の呼び出し

 それでは、この構造に沿ってLevel.8のスクリプトを作り替えて見ましょう。

テキストファイル処理関数の定義

 まず最初にテキストファイルを処理する関数を定義します。この部分は、Level.8の19〜44行目に相当しますので、この部分をコピーしてきます。加えて、関数化しますのでSub文の開始と終了を、最初と最後に入れましょう。FunctionではなくSubを使うのは戻り値が無いからです。

level09.vbs(テキストファイル処理関数)
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
' --< テキストファイル処理関数 >-------------------------------------------------
' 指定のフォルダに格納された各ファイルを処理する
Sub FileSrh(fn)
    Set subf = fso.GetFolder(fn)    ' 引数に指定されたフォルダの情報を取得
    For Each FileName In subf.Files
        FileEx = fso.GetExtensionName(FileName)   ' ファイル名から拡張子を抜き出す
        If LCase(FileEx) = "txt" Then             ' 拡張子を小文字化してから比較
    
          ' テキストファイルの内容読み込み処理
            Set TextFile = fso.OpenTextFile(FileName) ' テキストファイルのオープン
            srhLine = FileName & ":" & vbNewLine      ' 検索結果格納用変数の初期化
            srhFlg = 0                                ' 検索ヒットフラグのクリア
            Do Until TextFile.AtEndOfStream
                tmpLine = TextFile.ReadLine           ' 1行読み込み
    
                If regEx.Test(tmpLine) Then           ' 指定の文字列を含むか?
                    srhLine = srhLine & TextFile.Line - 1 & ":" & tmpLine & vbNewLine
                                                      ' 読み込んだ行のマージ
                    srhFlg =1                         ' 検索がヒットしたので1にする
                End If
            Loop
    
            If srhFlg = 1 Then                        ' 指定の文字列が含まれてたら
                WScript.Echo srhLine
            End If
    
        End If
    Next
End Sub

 このFileSrh()関数の引数fnには検索したいフォルダ名が入り、指定のフォルダに格納されたテキストファイルを検索します。

 修正ポイントは...

  • 関数化するためにSub〜End Subで全体を括った
  • フォルダ情報取得の為に、Level.8の11行目に在った「Set src = fso.GetFolder(srhFol)」の行を4行目に挿入し、オブジェクトの名前をsrcからsubfに変更、引数をsrhFolからfnに変更
  • 5行目のFor文に在ったsrc.Filesを先程定義したオブジェクトを利用するようにsubf.Filesに変更

 以上合計3点です。

フォルダ名、検索文字の入力処理

 次に、検索対象にしたいフォルダ名の入力と、検索文字の入力部分を作成ですが、この部分は修正らしい修正は無く、Level.8の該当部分は1〜17行目となっていますので、その部分をコピーして、先程作成したスクリプトの最後に追加するだけです。後は、ちょこちょこっと見易さのためにコメントを追加してみました。するとこんな感じになります。

level09.vbs(フォルダ名、検索文字の入力)
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
' --< main >---------------------------------------------------------------------
Set fso = CreateObject("Scripting.FileSystemObject")

' フォルダ名入力処理
srhFol = ""
Do Until fso.FolderExists(srhFol)  ' 存在するフォルダ名が入力されるまで繰り返す
    tmpMsg = "検索フォルダを入力して下さい" & vbNewLine & _
             "終了時は""c""と入力して下さい。"
    srhFol = InputBox(tmpMsg,,"C:\script\tips") ' 対象フォルダの指定
    If LCase(srhFol) = "c" Then     ' cが入力されたらスクリプトを終了する
        WScript.Quit
    End If
Loop
Set src = fso.GetFolder(srhFol)               ' 対象フォルダの指定

' 検索文字入力処理
Set regEx = New RegExp                        ' 文字列検索用オブジェクトの作成
srhStr = InputBox("検索文字を入力して下さい",,"ファイル") ' 検索文字列の指定

regEx.Pattern = srhStr                        ' 検索文字列を検索パターンとして指定
regEx.Global = True                           ' 文字列全体を検索するように指定
regEx.IgnoreCase = True                       ' 大文字・小文字は検索に影響しない

テキストファイル処理関数の呼び出し

 次はテキストファイル処理関数の呼び出し部分の記述ですが、上記で入力したフォルダ名を使ってテキストファイル処理関数を呼び出すだけですから、下記1行だけの処理となります(実際はコメントなどを入れているので3行です)。

level09.vbs(テキストファイル処理関数の呼び出し)
054
055
056
' テキスト処理関数呼び出し
FileSrh(srhFol)

サブフォルダ処理

 最後にサブフォルダの処理です。最初の方でも言いましたが、この部分の修正が今回の「キモ」になります。

 まず、Level.8の46〜51行目をコピーしてきます。それから、フォルダ名表示の為に使っていた47行目と51行目は削除します。最後に、繰り返し処理の中にある49行目をテキスト処理関数の呼び出しに修正すると完成です。

level09.vbs(サブフォルダ処理)
057
058
059
060
' 指定のフォルダに格納された各サブフォルダを処理する
For Each FolderName In src.Subfolders
    FileSrh(FolderName)
Next

 これで完成ですので通して見てみましょう。
level09.vbs(完成版)
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
' --< テキストファイル処理関数 >-------------------------------------------------
' 指定のフォルダに格納された各ファイルを処理する
Sub FileSrh(fn)
    Set subf = fso.GetFolder(fn)    ' 引数に指定されたフォルダの情報を取得
    For Each FileName In subf.Files
        FileEx = fso.GetExtensionName(FileName)   ' ファイル名から拡張子を抜き出す
        If LCase(FileEx) = "txt" Then             ' 拡張子を小文字化してから比較
    
          ' テキストファイルの内容読み込み処理
            Set TextFile = fso.OpenTextFile(FileName) ' テキストファイルのオープン
            srhLine = FileName & ":" & vbNewLine      ' 検索結果格納用変数の初期化
            srhFlg = 0                                ' 検索ヒットフラグのクリア
            Do Until TextFile.AtEndOfStream
                tmpLine = TextFile.ReadLine           ' 1行読み込み
    
                If regEx.Test(tmpLine) Then           ' 指定の文字列を含むか?
                    srhLine = srhLine & TextFile.Line & ":" & tmpLine & vbNewLine
                                                      ' 読み込んだ行のマージ
                    srhFlg =1                         ' 検索がヒットしたので1にする
                End If
            Loop
    
            If srhFlg = 1 Then                        ' 指定の文字列が含まれてたら
                WScript.Echo srhLine
            End If
    
        End If
    Next
End Sub

' --< main >---------------------------------------------------------------------
Set fso = CreateObject("Scripting.FileSystemObject")

' フォルダ名入力処理
srhFol = ""
Do Until fso.FolderExists(srhFol)  ' 存在するフォルダ名が入力されるまで繰り返す
    tmpMsg = "検索フォルダを入力して下さい" & vbNewLine & _
             "終了時は""c""と入力して下さい。"
    srhFol = InputBox(tmpMsg,,"C:\script\tips") ' 対象フォルダの指定
    If LCase(srhFol) = "c" Then     ' cが入力されたらスクリプトを終了する
        WScript.Quit
    End If
Loop
Set src = fso.GetFolder(srhFol)               ' 対象フォルダの指定

' 検索文字入力処理
Set regEx = New RegExp                        ' 文字列検索用オブジェクトの作成
srhStr = InputBox("検索文字を入力して下さい",,"ファイル") ' 検索文字列の指定

regEx.Pattern = srhStr                        ' 検索文字列を検索パターンとして指定
regEx.Global = True                           ' 文字列全体を検索するように指定
regEx.IgnoreCase = True                       ' 大文字・小文字は検索に影響しない

' テキスト処理関数呼び出し
FileSrh(srhFol)

' 指定のフォルダに格納された各サブフォルダを処理する
For Each FolderName In src.Subfolders
    FileSrh(FolderName)
Next

実行してみましょう

 Level.1でテスト環境について説明しましたが、Level.8までではC:\script\tipsフォルダ直下のテキストファイルしか検索対象になっていませんでした。今回作成した、Level.9ではサブフォルダも(1階層だけ)検索できるようにしましたので、tipsフォルダ内にある「Linuxフォルダ」「Scriptフォルダ」「Windowsフォルダ」内のテキストファイルも検索されれば成功です。

 さて、さっそく実行してみましょう。検索対象が増えますので、GUI起動だとOKボタンのクリックが多くなって面倒ですので、コマンド起動にして検索結果をテキストファイルに保存してみます。

level9.vbsファイルをコマンドラインから実行
C:\>cd c:\script    ... (*1)
C:\script>cscript level9.vbs > kekka.txt    ... (*2)
C:\script>notepad kekka.txt    ... (*3)

 まず、コマンドプロンプトを起動してカレントフォルダをC:\scriptに位置づけましょう(*1)。次に、スクリプトを起動します(*2)。このとき、"cscript level9.vbs"の後に" > kekka.txt"を付けることで、画面に表示される結果をkekka.txtファイルに格納することができます。

 スクリプトを起動すると、Level.8と同様、メッセージボックスが表示され検索フォルダと検索文字を聞いてきますので、デフォルト(「C:\script\tips」と「ファイル」)のまま次に進めます。

 正常終了したら、結果を確認してみましょう(*3)。上記の例では、コマンドラインからメモ帳(notepad.exe)を起動しています。

level9.vbsの実行結果をメモ帳で確認

 このように、Level.9では検索されていなかったサブフォルダの内容まで検索されていれば成功です。

 さてっ、今回はここまでです。次回はいよいよ再帰処理を使って複数の階層構造になっているフォルダ全てについて検索対象として見ます。

 それにしても、今回のLevel.9は前回のをアップしてから1ヶ月近くかかってしまいました(汗。何かと忙しかったせいもありますが、内容的にも結構苦労したのです。おかげで趣味のページ(北海道生活)の方もストップした状態となってまして、こっちの方も再会さいたいなぁ、などと思っています(でも、そうすると、こっちの方がまた遅れちゃうんですよねぇ...うーん)。



前ページへ   [ステップアップ WSHへ戻る]   次ページへ
-
※全ては自己責任でお願いします。
※当サイトに関するご連絡は tomomo_c@hotmail.com までお願いします。
最終更新日 2004.6.7