当 Directory.GetFiles() 被拒绝访问时忽略文件夹/文件

82

我正在尝试显示所选目录中(以及可选的任何子目录中)找到的所有文件的列表。 我遇到的问题是,当GetFiles()方法遇到无法访问的文件夹时,它会抛出异常并停止进程。

我该如何忽略此异常(并忽略受保护的文件夹/文件),并继续向列表中添加可访问的文件?

try
{
    if (cbSubFolders.Checked == false)
    {
        string[] files = Directory.GetFiles(folderBrowserDialog1.SelectedPath);
        foreach (string fileName in files)
            ProcessFile(fileName);
    }
    else
    {
        string[] files = Directory.GetFiles(folderBrowserDialog1.SelectedPath, "*.*", SearchOption.AllDirectories);
        foreach (string fileName in files)
            ProcessFile(fileName);
    }
    lblNumberOfFilesDisplay.Enabled = true;
}
catch (UnauthorizedAccessException) { }
finally {}
9个回答

50

您将需要手动执行递归; 不要使用AllDirectories- 一次查看一个文件夹,然后尝试从子目录获取文件。未经测试,但类似于以下内容(请注意使用委托而不是构建数组):

using System;
using System.IO;
static class Program
{
    static void Main()
    {
        string path = ""; // TODO
        ApplyAllFiles(path, ProcessFile);
    }
    static void ProcessFile(string path) {/* ... */}
    static void ApplyAllFiles(string folder, Action<string> fileAction)
    {
        foreach (string file in Directory.GetFiles(folder))
        {
            fileAction(file);
        }
        foreach (string subDir in Directory.GetDirectories(folder))
        {
            try
            {
                ApplyAllFiles(subDir, fileAction);
            }
            catch
            {
                // swallow, log, whatever
            }
        }
    }
}

