[C#] 43. 파일(FileInfo)과 디렉토리(DirectoryInfo) 다루기


Study/C#  2021. 10. 5. 13:46

안녕하세요. 명월입니다.


이 글은 C#에서 파일(FileInfo)과 디렉토리(DirectoryInfo) 다루기에 대한 글입니다.


이전 글에서 IO를 통해서 파일을 생성하고 읽어오는 방법에 대해서 소개했습니다.

link - [C#] 41. 파일 다루기(IO)와 파일 메타 데이터(FileInfo) 사용하기


이번 글에서는 파일을 작성하고 읽는 것보다 디렉토리와 파일 메타 데이터로 디렉토리와 파일에 대한 정보를 취득하고 탐색하는 방법에 설명하겠습니다.

FileInfo 클래스는 파일에 대한 메타 데이터, 즉, 파일에 대한 위치, 크기와 작성 일시, 수정 일시에 대한 정보가 있습니다.

디렉토리도 실제로는 바이너리 데이터로 존재하는 것이 아니고 OS에서 관리하는 메타 데이터입니다. FileInfo와 같이 DirectoryInfo가 있는데 디렉토리(폴더)에 대한 정보가 있습니다.

using System;
using System.IO;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 디렉토리 메타 데이터 취득
      var dirinfo = new DirectoryInfo("d:\\work");
      // 디렉토리 이름
      Console.WriteLine("DirectoryInfo Name - " + dirinfo.FullName);
      // 디렉토리 작성 일시
      Console.WriteLine("DirectoryInfo CreationTime - " + dirinfo.CreationTime);
      // 디렉토리 마지막으로 작성된 일시
      Console.WriteLine("DirectoryInfo LastWriteTime - " + dirinfo.LastWriteTime);
      // 디렉토리 마지막으로 접근한 일시
      Console.WriteLine("DirectoryInfo LastAccessTime - " + dirinfo.LastAccessTime);

      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

파일 메타 파일처럼 디렉토리 메타 정보를 취득할 수 있습니다.

DirectoryInfo에는 Fileinfo와 다르게 디렉토리를 추가, 삭제가 있습니다.

파일 생성은 해당 파일에 대한 바이너리 데이터가 필요하지만 디렉도리는 메타 데이터 추가, 삭제만으로 다룰 수 있기 때문입니다.

using System;
using System.IO;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 디렉토리 메타 데이터 취득
      var dirinfo = new DirectoryInfo("d:\\work\\abc");
      // 디렉토리 추가
      dirinfo.Create();      

      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

디렉토리가 추가되었습니다.

using System;
using System.IO;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 디렉토리 메타 데이터 취득
      var dirinfo = new DirectoryInfo("d:\\work\\abc");
      // 디렉토리 삭제
      dirinfo.Delete();      

      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

디렉토리가 삭제되었습니다.


그리고 FileInfo와 DirectoryInfo의 경우는 인스턴스를 생성해서 사용해야 하지만, 매번 메타 데이터를 사용하기 위해서 인스턴스를 생성하기에는 부담스럽네요.

그래서 static 클래스로 메타 데이터를 다룰 수 있습니다.

using System;
using System.IO;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 디렉토리가 존재 여부
      Console.WriteLine("Directory.Exists - " + Directory.Exists("d:\\work"));
      // 파일 존재 여부
      Console.WriteLine("File.Exists - " + File.Exists("d:\\work\\test.txt"));

      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

Exists의 함수는 FileInfo와 DirectoryInfo에도 있는 함수입니다만 File과 Directory의 static 클래스로도 메타 데이터를 사용할 수 있습니다.


이 File과 Directory 클래스로 단순히 파일과 디렉토리의 정보를 가져오는 클래스지만, 탐색 알고리즘를 이용하여 만들면 파일 탐색 프로그램을 작성할 수 있습니다.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace Example
{
  // 디렉토리와 파일 정보 클래스
  class DirNode
  {
    // 디렉토리 패스
    public string Path { get; private set; }
    // 하위 디렉토리
    public List<DirNode> Dirs { get; private set; }
    // 하위 파일들
    public List<string> Files { get; private set; }
    // 생성자
    public DirNode(string path)
    {
      // 인스턴스 생성
      this.Dirs = new List<DirNode>();
      this.Files = new List<string>();
      this.Path = path;
    }
  }
  class Program
  {
    // 디렉토리 정보 취득
    static DirNode GetDirectoryInfo(string path)
    {
      // 디렉토리가 존재하지 않으면
      if (!Directory.Exists(path))
      {
        // null 리턴
        return null;
      }
      // 디렉토리와 파일 정보 클래스의 인스턴스 생성
      var dir = new DirNode(path);
      // 하위 디렉토리 정보 취득
      var subdirs = Directory.GetDirectories(path);
      // 반복문으로 하위 디렉토리 탐색
      foreach (var d in subdirs)
      {
        // 하위 디렉토리 탐색(재귀 함수)
        dir.Dirs.Add(GetDirectoryInfo(d));
      }
      // 하위 파일 정보 취득
      dir.Files.AddRange(Directory.GetFiles(path));
      return dir;
    }
    // 콘솔 출력할 String 데이터 작성
    static void BuildPrintString(DirNode dir, StringBuilder sb, int space = 0, string replace = null)
    {
      // 띄어쓰기 위치 설정
      var pos = space;
      // 개행이 되면 띄어쓰기 현재 위치
      var insertSpace = new Action(() =>
      {
        // 개행
        sb.AppendLine();
        // 위치만큼 띄어쓰기
        for (int j = 0; j < pos; j++)
        {
          // 버퍼에 쓰기
          sb.Append(" ");
        }
      });
      // 루트 디렉토리를 치환
      var buf = replace != null ? dir.Path.Replace(replace, "") : dir.Path;
      // 버퍼에 쓰기
      sb.Append(buf);
      // 띄어쓰기 위치 추가
      pos = pos + buf.Length;
      // 하위 디렉토리가 있으면
      if (dir.Dirs.Count > 0)
      {
        // 하위 디렉토리만큼 탐색
        for (int i = 0; i < dir.Dirs.Count; i++)
        {
          // 두번째부터 개행
          if (i != 0)
          {
            // 개행 + 띄어쓰기
            insertSpace();
          }
          // 탐색 마크 버퍼에 쓰기
          sb.Append("  =>  ");
          // 재귀함수로 탐색
          BuildPrintString(dir.Dirs[i], sb, pos + 10, dir.Path);
        }
        // 개행 + 띄어쓰기
        insertSpace();
      }
      // 하위 파일이 있으면
      if (dir.Files.Count > 0)
      {
        // 하위 파일만큼 탐색
        for (int i = 0; i < dir.Files.Count; i++)
        {
          // 두번째부터 개행
          if (i != 0)
          {
            // 개행 + 띄어쓰기
            insertSpace();
          }
          // 탐색 마크 버퍼에 쓰기
          sb.Append("  =>  ");
          // 파일 이름 버퍼에 쓰기
          sb.Append(dir.Files[i].Replace(dir.Path, ""));
        }
      }
    }
    // 실행 함수
    static void Main(string[] args)
    {
      // d:\\work 디렉토리 정보 취득(하위 디렉토리 포함)
      var dir = GetDirectoryInfo("d:\\work");
      // StringBuilder 인스턴스 생성
      var sb = new StringBuilder();
      // 콘솔에 출력할 String 데이터 작성
      BuildPrintString(dir, sb);
      // 콘솔 출력
      Console.WriteLine(sb.ToString());
      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

위 예제에서 디렉토리에서 하위 디렉토리를 호출할 때는 다시 디렉토리 탐색을 하는 재귀함수를 통해서 디렉토리와 파일을 탐색했습니다.

재귀 함수라는게 매우 stack interrupt가 걸리는 작업이라 성능이 매우 느려지고 overflow 가능성도 있고 효율성이 떨어지지만, 소스가 매우 간결하게 작성할 수 있고, 또 이런 탐색 알고리즘을 만들 때 쉽게 설계가 가능하고 작성할 수 있기 때문에 자주 이용합니다.


여기까지 C#에서 파일(FileInfo)과 디렉토리(DirectoryInfo) 다루기에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.