1. 파일 시스템(Filesystem) 다루기
프로그램에서는 종종 파일을 통한 입력과 출력을 처리해야 하는 경우가 있으며 System과 System.IO 네임스페이스에는 이를 목적으로 만들어진 여러 클래스가 존재합니다.
● cross-platform 환경과 파일 시스템 제어
개발 중인 프로그램이 Windows와 Linux, MacOS 등 다양한 환경하에서 작동되어야 하는 것이라면 이것은 다른 OS로 인한 차이점 즉, cross-platform의 제어가 필요한 이유가 됩니다. 가장 대표적으로 들 수 있는 문제가 파일 시스템(예를 들면 경로(Path)와 같은)의 경우인데 파일 시스템에 관한 다양한 정보 확인을 통해 각각의 경우에 대비해야 합니다.
우선 간단한 Console App프로젝트를 생성하고 아래 3개의 정적 Import구문을 추가합니다.
using static System.IO.Directory;
using static System.IO.Path;
using static System.Environment;
위 네임스페이스에는 현재 파일시스템에 관한 여러 정보를 확인해 볼 수 있는 다양한 메서드와 속성이 존재하는데 아래 예제와 같이 사용할 수 있습니다.
Console.WriteLine($"PathSeparator : {PathSeparator}");
Console.WriteLine($"DirectorySeparatorChar : {DirectorySeparatorChar}");
Console.WriteLine($"GetCurrentDirectory : {GetCurrentDirectory()}");
Console.WriteLine($"CurrentDirectory : {CurrentDirectory}");
Console.WriteLine($"SystemDirectory : {SystemDirectory}");
Console.WriteLine($"GetTempPath : {GetTempPath()}");
Console.WriteLine($"GetFolderPath(SpecialFolder.System) : {GetFolderPath(SpecialFolder.System)}");
Console.WriteLine($"GetFolderPath(SpecialFolder.ApplicationData) : {GetFolderPath(SpecialFolder.ApplicationData)}");
Console.WriteLine($"GetFolderPath(SpecialFolder.MyDocuments) : {GetFolderPath(SpecialFolder.MyDocuments)}");
Console.WriteLine($"GetFolderPath(SpecialFolder.Personal) : {GetFolderPath(SpecialFolder.Personal)}");
// PathSeparator : ;
// DirectorySeparatorChar : \
// GetCurrentDirectory : C:\Users\Administrator
// CurrentDirectory : C:\Users\Administrator
// SystemDirectory : C:\Windows\system32
// GetTempPath : C:\Windows\Temp\
// GetFolderPath(SpecialFolder.System) : C:\Windows\system32
// GetFolderPath(SpecialFolder.ApplicationData) : C:\Users\Administrator\AppData\Roaming
// GetFolderPath(SpecialFolder.MyDocuments) : C:\Users\Administrator\Documents
// GetFolderPath(SpecialFolder.Personal) : C:\Users\Administrator\Documents
위 예제는 Windows상에서 실행한 결과이며 DirectorySeparatorChar로 \ 문자를 표시하고 있습니다. Windows에서는 디렉터리의 구분을 \ 문자로 하지만 MacOS나 Linux의 경우에는 / 문자로 디렉터리를 구분합니다.
● 드라이브(Drive) 확인
드라이브에 관한 정보는 DriveInfo의 다양한 정적메서드를 통해서 확인할 수 있습니다.
Console.WriteLine("{0,-5} | {1,-10} | {2,-7} | {3,18} | {4,18}", "NAME", "TYPE", "FORMAT", "SIZE (BYTES)", "FREE SPACE");
foreach (DriveInfo drive in DriveInfo.GetDrives())
{
if (drive.IsReady)
Console.WriteLine("{0,-5} | {1,-10} | {2,-7} | {3,18:N0} | {4,18:N0}", drive.Name, drive.DriveType, drive.DriveFormat, drive.TotalSize, drive.AvailableFreeSpace);
else
Console.WriteLine("{0,-5} | {1,-10}", drive.Name, drive.DriveType);
}
// NAME | TYPE | FORMAT | SIZE (BYTES) | FREE SPACE
// C:\ | Fixed | NTFS | 499,459,137,536 | 259,138,445,312
// W:\ | Removable | ODFS | 1,108,101,562,368 | 1,098,562,577,408
// Y:\ | Network | NTFS | 124,227,940,352 | 115,537,842,176
// Z:\ | Network | NTFS | 2,000,396,738,560 | 1,584,979,124,224
TYPE이 Fixed로된 것이면 현재 Computer에 고정된 Drive라는 것이며 Removable이면 USB와 같은 외부 저장 장치를 의미합니다. Network는 해당 드라이브가 Network Drive로 연결된 것을 의미합니다.
● 디렉토리(Directory) 다루기
디렉터리에 관해서는 Directory, Path, Environment의 정적 클래스를 사용할 수 있고 각각의 Type은 파일 시스템에 관한 많은 작업을 수행할 수 있는 여러 메서드를 가지고 있습니다.
이들 메서드를 사용하는 것은 특히 프로그램에서 사용자 임의의 경로를 다루어야 하는 경우처럼 특정 플렛폼에서 동작한다는 가정을 세울 수 없는 cross-platform환경에서는 디렉터리의 분리 문자가 무엇인지는 사전에 파악할 수 없으므로 더 중요하게 작용할 수 있습니다.
string customDirectory = Combine(GetFolderPath(SpecialFolder.Personal), "MyDirectory", "firstDirectory", "SecondDirectory");
Console.WriteLine($"새로운 디렉토리 : {customDirectory}");
if (!Exists(customDirectory))
CreateDirectory(customDirectory);
Delete(customDirectory, recursive: true);
위 예제는 사용자폴더하위에 새로운 폴더를 생성하기 위해서 생성할 폴더(MyDirectory, firstDirectory, SecondDirectory)와 SpecialFolder.Personal(사용자 폴더)를 Combine메서드로 결합하고 Exists로 해당 폴더의 존재 유무를 확인한 뒤 CreateDirectory() 메서드를 통해 디렉터리를 생성하도록 합니다.
그리고 마지막에는 생성한 디렉토리를 Delete() 메서드를 통해 삭제합니다. 이때 recursive를 true로 하였는데 이는 해당 디렉터리 내부에 하위 디렉터리나 파일이 존재해도 강제로 삭제하도록 하기 위한 것입니다.
여기서 중요한 것은 'Documents\MyDirectory\firstDirectory\SecondDirectory'처럼 임의의 경로분리 문자를 리터럴로 지정해 사용하지 않았다는 것입니다. Combine()은 플랫폼에 맞는 구조로 알아서 디렉터리를 결합해줄 것입니다.
● 파일(files) 다루기
파일을 다루기 위해서는 System.IO.Directory처럼 System.IO.File을 정적 Import하여 사용할 수 있습니다. 다만 이번에는 그렇게 하지 않을 것인데 이는 System.IO.Directory와 몇몇 메서드의 이름이 같은 문제로 인해 충돌이 발생할 수 있기 때문입니다. 하지만 File자체로도 충분히 짧은 이름을 가지고 있기 때문에 굳이 Import를 하지 않아도 사용하는 데는 큰 불편함이 없을 것입니다.
string customDirectory = Combine(GetFolderPath(SpecialFolder.Personal), "MyDirectory", "firstDirectory", "SecondDirectory");
Console.WriteLine($"새로운 디렉토리 : {customDirectory}");
if (!Exists(customDirectory))
CreateDirectory(customDirectory);
string myFile = Combine(customDirectory, "myFile.txt");
string copyFile = Combine(customDirectory, "myFile.bak");
Console.WriteLine($"생성할 파일 : {myFile}");
if (!File.Exists(myFile))
{
StreamWriter textWriter = File.CreateText(myFile);
textWriter.WriteLine("1234567890abcdefghijklmnopqrstuvwxyz");
textWriter.Close();
File.Copy(sourceFileName: myFile, destFileName: copyFile, overwrite: true);
StreamReader textReader = File.OpenText(copyFile);
Console.WriteLine(textReader.ReadToEnd());
textReader.Close();
}
예제는 myFile.txt라는 파일을 StreamWriter를 통해 생성해고 Copy로 myFile.bak로 복사하도록 한 뒤 복사한 파일을 읽어 해당 내용을 표시하도록 합니다. 이때 StreamWriter나 StreamReader에서는 사용이 종료되면 Close() 메서드를 호출하고 있는데 이는 시스템 리소스를 해제하여 불필요한 자원낭비를 막도록 하기 위함입니다.
특정 파일을 예제에서처럼 열게 되는 경우에는 그 용도에 따라서 어떻게 파일이 열릴지를 제어해야 하는 경우가 있으며 이를 위해 File의 Open()이라는 메서드는 FileMode, FileAccess, FileShare라는 3개의 enum값을 지정해 줄 수 있습니다.
FileMode | CreateNew, OpenOrCreate, Truncate등 파일을 통해 무엇을 할지를 지정합니다. |
FileAccess | ReadWrite와 같이 파일에 필요한 접근 레벨을 설정합니다. |
FileShare | 다른 프로세스에서 해당 파일에 접근할 수 있는 공유레벨을 설정합니다. |
위 설정을 토대로 특정 파일에 대한 읽기용도이면서 다른 프로세스에서 동시에 읽기를 허용하려면 다음과 같은 방법으로 파일을 열 수 있습니다.
FileStream file = File.Open(myFile, FileMode.Open, FileAccess.Read, FileShare.Read);
● 경로(Path) 다루기
Path는 단순한 경로를 나타내는 것 뿐아니라 파일이나 디렉터리의 전체 경로에서 파일명 혹은 디렉토리명, 또는 파일의 확장자를 추출하거나 새로운 파일 혹은 임시 파일명을 생성하는 경우에도 사용될 수 있습니다.
string customDirectory = Combine(GetFolderPath(SpecialFolder.Personal), "MyDirectory", "firstDirectory", "SecondDirectory");
Console.WriteLine($"새로운 디렉토리 : {customDirectory}");
if (!Exists(customDirectory))
CreateDirectory(customDirectory);
string myFile = Combine(customDirectory, "myFile.txt");
Console.WriteLine($"해당 경로의 폴더명 : {GetDirectoryName(myFile)}");
Console.WriteLine($"해당 경로의 파일명 : {GetFileName(myFile)}");
Console.WriteLine($"해당 경로의 파일 확장자 : {GetExtension(myFile)}");
Console.WriteLine($"랜덤 파일명 생성 : {GetRandomFileName()}");
Console.WriteLine($"임시 파일명 생성 : {GetTempFileName()}");
// 새로운 디렉토리 : C:\Users\Administrator\Documents\MyDirectory\firstDirectory\SecondDirectory
// 해당 경로의 폴더명 : C:\Users\Administrator\Documents\MyDirectory\firstDirectory\SecondDirectory
// 해당 경로의 파일명 : myFile.txt
// 해당 경로의 파일 확장자 : .txt
// 랜덤 파일명 생성 : czmjblwt.zdn
// 임시 파일명 생성 : C:\Windows\Temp\tmp87BC.tmp
예제에서 사용된 모든 메서드는 Path의 정적메서드입니다. 이 중에서 GetTempFileName() 메서드는 임시 폴더에 0byte 크기의 파일을 실제 생성하고 그 이름을 반환하지만 GetRandomFileName() 메서드는 이름만 반환할 뿐 실제 파일을 생성하지는 않습니다.
● 파일, 디렉토리 정보 가져오기
파일에서 파일의 크기나 마지막 접근시간 등의 정보를 확인하려면 FileInfo(디렉터리라면 DirectoryInfo)의 인스턴스를 생성하여 확인합니다.
FileInfo나 DirectoryInfo둘다 FileSystemInfo에서 상속되었으므로 각각에서 필요한 자체적인 Member와 함께 LastAccessTime이나 Delete와 같은 FileSystemInfo의 Member도 같이 가지게 됩니다.
자세한 것은 아래 표를 참고합니다.
Class | Field | Property | Method |
FileSystemInfo | FullPath, OriginalPath | Attributes, CreationTime, CreationTimeUtc, Exists, Extension, FullName, LastAccessTime, LastAccessTimeUtc, LastWriteTime, LastWriteTimeUtc, Name | Delete, GetObjectData, Refresh |
DirectoryInfo | Parent, Root | Create, CreateSubdirectory, EnumerateDirectories, EnumerateFiles, EnumerateFileSystemInfos, GetAccessControl, GetDirectories, GetFiles, GetFileSystemInfos, MoveTo, SetAccessControl | |
FileInfo | Directory, DirectoryName, IsReadOnly, Length | AppendText, CopyTo, Create, CreateText, Decrypt, Encrypt, GetAccessControl, MoveTo, Open, OpenRead, OpenText, OpenWrite, Replace, SetAccessControl |
string customDirectory = Combine(GetFolderPath(SpecialFolder.Personal), "MyDirectory", "firstDirectory", "SecondDirectory");
string myFile = Combine(customDirectory, "myFile.txt");
FileInfo info = new(myFile);
Console.WriteLine($"대상파일 : {myFile}");
Console.WriteLine($"파일크기 : {info.Length} bytes");
Console.WriteLine($"마지막 접근 일자 : {info.LastAccessTime}");
Console.WriteLine($"읽기전용 여부 : {info.IsReadOnly}");
// 대상파일 : C:\Users\Administrator\Documents\MyDirectory\firstDirectory\SecondDirectory\myFile.txt
// 파일크기 : 38 bytes
// 마지막 접근 일자 : 2022-06-10 오전 11:02:41
// 읽기전용 여부 : False
또한 FileAttributes를 사용하여 해당 파일의 속성을 확인해 줄 수도 있습니다.
FileInfo info = new(myFile);
Console.WriteLine($"파일의 압축여부 : {info.Attributes.HasFlag(FileAttributes.Compressed)}");
'.NET > C#' 카테고리의 다른 글
[C#] File 다루기 - 3. 인코딩(Encoding)과 디코딩(Decoding) (0) | 2022.06.26 |
---|---|
[C#] File 다루기 - 2. 스트림(Stream) 다루기 (0) | 2022.06.24 |
[C#] 인터페이스(Interface)와 상속(Inheriting) - 7. Code분석(StyleCop) (0) | 2022.06.24 |
[C#] 인터페이스(Interface)와 상속(Inheriting) - 6. 상속(Inheriting) (0) | 2022.06.24 |
[C#] 인터페이스(Interface)와 상속(Inheriting) - 5. NULL (0) | 2022.06.24 |