太好了,在VB.NET中我还没有找到像这样的东西。希望您不介意我在这里将其翻译成了VB.NET(http://stackoverflow.com/a/34924036/1197518)。 - Steve
10
仍然不够:当一个文件夹中只有一个文件无法访问时,GetFiles会在内部抛出异常。因此整个文件夹将无法处理。 - v.oddou
它的速度要慢得多。 - Meghdad
1
我不确定这是被接受的答案。GetFilesGetDirectories都可能抛出异常,导致在异常之后无法看到任何内容。 - HackSlash
@HackSlash,注释“swallow, log, whatever”的含义是邀请您根据您的情况添加适当的处理方式。 - Marc Gravell
1
@MarcGravell,我不想在catch块中做任何事情。我希望在抛出异常的文件或目录之后继续执行下一个,这都是在你的try块之外。提示:将该代码运行在您的C:\根目录上,它永远不会跳出根文件夹。 - HackSlash

28

自从.NET Standard 2.1(.NET Core 3+,.NET 5+)以来,你现在只需执行以下操作:

var filePaths = Directory.EnumerateFiles(@"C:\my\files", "*.xml", new EnumerationOptions
{
    IgnoreInaccessible = true,
    RecurseSubdirectories = true
});

根据MSDN文档关于IgnoreInaccessible的说明:

获取或设置一个值,指示在访问被拒绝时(例如UnauthorizedAccessException或SecurityException),是否跳过文件或目录。默认值为true。

默认值实际上是true,但我保留了它以显示该属性。

DirectoryInfo也可用相同重载进行操作。


这在.NET Core 2.1中也可以工作,但不适用于.NET Standard 2.0。 - Panagiotis Kanavos
这是唯一一个真正有效的答案,但它需要您更改目标框架。 - HackSlash
这非常有帮助。 - DJ Burb
2
此函数重载也包含在 NET 5 和 NET 6 RC 2 中。 - ElektroStudios
这可以通过使用NuGet中的Microsoft.IO.Redist在Net标准2.0下级版本中获得。 - L4marr

15

这个简单的函数运行良好,满足问题的要求。

private List<string> GetFiles(string path, string pattern)
{
    var files = new List<string>();
    var directories = new string[] { };

    try
    {
        files.AddRange(Directory.GetFiles(path, pattern, SearchOption.TopDirectoryOnly));
        directories = Directory.GetDirectories(path);
    }
    catch (UnauthorizedAccessException) { }

    foreach (var directory in directories)
        try
        {
            files.AddRange(GetFiles(directory, pattern));
        }
        catch (UnauthorizedAccessException) { }

    return files;
}

1
是的,由于缺少错误处理,它并没有太大用处。只需尝试搜索整个c:\树。在Windows文件系统中有许多区域,即使用户具有管理员权限,也可能没有足够的访问权限。这就是主要挑战所在(除了连接点等)。 - Philm
这里有同样的问题。如果在GetFiles命令中抛出异常,那么之后就什么也看不到了。 - HackSlash
@HackSlash,请尝试使用刚刚进行的编辑,以允许在目录拒绝访问后继续搜索。 - Ben Gripka

8

一个简单的方法是使用List来处理文件,使用队列Queue来处理目录。 这样可以节约内存。 如果您使用递归程序来执行相同的任务,可能会抛出OutOfMemory异常。 输出结果:添加到List中的文件将根据从上到下(广度优先)的目录树进行组织。

public static List<string> GetAllFilesFromFolder(string root, bool searchSubfolders) {
    Queue<string> folders = new Queue<string>();
    List<string> files = new List<string>();
    folders.Enqueue(root);
    while (folders.Count != 0) {
        string currentFolder = folders.Dequeue();
        try {
            string[] filesInCurrent = System.IO.Directory.GetFiles(currentFolder, "*.*", System.IO.SearchOption.TopDirectoryOnly);
            files.AddRange(filesInCurrent);
        }
        catch {
            // Do Nothing
        }
        try {
            if (searchSubfolders) {
                string[] foldersInCurrent = System.IO.Directory.GetDirectories(currentFolder, "*.*", System.IO.SearchOption.TopDirectoryOnly);
                foreach (string _current in foldersInCurrent) {
                    folders.Enqueue(_current);
                }
            }
        }
        catch {
            // Do Nothing
        }
    }
    return files;
}

步骤:

  1. 将根目录加入队列
  2. 循环,出队,将该目录下的文件添加到列表中,并将子文件夹添加到队列中。
  3. 重复以上步骤直至队列为空。

2

这是一个完整的、兼容.NET 2.0的实现。

你甚至可以修改生成的List文件,以跳过FileSystemInfo版本中的目录!

(注意null值!)

public static IEnumerable<KeyValuePair<string, string[]>> GetFileSystemInfosRecursive(string dir, bool depth_first)
{
    foreach (var item in GetFileSystemObjectsRecursive(new DirectoryInfo(dir), depth_first))
    {
        string[] result;
        var children = item.Value;
        if (children != null)
        {
            result = new string[children.Count];
            for (int i = 0; i < result.Length; i++)
            { result[i] = children[i].Name; }
        }
        else { result = null; }
        string fullname;
        try { fullname = item.Key.FullName; }
        catch (IOException) { fullname = null; }
        catch (UnauthorizedAccessException) { fullname = null; }
        yield return new KeyValuePair<string, string[]>(fullname, result);
    }
}

public static IEnumerable<KeyValuePair<DirectoryInfo, List<FileSystemInfo>>> GetFileSystemInfosRecursive(DirectoryInfo dir, bool depth_first)
{
    var stack = depth_first ? new Stack<DirectoryInfo>() : null;
    var queue = depth_first ? null : new Queue<DirectoryInfo>();
    if (depth_first) { stack.Push(dir); }
    else { queue.Enqueue(dir); }
    for (var list = new List<FileSystemInfo>(); (depth_first ? stack.Count : queue.Count) > 0; list.Clear())
    {
        dir = depth_first ? stack.Pop() : queue.Dequeue();
        FileSystemInfo[] children;
        try { children = dir.GetFileSystemInfos(); }
        catch (UnauthorizedAccessException) { children = null; }
        catch (IOException) { children = null; }
        if (children != null) { list.AddRange(children); }
        yield return new KeyValuePair<DirectoryInfo, List<FileSystemInfo>>(dir, children != null ? list : null);
        if (depth_first) { list.Reverse(); }
        foreach (var child in list)
        {
            var asdir = child as DirectoryInfo;
            if (asdir != null)
            {
                if (depth_first) { stack.Push(asdir); }
                else { queue.Enqueue(asdir); }
            }
        }
    }
}

2

请参考https://dev59.com/UlXTa4cB1Zd3GeqPzCxO#10728792,以解决UnauthorisedAccessException问题。

以上所有解决方案都会在具有混合权限的文件夹上调用GetFiles()或GetDirectories()时漏掉文件和/或目录。


1
所有涉及 GetFiles/GetDirectories 的解决方案都会遇到同样的问题,因此有些不太优雅。 - hello_earth

1

这应该能回答你的问题。我忽略了处理子目录的问题,假设你已经解决了这个问题。

当然,你不需要为此编写一个单独的方法,但你可能会发现这是一个有用的地方去验证路径是否有效,并处理调用GetFiles()时可能遇到的其他异常。

希望对你有所帮助。

private string[] GetFiles(string path)
{
    string[] files = null;
    try
    {
       files = Directory.GetFiles(path);
    }
    catch (UnauthorizedAccessException)
    {
       // might be nice to log this, or something ...
    }

    return files;
}

private void Processor(string path, bool recursive)
{
    // leaving the recursive directory navigation out.
    string[] files = this.GetFiles(path);
    if (null != files)
    {
        foreach (string file in files)
        {
           this.Process(file);
        }
    }
    else
    {
       // again, might want to do something when you can't access the path?
    }
}

这里也有同样的问题。任何包含单个异常的目录都会导致您丢失整个目录列表。 - HackSlash

1

我更喜欢使用c#框架函数,但我需要的函数将包含在.net框架5.0中,所以我必须自己编写。

// search file in every subdirectory ignoring access errors
    static List<string> list_files(string path)
    {
        List<string> files = new List<string>();

        // add the files in the current directory
        try
        {
            string[] entries = Directory.GetFiles(path);

            foreach (string entry in entries)
                files.Add(System.IO.Path.Combine(path,entry));
        }
        catch 
        { 
        // an exception in directory.getfiles is not recoverable: the directory is not accessible
        }

        // follow the subdirectories
        try
        {
            string[] entries = Directory.GetDirectories(path);

            foreach (string entry in entries)
            {
                string current_path = System.IO.Path.Combine(path, entry);
                List<string> files_in_subdir = list_files(current_path);

                foreach (string current_file in files_in_subdir)
                    files.Add(current_file);
            }
        }
        catch
        {
            // an exception in directory.getdirectories is not recoverable: the directory is not accessible
        }

        return files;
    }

1

对于目标框架低于.NET 2.1的用户,只需在NuGet上获取Microsoft.IO.Redist。

var filePaths = Directory.EnumerateFiles(@"C:\my\files", "*.xml", new EnumerationOptions
{
    IgnoreInaccessible = true,
    RecurseSubdirectories = true
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接