Perlでファイルリストを出来るだけ高速に取得する方法を教えてください。


以下のようなスクリプトで、再帰的にファイルリストを取得しようとしています。
(WindowsXP上のActivePerl使用)
#----------------------------
#!/usr/bin/perl
$top_dir = "c:\\test";
use File::Find;
find( \&print_file_full_name, $top_dir );
sub print_file_full_name{
print $File::Find::name, "\n";
}
#----------------------------

これだと、「表」「予」「申」「能」「十」「ソ」などの0x5C文字がフォルダ名の最後に付いていると
そのフォルダより下を走査できません。
[例]¥日程表¥ ¥教育技能¥

File::Findを使用して全てのファイルを取得出来るようにする方法はありませんでしょうか。
(同程度のスピードが出れば File::Find を使用しなくても構いません。)




以下の質問の関連です。
http://q.hatena.ne.jp/1217570180

回答の条件
  • 1人5回まで
  • 登録:
  • 終了:2008/09/03 00:16:50
※ 有料アンケート・ポイント付き質問機能は2023年2月28日に終了しました。

回答2件)

id:Mook No.1

回答回数1314ベストアンサー獲得回数393

ポイント35pt

perl でできなくはないと思いますが、フォルダ名やファイル名に日本語が含まれている場合

どうしても文字コードの問題が付きまといます。


餅は餅屋ではないですが Windows であれば VBS 等、ファイルシステムとの親和性の高いものを

利用してはどうでしょうか。


perl から利用する場合は、コメントした通りです。

#!C:/Perl/bin/perl -w

print system("CScript //nologo \"C:\\test\\listFile.vbs\" \"C:\\Test\\\"");

下記は、

>「第○階層まで」としたり、          depthLimit で指定

>「○○と付くフォルダは除外する」という指定  ignoreFolder で指定

を vbs で実装した例です。


【listFile.vbs】

Option Explicit

'----------------------------------------
Const depthLimit = 3
'--- 処理をしないフォルダに含まれる文字を「,」区切りで列挙
Const ignoreFolder = "test,temp,log"

'----------------------------------------
Dim fso, rootFolder, iFolders
Set fso = CreateObject("Scripting.FileSystemObject")

'--- 引数の確認
If WScript.Arguments.Count > 0 Then
    rootFolder = WScript.Arguments.item(0)
Else
'--- 指定がなければ C のルートを設定
    rootFolder = "C:\"
End If

'--- フィルタ文字列を配列に変換
iFolders = Split( ignoreFolder, "," )

'--- 表示処理
printFolder 0, fso.getFolder( rootFolder )

'----------------------------------------
Sub printFolder( depth, objFolder )
'----------------------------------------
    WScript.Echo objFolder.Path & "\"
    If depth >= depthLimit Then
        Exit Sub
    End If

    Dim sFolder, sFile
'--- 指定の文字を含むフォルダは処理をしない
    For Each sFolder In iFolders
    If InStr( objFolder.Name, sFolder ) > 0 Then
        Exit Sub
    End If
    Next

'--- フォルダ内のファイルの列挙
    For Each sFile In objFolder.Files
        WScript.Echo sFile.Path
    Next

'--- サブフォルダの処理
    For Each sFolder In objFolder.SubFolders
        printFolder depth + 1, sFolder
    Next
End Sub

インデントが見やすいのでコメントでなく回答させていただきましたが、

あくまで perl で処理を行うことが目的で、ということでしたらポイント不要です。

(他に回答なければキャンセルください。)

id:harrypotter

回答ありがとうございます。

目的が達せられるなら言語は問いません。

あと、結果にファイルだけ、フォルダだけ、両方、が選べたら完璧です :-)


週明けに試して再度返信させていただきます。

2008/08/29 21:27:11
id:Mook No.2

回答回数1314ベストアンサー獲得回数393

ポイント35pt

ファイル、フォルダのオプションを第二引数として追加した例です。

コマンド上で

CScript //nologo listFolder listFile.vbs ファイルパス  [-folder | -file]

のように指定します。


Option Explicit

'----------------------------------------
Const depthLimit = 3
'--- 処理をしないフォルダに含まれる文字を「,」区切りで列挙
Const ignoreFolder = "test,temp,log"

'----------------------------------------
Dim fso, rootFolder, iFolders
Set fso = CreateObject("Scripting.FileSystemObject")

'--- 引数の確認
If WScript.Arguments.Count > 0 Then
    rootFolder = WScript.Arguments.item(0)
Else
'--- 指定がなければ C のルートを設定
    rootFolder = "C:\"
End If

Dim mode
mode = "BOTH"
If WScript.Arguments.Count > 1 Then
    If UCase( WScript.Arguments.item(1) ) = "-FOLDER" Then
        mode = "FOLDER"
    End If
    If UCase( WScript.Arguments.item(1) ) = "-FILE" Then
        mode = "FILE"
    End If
End IF

'--- フィルタ文字列を配列に変換
iFolders = Split( ignoreFolder, "," )

'--- 表示処理
printFolder 0, fso.getFolder( rootFolder )

'----------------------------------------
Sub printFolder( depth, objFolder )
'----------------------------------------
    If mode <> "FILE" Then
        WScript.Echo objFolder.Path & "\"
    End If

    If depth >= depthLimit Then
        Exit Sub
    End If

    Dim sFolder, sFile
'--- 指定の文字を含むフォルダは処理をしない
    For Each sFolder In iFolders
    If InStr( objFolder.Name, sFolder ) > 0 Then
        Exit Sub
    End If
    Next

'--- フォルダ内のファイルの列挙
    If mode <> "FOLDER" Then
        For Each sFile In objFolder.Files
            WScript.Echo sFile.Path
        Next
    End If
'--- サブフォルダの処理
    For Each sFolder In objFolder.SubFolders
        printFolder depth + 1, sFolder
    Next
End Sub
id:harrypotter

わざわざ機能増強していただきましてすみません。

週明けに試させていただきます。

-----------------------------------------------

[9.1追記]

少しテストしただけですが、とても高速ですね。ありがとうございます。

ただ、途中で読み取れないフォルダ(例えばアクセス権限がない)があるとその時点でエラーで止まってしまいます。

現在自作のPerlのスクリプトの方では読み取りエラーで処理出来なかった場合、処理出来なかったフォルダ・ファイルの一覧を別途吐き出すようにしています。

同様にエラーを取得して吐き出すにはどのようにすればいいでしょうか。

すいませんがよろしくお願いします。

------------------------------------------------

[さらに追記]

On Error Resume Next で飛ばしてエラーナンバーを拾って実装出来ました。

ありがとうございました。

------------------------------------------------

[終了のお礼]

スピードはFile::Findに到底かないませんでしたが、他に方法がなさそうなので今回はVBSを使用します。

ありがとうございました。

2008/09/03 00:16:40
  • id:Mook
    Windows であれば、下記のバッチで同等なことが Perl でやりたいのですか?
      dir /S /B "C:\test"
    Perl でも
      print system("dir /S /B \"C:\\test\"");
    でできそうですが。
  • id:harrypotter
    階層指定して「第○階層まで」としたり、「○○と付くフォルダは除外する」という指定を組み込んだり、他のプログラムに処理を渡したりするのにPerlを使用しています。
    上記スクリプトはその一部です。

    dirコマンドでは解決になりません。
  • id:Mook
    そうですね。
    アクセス権がない場合は、エラーハンドリングで処理するのが良いかと思います。

    ご自身で、対応できたようでなによりでした。

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

回答リクエストを送信したユーザーはいません