CodeGym /행동 /C# SELF /파일과 디렉토리 정보 얻기

파일과 디렉토리 정보 얻기

C# SELF
레벨 39, 레슨 2
사용 가능

1. 소개

Windows(뿐만 아니라 다른 환경에서도)에서 각 파일과 디렉토리는 일련의 속성(메타데이터)을 가진다: 전체 경로, 이름, 확장자, 크기, 생성/수정/접근 시간, 속성 등. 파일은 단순한 바이트 덩어리가 아니라 FileInfo와 DirectoryInfo로 읽을 수 있는 프로필 같은 거야.

속성 설명 예시
전체 경로 파일/폴더의 전체 이름
C:\data\myfile.txt
이름 경로를 제외한 이름
myfile.txt
확장자 .txt, .csv, .jpg 등
.txt
크기 (Bytes) 파일 크기(바이트)
4096
생성 일시 파일/폴더가 생성된 시각
2024-04-16 19:30:10
수정 일시 마지막으로 내용이 변경된 시각
2024-05-01 18:14:02
속성 예: 읽기 전용, 숨김 등
ReadOnly, Hidden
부모 폴더 파일/디렉토리가 속한 폴더
C:\data

2. 파일 속성 심화 작업

시간 스탬프 상세 분석

속성 CreationTime, LastWriteTime, LastAccessTime는 DateTime을 반환하고, 동작은 파일 시스템과 파일에 대한 작업에 따라 달라져.


var fileInfo = new FileInfo("document.txt");

