다음을 통해 공유


방법: 디렉터리 트리 반복(C# 프로그래밍 가이드)

"디렉터리 트리 반복"이란 깊이에 관계없이 지정된 루트 폴더 아래에 있는 각 중첩 하위 디렉터리에서 각 파일에 액세스한다는 의미입니다.이때 각 파일을 반드시 열어야 하는 것은 아닙니다.파일이나 하위 디렉터리의 이름만 string으로 검색하거나 System.IO.FileInfo 또는 System.IO.DirectoryInfo 개체의 형태로 추가 정보를 검색할 수 있습니다.

[!참고]

Windows에서 "디렉터리"와 "폴더"라는 용어는 같은 의미로 사용됩니다.대부분의 설명서와 사용자 인터페이스 텍스트에서는 "폴더"라는 용어가 사용되지만 .NET Framework 클래스 라이브러리에서는 "디렉터리"라는 용어가 사용됩니다.

지정된 루트 아래에 있는 모든 디렉터리에 대한 액세스 권한이 있는 경우가 가장 간단하며, 이 경우에는 System.IO.SearchOption.AllDirectories 플래그를 사용할 수 있습니다.이 플래그는 지정된 패턴과 일치하는 모든 중첩 하위 디렉터리를 반환합니다.다음 예제에서는 이 플래그를 사용하는 방법을 보여 줍니다.

root.GetDirectories("*.*", System.IO.SearchOption.AllDirectories);

이 방식의 단점은 지정된 루트 아래에 있는 하위 디렉터리 중 하나가 DirectoryNotFoundException 또는 UnauthorizedAccessException을 발생시키면 전체 메서드가 실패하고 디렉터리가 반환되지 않는다는 것입니다.이것은 GetFiles 메서드를 사용할 경우에도 마찬가지입니다.특정 하위 폴더에서 이러한 예외를 처리해야 한다면 다음 예제에서 볼 수 있는 것처럼 디렉터리 트리를 수동으로 탐색해야 합니다.

디렉터리 트리를 수동으로 탐색할 때 하위 디렉터리를 먼저(전위 탐색) 처리하거나 파일을 먼저(후위 탐색) 처리할 수 있습니다.전위 탐색을 수행할 경우 폴더 자체에 들어 있는 파일을 반복하기 전에 먼저 현재 폴더 아래에 있는 전체 트리를 탐색합니다.이 문서의 뒷부분에 있는 예제에서는 후위 탐색을 수행하지만 전위 탐색을 수행하도록 수정하는 것은 간단합니다.

다른 옵션은 재귀를 사용할지 스택 기반 탐색을 사용할지 결정하는 것입니다.이 문서의 뒷부분에 있는 예제에서는 두 방식을 모두 보여 줍니다.

파일과 폴더에 대한 다양한 작업을 수행해야 한다면 단일 대리자를 통해 호출할 수 있는 별도의 함수로 작업을 리팩터링하여 이 예제를 모듈화할 수 있습니다.

[!참고]

NTFS 파일 시스템은 연결 지점, 기호화된 링크 및 하드 링크의 형태로 재분석 지점을 포함할 수 있습니다.GetFilesGetDirectories 등의 .NET Framework 메서드는 재분석 지점 아래에 있는 하위 디렉터리를 반환하지 않습니다.이 동작은 두 재분석 지점이 상호 참조하는 경우 무한 루프에 빠지지 않도록 보호합니다.일반적으로 재분석 지점으로 작업할 때에는 실수로 파일을 수정하거나 삭제하지 않도록 특히 주의해야 합니다.재분석 지점에 대한 정밀한 제어가 필요한 경우에는 적절한 Win32 파일 시스템 메서드를 직접 호출하는 플랫폼 호출이나 네이티브 코드를 사용하십시오.

예제

다음 예제에서는 재귀를 사용하여 디렉터리 트리를 탐색하는 방법을 보여 줍니다.재귀는 적절한 방식이기는 하지만 디렉터리 트리의 규모가 크고 복잡하게 중첩되어 있는 경우 스택 오버플로 예외를 발생시킬 위험이 있습니다.

처리되는 특정한 예외와 각 파일이나 폴더에 수행되는 특정 작업은 예제로만 제공됩니다.따라서 해당 요구 사항에 맞게 이 코드를 수정해야 합니다.자세한 내용은 코드의 주석을 참조하십시오.

public class RecursiveFileSearch
{
    static System.Collections.Specialized.StringCollection log = new System.Collections.Specialized.StringCollection();

    static void Main()
    {
        // Start with drives if you have to search the entire computer.
        string[] drives = System.Environment.GetLogicalDrives();

        foreach (string dr in drives)
        {
            System.IO.DriveInfo di = new System.IO.DriveInfo(dr);

            // Here we skip the drive if it is not ready to be read. This
            // is not necessarily the appropriate action in all scenarios.
            if (!di.IsReady)
            {
                Console.WriteLine("The drive {0} could not be read", di.Name);
                continue;
            }
            System.IO.DirectoryInfo rootDir = di.RootDirectory;
            WalkDirectoryTree(rootDir);
        }

        // Write out all the files that could not be processed.
        Console.WriteLine("Files with restricted access:");
        foreach (string s in log)
        {
            Console.WriteLine(s);
        }
        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key");
        Console.ReadKey();
    }

    static void WalkDirectoryTree(System.IO.DirectoryInfo root)
    {
        System.IO.FileInfo[] files = null;
        System.IO.DirectoryInfo[] subDirs = null;

        // First, process all the files directly under this folder
        try
        {
            files = root.GetFiles("*.*");
        }
        // This is thrown if even one of the files requires permissions greater
        // than the application provides.
        catch (UnauthorizedAccessException e)
        {
            // This code just writes out the message and continues to recurse.
            // You may decide to do something different here. For example, you
            // can try to elevate your privileges and access the file again.
            log.Add(e.Message);
        }

        catch (System.IO.DirectoryNotFoundException e)
        {
            Console.WriteLine(e.Message);
        }

        if (files != null)
        {
            foreach (System.IO.FileInfo fi in files)
            {
                // In this example, we only access the existing FileInfo object. If we
                // want to open, delete or modify the file, then
                // a try-catch block is required here to handle the case
                // where the file has been deleted since the call to TraverseTree().
                Console.WriteLine(fi.FullName);
            }

            // Now find all the subdirectories under this directory.
            subDirs = root.GetDirectories();

            foreach (System.IO.DirectoryInfo dirInfo in subDirs)
            {
                // Resursive call for each subdirectory.
                WalkDirectoryTree(dirInfo);
            }
        }            
    }
}

다음 예제에서는 재귀를 사용하지 않고 디렉터리 트리에서 파일 및 폴더를 반복하는 방법을 보여 줍니다.이 방법에서는 LIFO(후입선출) 스택인 제네릭 Stack<T> 컬렉션 형식을 사용합니다.

처리되는 특정한 예외와 각 파일이나 폴더에 수행되는 특정 작업은 예제로만 제공됩니다.따라서 해당 요구 사항에 맞게 이 코드를 수정해야 합니다.자세한 내용은 코드의 주석을 참조하십시오.

public class StackBasedIteration
{
    static void Main(string[] args)
    {
        // Specify the starting folder on the command line, or in 
        // Visual Studio in the Project > Properties > Debug pane.
        TraverseTree(args[0]);

        Console.WriteLine("Press any key");
        Console.ReadKey();
    }

    public static void TraverseTree(string root)
    {
        // Data structure to hold names of subfolders to be
        // examined for files.
        Stack<string> dirs = new Stack<string>(20);

        if (!System.IO.Directory.Exists(root))
        {
            throw new ArgumentException();
        }
        dirs.Push(root);

        while (dirs.Count > 0)
        {
            string currentDir = dirs.Pop();
            string[] subDirs;
            try
            {
                subDirs = System.IO.Directory.GetDirectories(currentDir);
            }
            // An UnauthorizedAccessException exception will be thrown if we do not have
            // discovery permission on a folder or file. It may or may not be acceptable 
            // to ignore the exception and continue enumerating the remaining files and 
            // folders. It is also possible (but unlikely) that a DirectoryNotFound exception 
            // will be raised. This will happen if currentDir has been deleted by
            // another application or thread after our call to Directory.Exists. The 
            // choice of which exceptions to catch depends entirely on the specific task 
            // you are intending to perform and also on how much you know with certainty 
            // about the systems on which this code will run.
            catch (UnauthorizedAccessException e)
            {                    
                Console.WriteLine(e.Message);
                continue;
            }
            catch (System.IO.DirectoryNotFoundException e)
            {
                Console.WriteLine(e.Message);
                continue;
            }

            string[] files = null;
            try
            {
                files = System.IO.Directory.GetFiles(currentDir);
            }

            catch (UnauthorizedAccessException e)
            {

                Console.WriteLine(e.Message);
                continue;
            }

            catch (System.IO.DirectoryNotFoundException e)
            {
                Console.WriteLine(e.Message);
                continue;
            }
            // Perform the required action on each file here.
            // Modify this block to perform your required task.
            foreach (string file in files)
            {
                try
                {
                    // Perform whatever action is required in your scenario.
                    System.IO.FileInfo fi = new System.IO.FileInfo(file);
                    Console.WriteLine("{0}: {1}, {2}", fi.Name, fi.Length, fi.CreationTime);
                }
                catch (System.IO.FileNotFoundException e)
                {
                    // If file was deleted by a separate application
                    //  or thread since the call to TraverseTree()
                    // then just continue.
                    Console.WriteLine(e.Message);
                    continue;
                }
            }

            // Push the subdirectories onto the stack for traversal.
            // This could also be done before handing the files.
            foreach (string str in subDirs)
                dirs.Push(str);
        }
    }
}

응용 프로그램에 폴더를 열 수 있는 권한이 있는지 확인하기 위해 모든 폴더를 테스트하는 작업은 시간이 너무 많이 걸립니다.따라서 이 코드 예제에서는 작업의 해당 부분을 try/catch 블록으로 묶기만 했습니다.폴더에 대한 액세스가 거부된 경우 권한을 상승시키고 다시 액세스할 수 있도록 catch 블록을 수정할 수 있습니다.일반적으로 응용 프로그램을 알 수 없는 상태로 두지 않고 처리할 수 있는 예외만 catch합니다.

디렉터리 트리의 내용을 메모리나 디스크에 저장해야 하는 경우 각 파일의 FullName 속성(string 형식)만 저장하는 것이 좋습니다.그런 다음 필요에 따라 이 문자열을 사용하여 새 FileInfo 또는 DirectoryInfo 개체를 만들거나 추가적인 처리가 필요한 파일을 열 수 있습니다.

강력한 프로그래밍

강력한 파일 반복 코드에서는 파일 시스템의 여러 가지 복잡한 특성을 고려해야 합니다.자세한 내용은 NTFS Technical Reference를 참조하십시오.

참고 항목

참조

System.IO

개념

LINQ 및 파일 디렉터리

기타 리소스

파일 시스템 및 레지스트리(C# 프로그래밍 가이드)