読み取れないディレクトリを除いてそれを取得する

タグの編集
投稿者 ねぼすけ  (社会人) 投稿日時 2016/11/29 16:03:56
Dim targetFolder As String = "D:\"
Dim folder As String() = System.IO.Directory.GetDirectories(targetFolder, "*", IO.SearchOption.AllDirectories)

で、D:\以下のサブディレクトリを取得しようとしますが、「System Volume Information」の配下のディレクトリに取得を拒否され、
「型 'System.UnauthorizedAccessException' のハンドルされていない例外が mscorlib.dll で発生しました」とエラーが出て、読み取ることができません。(すべてのファイルを取得するときもそうですが…)
取得できないディレクトリを除いて取得するにはどうしたらよいのでしょうか。お願いします。
投稿者 YuO  (社会人) 投稿日時 2016/11/29 16:48:55
再帰 (またはスタックorキューを使って対応) とTry-Catchを使ってください。
AllDirectoriesを使った方法では,アクセス拒否等に対して対応できません。
投稿者 (削除されました)  () 投稿日時 2016/11/29 19:14:05
(削除されました)
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2016/11/29 19:16:41
手前味噌ですが、実装例がこのあたりにあります。
http://rucio.cloudapp.net/ThreadDetail.aspx?ThreadId=9982

とはいえ古いコードですので、折角なので現行バージョンっぽい書き方にしてみました。
VB2012 以降が必要です。

Imports System.IO
Module Module1
    Public Sub Main()
        'これだと、権限エラー(UnauthorizedAccessException)等で止まってしまうことが… 
        ' 
        'For Each s In Directory.EnumerateDirectories("D:\", "*", SearchOption.AllDirectories) 
        '    Console.WriteLine(s) 
        'Next 

        'こちらなら、アクセス権の無いディレクトリを読み飛ばして列挙しつづけます 
        ' 
        For Each s In SafeEnumerateDirectories("D:\", "*")
            Console.WriteLine(s)
        Next
    End Sub


    Public Iterator Function SafeEnumerateDirectories(ByVal parentDir As String, Optional ByVal seachPattern As String = "*") As IEnumerable(Of String)
        Dim directories As IEnumerable(Of String) = Nothing
        Try
            directories = Directory.EnumerateDirectories(parentDir, seachPattern, SearchOption.TopDirectoryOnly)
        Catch
            'Debug.WriteLine(parentDir) 
        End Try
        If directories Is Nothing Then
            Return
        Else
            For Each subDir In directories
                Yield subDir
                For Each recursive In SafeEnumerateDirectories(subDir, seachPattern)
                    Yield recursive
                Next
            Next
        End If
    End Function
End Module



元のコードは List(Of ) なため、列挙までに時間がかかりましたが、
今回は Iterator にしていますので、見つかった物から即時返却されはじめます。


なお、シンボリックリンクの中も探索する仕様なので、
親子が繋がった無限階層なパスの中も、延々と探し続けますのでご注意を。
(といっても、無限階層であれば、パス長エラーで打ち切られるはずですが) 
投稿者 ねぼすけ  (社会人) 投稿日時 2016/12/1 19:02:00
返答遅れました。
YuOさんのアドバイス、再帰は薄々感じていたのですが、どうコード化すれば良いのか悩んでいました。
魔界の仮面弁士さん、コードどうもありがとうございます。自作のコードに組み込みました。思惑通りに動きました。ですが、「Stack」「Queue」「IEnumerable」や「 Iterator」の理解に時間がかかってしまいました。
ただ、やはり再帰処理が理解できていません。脳味噌硬いですね。
いずれ、理解します。数学の「 n ! 」(nの階乗)を自分の頭でコード化できるようになれば再帰の意味もはっきりするだろうと思っています。
自分自身の課題も残っていますが、「解決」にチェック入れます。
YuOさん、魔界の仮面弁士さんありがとうございました。
投稿者 ねぼすけ  (社会人) 投稿日時 2016/12/2 08:44:58
チェック入れるの忘れました。
投稿者 ねぼすけ  (社会人) 投稿日時 2016/12/2 14:42:42
解決済みとしたのですが、疑問が出てきました。
SafeEnumerateDirectories関数中のコード、

①Dim directories As IEnumerable(Of String) = Nothing
②Dim directories As String() = Nothing          

③directories = Directory.EnumerateDirectories(parentDir, seachPattern, SearchOption.TopDirectoryOnly)                        
④directories = Directory.GetDirectories(parentDir, seachPattern, SearchOption.TopDirectoryOnly)

②と④の組み合わせ
①と④の組み合わせ
①と③の組み合わせ(魔界の仮面弁士さんのコード)
は見た目、同じ結果になりますが、②と③の組み合わせではエラーこそ出ませんが、「Yield subDir」
「subDir」を返せないでいるのか一向に動きがありません。
「IEnumerable」「EnumerateDirectories」にそのからくりが在りそうですが、
「IEnumerable」は「For Each」を使えるようにする。「EnumerateDirectories」は随時処理までは押さえているのですが、どうもすっきりしなくて…。
教えていただけないでしょうか。
投稿者 YuO  (社会人) 投稿日時 2016/12/2 16:18:19
まず,Directory.EnumerateDirectoriessはIEnumerable(Of String)を,
Directory.GetDirectoriesはString()を,それぞれ戻り値として返します。

その上で,①と③,②と④は型が同じなので機能します。
また,①と④はString()がIEnuemrable(Of String)を実装しているかのように振る舞うため,機能します。

しかし,②と③は,IEnumerable(Of String)はString()ではないので,そもそも正しくありません。
キャスト失敗の例外がCatchに捕らえられている,というのが実際かと思います。
# Catch節をCatch e As UnauthorizedAccessExceptionにすると例外に気付くはず。


このサイトのスタンスには反するのですが,Option StrictはOnにしてプログラムを行う事をお勧めします。
# Option Strict Onならばそもそもエラーになったはず。
投稿者 ねぼすけ  (社会人) 投稿日時 2016/12/2 16:58:14
なるほどです。すっきりしました。
>「subDir」を返せないでいるのか一向に動きがありません。
ではなくて、エラーしていてCatch節で処理していたのですね。
YuOさん、どうもありがとうございます。
投稿者 (削除されました)  () 投稿日時 2016/12/2 18:41:44
(削除されました)
投稿者 (削除されました)  () 投稿日時 2016/12/2 20:43:36
(削除されました)