if (fileInfo.Exists)
{
    Console.WriteLine($"생성: {fileInfo.CreationTime:yyyy-MM-dd HH:mm:ss}");
    Console.WriteLine($"수정: {fileInfo.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
    Console.WriteLine($"접근: {fileInfo.LastAccessTime:yyyy-MM-dd HH:mm:ss}");
    
    // 생성과 마지막 수정의 차이
    var age = fileInfo.LastWriteTime - fileInfo.CreationTime;
    Console.WriteLine($"파일은 {age.TotalDays:F1} 일 동안 변경되었음");
}

파일을 복사하면 생성 날짜는 일반적으로 현재 시각으로 바뀌지만 수정 날짜는 원본에서 유지되는 경우가 있어. 백업이나 활동 분석 시 이 점을 유의해야 해.

확장자 및 파일명 작업

속성 FullName, Name, Extension은 단순해 보이지만 예외가 있어: 확장자가 없는 파일, .tar.gz 같은 복합 확장자, 점으로 시작하는 숨김 파일 등.


var files = new[]
{
    new FileInfo("document.txt"),
    new FileInfo("archive.tar.gz"),
    new FileInfo("README"),
    new FileInfo(".gitignore")
};

foreach (var file in files)
{
    Console.WriteLine($"전체 이름: {file.FullName}");
    Console.WriteLine($"이름: {file.Name}");
    Console.WriteLine($"확장자: '{file.Extension}'");
    
    // 확장자를 제외한 이름
    string nameWithoutExtension = Path.GetFileNameWithoutExtension(file.Name);
    Console.WriteLine($"확장자 없는 이름: {nameWithoutExtension}");
    Console.WriteLine("---");
}

파일 크기와 포맷팅

속성 Length는 바이트 단위 크기를 반환해; 사용자에게는 KB/MB/GB 단위가 편하지. 보조 함수 예:


static string FormatFileSize(long bytes)
{
    string[] suffixes = { "Б", "КБ", "МБ", "ГБ", "ТБ" };
    int counter = 0;
    decimal number = bytes;
    
    while (Math.Round(number / 1024) >= 1)
    {
        number /= 1024;
        counter++;
    }
    
    return $"{number:N1} {suffixes[counter]}";
}

// 사용 예
var file = new FileInfo("bigfile.zip");
if (file.Exists)
{
    Console.WriteLine($"파일 크기: {FormatFileSize(file.Length)}");
}

3. 파일 속성(Attributes) 심화

속성은 비트 플래그의 집합인 열거형 FileAttributes로 표현돼서, 한 파일에 여러 속성이 동시에 있을 수 있어. 확인은 HasFlag로 하면 편해.


var fileInfo = new FileInfo("important.txt");

Console.WriteLine($"파일 속성: {fileInfo.Attributes}");

// 특정 속성 확인
if (fileInfo.Attributes.HasFlag(FileAttributes.Hidden))
{
    Console.WriteLine("파일이 숨김 상태야!");
}

if (fileInfo.Attributes.HasFlag(FileAttributes.ReadOnly))
{
    Console.WriteLine("파일이 쓰기 금지야!");
}

if (fileInfo.Attributes.HasFlag(FileAttributes.System))
{
    Console.WriteLine("이건 시스템 파일이야!");
}

속성은 코드로 변경할 수 있어:


// 파일을 숨김으로 만들기
fileInfo.Attributes |= FileAttributes.Hidden;

// "읽기 전용" 속성 제거
fileInfo.Attributes &= ~FileAttributes.ReadOnly;

// 여러 속성을 한 번에 설정
fileInfo.Attributes = FileAttributes.ReadOnly | FileAttributes.Hidden;

4. 디렉토리 고급 작업

패턴으로 파일 검색하기

메서드 GetFiles()와 GetDirectories()는 패턴을 받아 내용 필터링에 도움을 줘.


var dir = new DirectoryInfo(@"C:\Projects");

if (dir.Exists)
{
    // 모든 텍스트 파일 찾기
    var textFiles = dir.GetFiles("*.txt");
    Console.WriteLine($"찾은 텍스트 파일 수: {textFiles.Length}");
    
    // "temp"로 시작하는 모든 파일 찾기
    var tempFiles = dir.GetFiles("temp*");
    
    // 모든 이미지 파일 찾기
    var imageExtensions = new[] { "*.jpg", "*.png", "*.gif", "*.bmp" };
    var allImages = imageExtensions.SelectMany(ext => dir.GetFiles(ext)).ToArray();
    
    Console.WriteLine($"찾은 이미지 수: {allImages.Length}");
}

하위폴더 재귀 검색

모든 하위폴더를 순회하려면 SearchOption.AllDirectories를 사용해.


var dir = new DirectoryInfo(@"C:\Development");

// 모든 하위폴더에서 C# 파일 찾기
var csharpFiles = dir.GetFiles("*.cs", SearchOption.AllDirectories);
Console.WriteLine($".cs 파일 총 개수: {csharpFiles.Length}");

// 경로와 함께 상위 10개 파일 표시
foreach (var file in csharpFiles.Take(10))
{
    Console.WriteLine($"{file.FullName} ({FormatFileSize(file.Length)})");
}

디렉토리 내용 분석

요약 분석 예: 파일/폴더 수, 총 크기, 확장자별 분포, 가장 큰 파일 top-5 등.


static void AnalyzeDirectory(DirectoryInfo dir)
{
    if (!dir.Exists)
    {
        Console.WriteLine("디렉토리가 존재하지 않아!");
        return;
    }
    
    var files = dir.GetFiles();
    var subdirs = dir.GetDirectories();
    
    Console.WriteLine($"디렉토리 분석: {dir.FullName}");
    Console.WriteLine($"파일 수: {files.Length}, 하위디렉토리 수: {subdirs.Length}");
    
    if (files.Length == 0)
    {
        Console.WriteLine("파일이 없음.");
        return;
    }
    
    long totalSize = files.Sum(f => f.Length);
    Console.WriteLine($"파일 총용량: {FormatFileSize(totalSize)}");
    
    // 확장자별 그룹화
    var byExtension = files.GroupBy(f => f.Extension.ToLower())
                           .OrderByDescending(g => g.Sum(f => f.Length));
    
    Console.WriteLine("\n파일 유형 분포:");
    foreach (var group in byExtension)
    {
        string ext = string.IsNullOrEmpty(group.Key) ? "(확장자 없음)" : group.Key;
        long groupSize = group.Sum(f => f.Length);
        Console.WriteLine($"  {ext}: {group.Count()} 개, {FormatFileSize(groupSize)}");
    }
    
    // 가장 큰 파일 top-5
    var largestFiles = files.OrderByDescending(f => f.Length).Take(5);
    Console.WriteLine("\n가장 큰 파일:");
    foreach (var file in largestFiles)
    {
        Console.WriteLine($"  {file.Name}: {FormatFileSize(file.Length)}");
    }
}

5. 디렉토리 크기 최적화된 계산

큰 폴더에서는 GetFiles() 대신 지연 열거자 EnumerateFiles()/EnumerateDirectories()를 사용하고 예외를 처리하며 진행 상황을 표시하는 게 좋아.


static long GetDirectorySizeAdvanced(DirectoryInfo dir, bool showProgress = false)
{
    long totalSize = 0;
    int fileCount = 0;
    var inaccessibleDirs = new List<string>();
    
    try
    {
        // 큰 디렉토리에서는 EnumerateFiles 사용(지연 로드)
        foreach (var file in dir.EnumerateFiles())
        {
            try
            {
                totalSize += file.Length;
                fileCount++;
                
                if (showProgress && fileCount % 1000 == 0)
                {
                    Console.WriteLine($"처리한 파일 수: {fileCount}, 크기: {FormatFileSize(totalSize)}");
                }
            }
            catch (UnauthorizedAccessException)
            {
                // 파일 접근 불가, 건너뜀
            }
            catch (IOException)
            {
                // 파일 읽기 문제, 건너뜀
            }
        }
        
        // 하위디렉토리 재귀 처리
        foreach (var subdir in dir.EnumerateDirectories())
        {
            try
            {
                totalSize += GetDirectorySizeAdvanced(subdir, showProgress);
            }
            catch (UnauthorizedAccessException)
            {
                inaccessibleDirs.Add(subdir.FullName);
            }
        }
    }
    catch (UnauthorizedAccessException)
    {
        inaccessibleDirs.Add(dir.FullName);
    }
    
    if (inaccessibleDirs.Any() && showProgress)
    {
        Console.WriteLine($"접근 불가 디렉토리 수: {inaccessibleDirs.Count}");
    }
    
    return totalSize;
}

// 사용 예
var targetDir = new DirectoryInfo(@"C:\Users");
Console.WriteLine("디렉토리 크기 계산 시작...");
long size = GetDirectorySizeAdvanced(targetDir, showProgress: true);
Console.WriteLine($"총 크기: {FormatFileSize(size)}");

6. 메타데이터의 실용적 적용

날짜로 파일 검색하기

지정 기간에 수정된 파일을 찾으면 정리, 분석, 감사에 편해.


static void FindFilesByDate(DirectoryInfo dir, DateTime fromDate, DateTime toDate)
{
    Console.WriteLine($"{fromDate:yyyy-MM-dd} 부터 {toDate:yyyy-MM-dd} 까지 변경된 파일 검색");
    
    var matchingFiles = dir.GetFiles("*", SearchOption.AllDirectories)
                           .Where(f => f.LastWriteTime >= fromDate && f.LastWriteTime <= toDate)
                           .OrderByDescending(f => f.LastWriteTime);
    
    Console.WriteLine($"찾은 파일 수: {matchingFiles.Count()}");
    
    foreach (var file in matchingFiles.Take(20))
    {
        Console.WriteLine($"{file.LastWriteTime:yyyy-MM-dd HH:mm} - {file.Name} ({FormatFileSize(file.Length)})");
    }
}

// 예: 지난 주에 변경된 모든 파일 찾기
var dir = new DirectoryInfo(@"C:\Documents");
var weekAgo = DateTime.Now.AddDays(-7);
FindFilesByDate(dir, weekAgo, DateTime.Now);

중복 파일 찾기

간단한 방법은 크기별로 그룹화하는 것. 정확도를 높이려면 내용 해시 비교를 추가하면 돼.


static void FindPotentialDuplicates(DirectoryInfo dir)
{
    Console.WriteLine($"잠재적 중복 파일 검색: {dir.FullName}");
    
    var files = dir.GetFiles("*", SearchOption.AllDirectories)
                   .Where(f => f.Length > 0) // 빈 파일 제외
                   .GroupBy(f => f.Length)
                   .Where(g => g.Count() > 1) // 여러 파일이 있는 그룹만
                   .OrderByDescending(g => g.Key); // 크기순 정렬
    
    foreach (var sizeGroup in files.Take(10))
    {
        Console.WriteLine($"\n크기 {FormatFileSize(sizeGroup.Key)} 인 파일들 ({sizeGroup.Count()} 개):");
        foreach (var file in sizeGroup)
        {
            Console.WriteLine($"  {file.FullName}");
            Console.WriteLine($"    수정일: {file.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
        }
    }
}

디렉토리 변경 모니터링

최근 N분 동안 변경된 파일을 보여주는 예시야.


static void MonitorRecentChanges(DirectoryInfo dir, int minutesBack = 60)
{
    var cutoffTime = DateTime.Now.AddMinutes(-minutesBack);
    
    var recentFiles = dir.GetFiles("*", SearchOption.AllDirectories)
                         .Where(f => f.LastWriteTime > cutoffTime)
                         .OrderByDescending(f => f.LastWriteTime);
    
    Console.WriteLine($"최근 {minutesBack} 분 동안 변경된 파일:");
    
    if (!recentFiles.Any())
    {
        Console.WriteLine("변경된 파일 없음.");
        return;
    }
    
    foreach (var file in recentFiles)
    {
        var minutesAgo = (DateTime.Now - file.LastWriteTime).TotalMinutes;
        Console.WriteLine($"{file.Name} - {minutesAgo:F0} 분 전 ({FormatFileSize(file.Length)})");
    }
}

7. 부모 디렉토리 작업

FileInfo의 Directory와 DirectoryInfo의 Parent로 계층을 올라갈 수 있어.


var file = new FileInfo(@"C:\Projects\MyApp\src\Program.cs");

Console.WriteLine($"파일: {file.Name}");
Console.WriteLine($"폴더: {file.Directory.Name}");
Console.WriteLine($"부모 폴더: {file.Directory.Parent.Name}");
Console.WriteLine($"프로젝트 루트 폴더: {file.Directory.Parent.Parent.Name}");

// 루트까지 올라가기
var currentDir = file.Directory;
while (currentDir.Parent != null)
{
    Console.WriteLine($"레벨: {currentDir.Name}");
    currentDir = currentDir.Parent;
}
Console.WriteLine($"루트: {currentDir.Name}");

8. 함정과 흔한 실수

1. 캐싱. FileInfo와 DirectoryInfo 객체는 값을 캐시해. 객체 생성 후 파일이 바뀌면 데이터가 오래될 수 있어. 최신화하려면 Refresh()를 사용해.


var file = new FileInfo("test.txt");
file.Refresh(); // 메타데이터 갱신

2. 접근 예외. 일부 파일/폴더는 접근 불가해: UnauthorizedAccessException 등 접근 관련 예외를 처리해야 해.


try
{
    var files = new DirectoryInfo(path).GetFiles();
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("접근 불가");
}

3. 시간 스탬프. 복사 시 CreationTime은 바뀔 수 있고, LastWriteTime은 유지될 수 있어. 보고서나 동기화 알고리즘에 영향을 줘.


File.Copy("a.txt", "b.txt");

4. 성능. GetFiles()는 모든 항목을 한꺼번에 로드하므로 큰 폴더에서는 느릴 수 있어. 지연 열거인 EnumerateFiles()를 선호해.

2
과제
C# SELF, 레벨 39, 레슨 2
잠금
디렉터리 내용 나열하기
디렉터리 내용 나열하기
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION