目录.EnumerateFiles =>未经授权的访问异常

发布于 2024-10-18 22:47:32 字数 476 浏览 3 评论 0原文

.NET 4.0 中有一个很好的新方法,可以通过枚举以流方式获取目录中的文件。

这里的问题是,如果希望枚举所有文件,则可能无法提前知道哪些文件或文件夹受到访问保护,并且可能会抛出 UnauthorizedAccessException。

要重现,只需运行此片段:

foreach (var file in Directory.EnumerateFiles(@"c:\", "*", SearchOption.AllDirectories))
{
   // whatever
}

在此 .NET 方法存在之前,可以通过在字符串数组返回方法上实现递归迭代器来实现大致相同的效果。但它并不像新的 .NET 方法那么懒惰。

那么该怎么办呢?使用此方法时,UnauthorizedAccessException 是否可以被抑制或者是不可避免的事实?

在我看来,该方法应该有一个重载,接受一个操作来处​​理任何异常。

There is a nice new method in .NET 4.0 for getting files in a directory in a streaming way via enumeration.

The problem here is that if one wishes to enumerate all files one may not know in advance which files or folders are access protected and can throw an UnauthorizedAccessException.

To reproduce, one can just run this fragment:

foreach (var file in Directory.EnumerateFiles(@"c:\", "*", SearchOption.AllDirectories))
{
   // whatever
}

Before this .NET method existed it was possible to achieve roughly the same effect by implementing a recursive iterator on the string-array returning methods. But it's not quite as lazy as the new .NET method is.

So what to do? Can the UnauthorizedAccessException be suppressed or is a fact of life when using this method?

Seems to me that the method should have an overload accepting an action to deal with any exceptions.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

左岸枫 2024-10-25 22:47:32

我无法使上述工作正常,但这是我的实现,我已经在“Win7”盒子上的 c:\users 上测试了它,因为如果有所有这些“讨厌的”目录:

SafeWalk.EnumerateFiles(@"C:\users", "*.jpg", SearchOption.AllDirectories).Take(10)

类:

public static class SafeWalk
{
    public static IEnumerable<string> EnumerateFiles(string path, string searchPattern, SearchOption searchOpt)
    {   
        try
        {
            var dirFiles = Enumerable.Empty<string>();
            if(searchOpt == SearchOption.AllDirectories)
            {
                dirFiles = Directory.EnumerateDirectories(path)
                                    .SelectMany(x => EnumerateFiles(x, searchPattern, searchOpt));
            }
            return dirFiles.Concat(Directory.EnumerateFiles(path, searchPattern));
        }
        catch(UnauthorizedAccessException ex)
        {
            return Enumerable.Empty<string>();
        }
    }
}

I Couldn't get the above to work, but here is my implementation, i've tested it on c:\users on a "Win7" box, because if has all these "nasty" dirs:

SafeWalk.EnumerateFiles(@"C:\users", "*.jpg", SearchOption.AllDirectories).Take(10)

Class:

public static class SafeWalk
{
    public static IEnumerable<string> EnumerateFiles(string path, string searchPattern, SearchOption searchOpt)
    {   
        try
        {
            var dirFiles = Enumerable.Empty<string>();
            if(searchOpt == SearchOption.AllDirectories)
            {
                dirFiles = Directory.EnumerateDirectories(path)
                                    .SelectMany(x => EnumerateFiles(x, searchPattern, searchOpt));
            }
            return dirFiles.Concat(Directory.EnumerateFiles(path, searchPattern));
        }
        catch(UnauthorizedAccessException ex)
        {
            return Enumerable.Empty<string>();
        }
    }
}
帅的被狗咬 2024-10-25 22:47:32

上述答案的问题是它不处理子目录中的异常。这将是处理这些异常的更好方法,因此您可以从所有子目录中获取所有文件,除了那些抛出访问异常的文件:

    /// <summary>
    /// A safe way to get all the files in a directory and sub directory without crashing on UnauthorizedException or PathTooLongException
    /// </summary>
    /// <param name="rootPath">Starting directory</param>
    /// <param name="patternMatch">Filename pattern match</param>
    /// <param name="searchOption">Search subdirectories or only top level directory for files</param>
    /// <returns>List of files</returns>
    public static IEnumerable<string> GetDirectoryFiles(string rootPath, string patternMatch, SearchOption searchOption)
    {
        var foundFiles = Enumerable.Empty<string>();

        if (searchOption == SearchOption.AllDirectories)
        {
            try
            {
                IEnumerable<string> subDirs = Directory.EnumerateDirectories(rootPath);
                foreach (string dir in subDirs)
                {
                    foundFiles = foundFiles.Concat(GetDirectoryFiles(dir, patternMatch, searchOption)); // Add files in subdirectories recursively to the list
                }
            }
            catch (UnauthorizedAccessException) { }
            catch (PathTooLongException) {}
        }

        try
        {
            foundFiles = foundFiles.Concat(Directory.EnumerateFiles(rootPath, patternMatch)); // Add files from the current directory
        }
        catch (UnauthorizedAccessException) { }

        return foundFiles;
    }

Ths issue with the above answer is that is does not take care of exception in sub directories. This would be a better way to handling those exceptions so you get ALL files from ALL subdirectories except those with threw an access exception:

    /// <summary>
    /// A safe way to get all the files in a directory and sub directory without crashing on UnauthorizedException or PathTooLongException
    /// </summary>
    /// <param name="rootPath">Starting directory</param>
    /// <param name="patternMatch">Filename pattern match</param>
    /// <param name="searchOption">Search subdirectories or only top level directory for files</param>
    /// <returns>List of files</returns>
    public static IEnumerable<string> GetDirectoryFiles(string rootPath, string patternMatch, SearchOption searchOption)
    {
        var foundFiles = Enumerable.Empty<string>();

        if (searchOption == SearchOption.AllDirectories)
        {
            try
            {
                IEnumerable<string> subDirs = Directory.EnumerateDirectories(rootPath);
                foreach (string dir in subDirs)
                {
                    foundFiles = foundFiles.Concat(GetDirectoryFiles(dir, patternMatch, searchOption)); // Add files in subdirectories recursively to the list
                }
            }
            catch (UnauthorizedAccessException) { }
            catch (PathTooLongException) {}
        }

        try
        {
            foundFiles = foundFiles.Concat(Directory.EnumerateFiles(rootPath, patternMatch)); // Add files from the current directory
        }
        catch (UnauthorizedAccessException) { }

        return foundFiles;
    }
魄砕の薆 2024-10-25 22:47:32

我知道是 MoveNext 引发了异常。

我尝试编写一个方法来安全地遍历序列并尝试忽略 MoveNext 异常。但是我不确定 MoveNext 在抛出异常时是否会前进位置,所以这也可能是无限循环。这也是一个坏主意,因为我们将依赖于实现细节。

但这只是太有趣了!

public static IEnumerable<T> SafeWalk<T> (this IEnumerable<T> source)
{
    var enumerator = source.GetEnumerator();
    bool? hasCurrent = null;

    do {
        try {
            hasCurrent = enumerator.MoveNext();
        } catch {
            hasCurrent = null; // we're not sure
        }

        if (hasCurrent ?? false) // if not sure, do not return value
            yield return enumerator.Current;

    } while (hasCurrent ?? true); // if not sure, continue walking
}

foreach (var file in Directory.EnumerateFiles("c:\\", "*", SearchOption.AllDirectories)
                              .SafeWalk())
{
    // ...
}

只有当框架实现此迭代器时满足以下条件时,这才会起作用(请参阅 Reflector 中的 FileSystemEnumerableIterator 以供参考):

  • < code>MoveNext 失败时前进其位置;
  • MoveNext 在最后一个元素上失败时,后续调用将返回 false 而不是抛出异常;
  • 对于不同版本的 .NET Framework,此行为是一致的;
  • 我没有犯任何逻辑或语法错误。

即使它有效,也请不要在生产中使用它!
但我真的想知道是否真的如此。

I understand it's MoveNext that throws the exception.

I tried to write a method that safe-walks a sequence and tries to ignore MoveNext exceptions. However I'm not sure if MoveNext advances position when it throws an exception, so this might as well be infinite loop. It is also bad idea because we would rely on implementation details.

But it's just so much fun!

public static IEnumerable<T> SafeWalk<T> (this IEnumerable<T> source)
{
    var enumerator = source.GetEnumerator();
    bool? hasCurrent = null;

    do {
        try {
            hasCurrent = enumerator.MoveNext();
        } catch {
            hasCurrent = null; // we're not sure
        }

        if (hasCurrent ?? false) // if not sure, do not return value
            yield return enumerator.Current;

    } while (hasCurrent ?? true); // if not sure, continue walking
}

foreach (var file in Directory.EnumerateFiles("c:\\", "*", SearchOption.AllDirectories)
                              .SafeWalk())
{
    // ...
}

This will only work if the following conditions are true about framework's implementation of this iterator (see FileSystemEnumerableIterator<TSource> in Reflector for reference):

  • MoveNext advances its position when it fails;
  • When MoveNext fails on last element, subsequent calls will return false instead of throwing an exception;
  • This behavior is consistent for different versions of .NET Framework;
  • I haven't made any logic or syntax mistakes.

Even if it works, please, never use it in production!
But I really wonder if it does.

一生独一 2024-10-25 22:47:32

作为答案发布,因为我没有代表添加评论,更不用说编辑现有答案了。我的要求是尽量减少内存分配、冗余变量,并让系统对目录进行一次枚举。

static IEnumerable<string> FindFiles(string path, string filter = "*", bool recursive = false)
{
    IEnumerator<string> fEnum;
    try
    {
        fEnum = Directory.EnumerateFiles(path, filter, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly).GetEnumerator();
    }
    catch (UnauthorizedAccessException) { yield break; }
    while (true)
    {
        try { if (!fEnum.MoveNext()) break; }
        catch (UnauthorizedAccessException) { continue; }
        yield return fEnum.Current;
    }
}

丹·贝查德 (Dan Bechard) 在评论中提到:

不幸的是,MoveNext() 在抛出异常时不会前进其位置。

这可能已在较新版本的 .Net 或 Windows 10 版本中修复?我在 Windows 10 上的 .NET 5.0 中没有遇到此问题。通过搜索整个系统驱动器进行测试。


在 VB.NET 中:

Public Iterator Function FindFiles(path As String, Optional filter As String = "*", Optional recursive As Boolean = False) As IEnumerable(Of String)

    Dim fEnum As IEnumerator(Of String)
    Dim searchDepth = If(recursive, SearchOption.AllDirectories, SearchOption.TopDirectoryOnly)

    Try
        fEnum = Directory.EnumerateFiles(path, filter, searchDepth).GetEnumerator()
    Catch uae As UnauthorizedAccessException
        Return
    End Try

    Do While True
        Try
            If Not fEnum.MoveNext() Then Exit Do
            Yield fEnum.Current
        Catch uae As UnauthorizedAccessException

        End Try

    Loop

End Function

Posting as an answer since I don't have the rep to add a comment let alone edit existing answers. My requirement was to minimize memory allocations, redundant variables, and have the system do a single enumeration of the directory.

static IEnumerable<string> FindFiles(string path, string filter = "*", bool recursive = false)
{
    IEnumerator<string> fEnum;
    try
    {
        fEnum = Directory.EnumerateFiles(path, filter, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly).GetEnumerator();
    }
    catch (UnauthorizedAccessException) { yield break; }
    while (true)
    {
        try { if (!fEnum.MoveNext()) break; }
        catch (UnauthorizedAccessException) { continue; }
        yield return fEnum.Current;
    }
}

Dan Bechard mentions in a comment:

Unfortunately, MoveNext() doesn't advance its position when it throws an exception.

This may have been fixed in either a newer release of .Net or in a Windows 10 build? I'm not having this issue in .NET 5.0 on Windows 10. Tested by searching my entire system drive.


In VB.NET:

Public Iterator Function FindFiles(path As String, Optional filter As String = "*", Optional recursive As Boolean = False) As IEnumerable(Of String)

    Dim fEnum As IEnumerator(Of String)
    Dim searchDepth = If(recursive, SearchOption.AllDirectories, SearchOption.TopDirectoryOnly)

    Try
        fEnum = Directory.EnumerateFiles(path, filter, searchDepth).GetEnumerator()
    Catch uae As UnauthorizedAccessException
        Return
    End Try

    Do While True
        Try
            If Not fEnum.MoveNext() Then Exit Do
            Yield fEnum.Current
        Catch uae As UnauthorizedAccessException

        End Try

    Loop

End Function
檐上三寸雪 2024-10-25 22:47:32

我迟到了,但我建议改用可观察模式:

public class FileUtil
{
  private static void FindFiles_(string path, string pattern,
    SearchOption option, IObserver<string> obs, CancellationToken token)
  {
    try
    {
      foreach (var file in Directory.EnumerateFiles(path, pattern,
        SearchOption.TopDirectoryOnly))
      {
        if (token.IsCancellationRequested) break;
        obs.OnNext(file);
      }

      if (option != SearchOption.AllDirectories) return;

      foreach (var dir in Directory.EnumerateDirectories(path, "*", 
        SearchOption.TopDirectoryOnly))
      {
        FindFiles_(dir, pattern, option, obs, token);
      }
    }
    catch (UnauthorizedAccessException) { }
    catch (PathTooLongException) { }
    catch (IOException) { }
    catch (Exception err) { obs.OnError(err); }
  }

  public static IObservable<string> GetFiles(string root, string pattern,
    SearchOption option)
  {
    return Observable.Create<string>(
      (obs, token) =>
        Task.Factory.StartNew(
          () =>
          {
            FindFiles_(root, pattern, option, obs, token);
            obs.OnCompleted();
          },
          token));
  }
}

I'm late, but I suggest using observable pattern instead:

public class FileUtil
{
  private static void FindFiles_(string path, string pattern,
    SearchOption option, IObserver<string> obs, CancellationToken token)
  {
    try
    {
      foreach (var file in Directory.EnumerateFiles(path, pattern,
        SearchOption.TopDirectoryOnly))
      {
        if (token.IsCancellationRequested) break;
        obs.OnNext(file);
      }

      if (option != SearchOption.AllDirectories) return;

      foreach (var dir in Directory.EnumerateDirectories(path, "*", 
        SearchOption.TopDirectoryOnly))
      {
        FindFiles_(dir, pattern, option, obs, token);
      }
    }
    catch (UnauthorizedAccessException) { }
    catch (PathTooLongException) { }
    catch (IOException) { }
    catch (Exception err) { obs.OnError(err); }
  }

  public static IObservable<string> GetFiles(string root, string pattern,
    SearchOption option)
  {
    return Observable.Create<string>(
      (obs, token) =>
        Task.Factory.StartNew(
          () =>
          {
            FindFiles_(root, pattern, option, obs, token);
            obs.OnCompleted();
          },
          token));
  }
}
陌若浮生 2024-10-25 22:47:32

我自己做了一个围绕这个问题的类的实现,因为以前的答案似乎没有做我想做的事。这只是跳过它无法访问的所有文件和文件夹,并返回它可以访问的所有文件。

public static class SafeWalk
{
    public static IEnumerable<string> EnumerateFiles(string path, string searchPattern, SearchOption searchOpt)
    {
        if (searchOpt == SearchOption.TopDirectoryOnly)
        {
            return Directory.EnumerateFiles(path, searchPattern, SearchOption.TopDirectoryOnly);
        }

        List<string> folders = new List<string>() { path };
        int folCount = 1;
        List<string> files = new List<string>() { };

        for (int i = 0; i < folCount; i++)
        {
            try
            {
                foreach (var newDir in Directory.EnumerateDirectories(folders[i], "*", SearchOption.TopDirectoryOnly))
                {
                    folders.Add(newDir);
                    folCount++;
                    try
                    {

                        foreach (var file in Directory.EnumerateFiles(newDir, searchPattern))
                        {
                            files.Add(file);
                        }
                    } catch (UnauthorizedAccessException)
                    {
                        // Failed to read a File, skipping it.
                    }
                }
            }
            catch (UnauthorizedAccessException)
            {
                // Failed to read a Folder, skipping it.
                continue;
            }
        }
        return files;
    }
}

与常规 EnumerateFiles 函数完全相同,只需使用 SafeWalk.EnumerateFiles(...) 而不是 Dictionary.EnumerateFiles(...)

I made my own Implementation of a Class that works arround this, as previous answers didnt seem to do what i wanted to. This simply skips all Files and Folders that it can not access, and returns all files that it could access.

public static class SafeWalk
{
    public static IEnumerable<string> EnumerateFiles(string path, string searchPattern, SearchOption searchOpt)
    {
        if (searchOpt == SearchOption.TopDirectoryOnly)
        {
            return Directory.EnumerateFiles(path, searchPattern, SearchOption.TopDirectoryOnly);
        }

        List<string> folders = new List<string>() { path };
        int folCount = 1;
        List<string> files = new List<string>() { };

        for (int i = 0; i < folCount; i++)
        {
            try
            {
                foreach (var newDir in Directory.EnumerateDirectories(folders[i], "*", SearchOption.TopDirectoryOnly))
                {
                    folders.Add(newDir);
                    folCount++;
                    try
                    {

                        foreach (var file in Directory.EnumerateFiles(newDir, searchPattern))
                        {
                            files.Add(file);
                        }
                    } catch (UnauthorizedAccessException)
                    {
                        // Failed to read a File, skipping it.
                    }
                }
            }
            catch (UnauthorizedAccessException)
            {
                // Failed to read a Folder, skipping it.
                continue;
            }
        }
        return files;
    }
}

Usable exactly like the regular EnumerateFiles function, simply using SafeWalk.EnumerateFiles(...) instead of Dictionary.EnumerateFiles(...)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文