1. 소개
Windows(뿐만 아니라 다른 환경에서도)에서 각 파일과 디렉토리는 일련의 속성(메타데이터)을 가진다: 전체 경로, 이름, 확장자, 크기, 생성/수정/접근 시간, 속성 등. 파일은 단순한 바이트 덩어리가 아니라 FileInfo와 DirectoryInfo로 읽을 수 있는 프로필 같은 거야.
| 속성 | 설명 | 예시 |
|---|---|---|
| 전체 경로 | 파일/폴더의 전체 이름 | |
| 이름 | 경로를 제외한 이름 | |
| 확장자 | .txt, .csv, .jpg 등 | |
| 크기 (Bytes) | 파일 크기(바이트) | |
| 생성 일시 | 파일/폴더가 생성된 시각 | |
| 수정 일시 | 마지막으로 내용이 변경된 시각 | |
| 속성 | 예: 읽기 전용, 숨김 등 | |
| 부모 폴더 | 파일/디렉토리가 속한 폴더 | |
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()를 선호해.
GO TO FULL VERSION