【问题标题】:Directory.EnumerateFiles => UnauthorizedAccessExceptionDirectory.EnumerateFiles => UnauthorizedAccessException
【发布时间】:2011-07-03 03:37:44
【问题描述】:

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

这里的问题是,如果希望枚举所有文件,可能事先不知道哪些文件或文件夹受到访问保护,并可能引发 UnauthorizedAccessException。

要重现,只需运行这个片段:

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

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

那该怎么办?使用此方法时,UnauthorizedAccessException 是否可以被抑制或成为现实?

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

【问题讨论】:

  • 是的,您的 Dump() 方法应该能够应对它试图转储的文件的问题。给它一个超载。
  • 这不是我的问题,汉斯。问题是对文件迭代器 (EnumerateFiles) 的 foreach 会引发 UnauthorizedAccessException,进而停止进一步的枚举,这在需要详尽的结果集时是不可取的。
  • @Hans - Dump() 方法不是这里的问题,它只是遍历字符串枚举。问题在于Directory.EnumerateFiles 方法本身。而且我认为没有办法解决这个问题。我相信你必须求助于SearchOption.TopDirectoryOnly 并自己处理递归。
  • 这(以及其他原因)是我最终为NtQueryDirectoryFile 自己编写包装器的原因。
  • 我已经在stackoverflow.com/questions/13130052/… 上发布了一个解决方案。我发布的解决方案表现得像一个真正的 IEnumerable,因为它只有在需要工作时才有效。

标签: .net filesystems lazy-evaluation unauthorizedaccessexcepti


【解决方案1】:

我无法让上述工作,但这是我的实现,我已经在“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>();
        }
    }
}

【讨论】:

  • 我也遇到了这个问题。我想出的解决方案可以在stackoverflow.com/questions/13130052/… 找到。它表现得像一个真正的可枚举,因为它只有在你向它请求下一个项目时才起作用。
  • 如果遇到受限文件并且抛出/忽略异常会发生什么。它不会停在那里并忽略这一点后面的所有文件吗?
【解决方案2】:

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

    /// <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;
    }

【讨论】:

  • 部分且不正确的解决方案。您可以访问一个目录并在特定文件上获得 access denied
  • 请不要引用“以上答案”的其他答案...
  • 您也可以使用此代码获得Could not find a part of the path
【解决方案3】:

我知道是 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&lt;TSource&gt; 以供参考):

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

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

【讨论】:

  • 不错,但它不适用于我的场景:我需要它在遇到异常时继续并继续前进:安全步行和正常步行之间的唯一区别是safe-walk 只是停止枚举,而普通方法因异常而停止。我需要它继续并忽略任何异常,因为它应该枚举它可以枚举的所有目录,而只是跳过它无权访问的目录。不幸的是,这似乎需要 BCL 实现的新实现。
  • ...如果它可以在生产环境中使用它,我不会有任何问题 ;-) ...但即便如此,它也需要一些修改:例如你不想捕获 所有异常,它应该只是 UnauthorizedAccessException 或者至少应该可以通过 lambda 过滤。
  • 不幸的是,MoveNext() 在抛出异常时并没有提升它的位置。
  • 像 DirectoryEnumerationPolicy.SkipUnauthorizedPaths 这样的东西作为 Directory.Enumerate{Directories,Files,FileSystemEntries} 的附加参数会很好。
  • 迄今为止最快的选择。大多数解决方案在返回它们之前将一个级别的所有条目加载到内存中 - 这在递归时会迅速建立。这个解决方案神奇地希望 MoveNext 即使抛出异常也能正常工作。有风险 - 使用 netstandard 2.0 进行测试,它可以在 $Recyclebin 和系统目录被跳过时递归搜索 C:\。
【解决方案4】:

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

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

【讨论】:

    【解决方案5】:

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

    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));
      }
    }
    

    【讨论】:

      【解决方案6】:

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

      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(...)

      【讨论】:

        猜你喜欢
        • 2021-08-11
        • 1970-01-01
        • 2012-12-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-10-08
        • 1970-01-01
        • 2010-09-16
        相关资源
        最近更新 更多