파일 시스템이란 데이터를 저장하고 관리하는 체계를 의미한다. 이러한 파일 시스템은 운영체제에 따라 디렉토리, 파일 종류, 권한, 상태, 속석 등의 구조가 다르다. 따라서 운영체제마다 파일 시스템 프로그래밍에서 사용하는 헤더 파일과 사용 방법이 다르다.
C++ 표준 라이브러리
에서는 이러한 어려움을 해결할 수 있도록 파일 시스템과 관련된 다양한 형식과 함수를 지원한다.
파일 시스템 라이브러리는 데이터의 입출력을 담당하는 fstream
과는 다르다. fstream
은 해당 파일의 내용을 읽어내는 역할을 하지만, 파일 이름이나 위치 같은 파일에 관한 정보(metadata)를 수정할 수는 없다. 반면에 파일 시스템 라이브러리
는 파일에 관한 정보에 접근할 수 있지만, 파일 자체를 읽을 수는 없다.
C++14
까지 표준 라이브러리에는 파일 입출력을 위한 수단은 있었지만, 파일 시스템을 위한 수단은 없었다. C++17
에서 드디어 C++도 파일 시스템 라이브러리를 지원하게 되었다.
C++ 파일 시스템 라이브러리의 모든 형식과 함수는 std::filesystem
네임스페이스에 속해 있으며, 이를 사용하려면 소스파일에 <filesystem>
헤더를 포함해야 한다.
C++ 파일 시스템 라이브러리
는 파일과 디렉토리 조작에 유용한 기능을 제공한다. 주요 구성 요소와 함수를 정리하면 다음과 같다.
구분 | 이름 | 설명 |
---|---|---|
클래스 | path | 파일과 디렉토리 경로를 나타내는 클래스. 경로 조작이나 분석 지원 |
directory_entry | 디렉토리 내의 항목을 나타내는 클래스. 경로나 속성 정보 제공 | |
directory_iterator | 디렉토리 내의 모든 항목을 순회하기 위한 반복자 클래스 | |
file_status | 파일이나 디렉토리의 상태 정보를 나타내는 열거형 클래스 | |
file_time_type | 파일이나 시간 정보를 나타내는 file_time과 같은 형식 | |
space_info | 디스크 공간에 대한 정보를 제공하는 클래스 | |
열거형 | perms | 파일이나 디렉토리의 권한을 나타내는 열거형 상수 집합 |
file_type | 파일이나 디렉토리의 유형을 나타내는 열거형 상수 집합 | |
함수 | exists | 주어진 경로에 파일이나 디렉토리가 존재하는지 확인 |
is_directory | 주어진 경로가 디렉토리인지 확인 | |
is_regular_file | 주어진 경로가 일반 파일인지 확인 | |
create_directory | 디렉토리 생성 | |
create_directories | 경로에 지정된 디렉토리나 중간 디렉토리 생성 | |
remove | 파일이나 디렉토리 삭제 | |
rename | 파일이나 디렉토리의 이름 변경 | |
copy | 파일이나 디렉토리 복사 | |
copy_file | 파일 복사 | |
copy_directory | 디렉토리 복사 | |
copy_symlink | 심볼릭 링크 복사 | |
file_size | 파일의 크기 반환 | |
last_write_time | 파일이나 디렉토리의 마지막 수정 시간 반환 | |
current_path | 현재 작업 디렉토리의 경로 반환 | |
equivalent | 두 경로가 같은 파일이나 디렉토리를 가리키는지 확인 | |
is_empty | 주어진 디렉토리가 비었는지 확인 | |
remove_all | 디렉토리와 하위 항목 모두 삭제 | |
resize_file | 파일 크기 변경 | |
status | 파일이나 디렉토리의 상태 정보 반환 | |
temp_directory_path | 임시 디렉토리의 경로 반환 |
이러한 기능들은 파일 시스템 조작을 더 편리하게 해주므로 필요할 때 찾아서 활용하면 더 빠르고 쉽게 프로그래밍할 수 있다.
파일 시스템 라이브러리에서 파일이나 디렉토리를 다루는 모든 함수는 path
객체를 매개변수로 받는다. 따라서 보통 다음의 순서로 작업한다.
path
정의path
로 파일/디렉토리 정보 수집한 가지 중요한 점은 path
객체만으로는 실제 해당 경로에 파일이 존재하는지 알 수 없다. path 클래스
는 그냥 경로를 나타낼 뿐 실제 파일을 지칭하지는 않기 때문이다.
만약, 해당 경로에 파일이 실제로 존재하는지 확인하려면 exists
함수를 사용해야 한다.
bool exists(const std::filesystem::path& p)
파일 시스템 라이브러리는 원래 boost
의 filesystem
을 C++17
표준에 병합한 것이다. 아직은 표준보다 boost
가 더 기능이 많고 광범위하게 사용되므로 std::filesystem
으로 사용하는 것은 boost::filesystem
으로 대체할 수 있다.
boost
를 사용할 때는 헤더 파일만 포함해도 되는 것이 많지만, boost::filesystem
은 사용하는 시스템에 맞게 컴파일된 라이브러리가 필요하다. std::filesystem
도 언급한 바와 같이 C++17
이상의 컴파일러에서만 사용할 수 있다.
다음 예제 코드는 파일 시스템 라이브러리를 사용하여 파일과 디렉토리를 생성, 쓰기, 읽기, 삭제까지 다르는 예제이다.
#include <iostream>
#include <string>
#include <filesystem> // 파일 시스템 헤더 파일
#include <fstream> // 파일 입출력 헤더 파일
using namespace std;
namespace fs = std::filesystem;
int main()
{
// 디렉토리 생성
fs::create_directories("MyDirectory");
// 파일 생성 및 쓰기
ofstream outFile("MyDirectory/MyFile.txt");
outFile << "Hello, World!" << endl;
outFile.close();
// 디렉토리 내의 파일 확인
cout << "Files in MyDirectory: " << endl;
for (const fs::directory_entry& entry : fs::directory_iterator("MyDirectory")) {
if (entry.is_regular_file()) {
cout << entry.path().filename() << endl;
}
}
// 파일 읽기
ifstream inFile("MyDirectory/MyFile.txt");
string line;
while (getline(inFile, line)) {
cout << line << endl;
}
inFile.close();
fs::remove_all("MyDirectory");
return 0;
}
실행 결과
Files in MyDirectory:
"MyFile.txt"
Hello, World!
create_directories
함수는 지정된 경로에 디렉토리를 생성한다. 필요하면 중간 단계의 디렉토리도 함께 생성한다. 예를 들어 path/to/MyDirectory
에 해당하는 디렉토리를 생성하려고 할 때 path
와 to
디렉토리가 없으면 새로 만들고, 이미 있어도 오류가 발생하지 않으므로 안전하게 사용할 수 있다.
fs::create_directories("path/to/MyDirectory");
Linux 명령어 중에
mkdir -p
와 같다.
ostream
을 사용해서 MyDirectory에 MyFile.txt
라는 파일을 생성하고, 이 파일을 열어서 Hello, World!
라는 문자열을 쓰고 닫는다.
ofstream outFile("MyDirectory/MyFile.txt");
outFile << "Hello, World!" << endl;
outFile.close();
ofstream (파일 출력 스트림)
을 사용하여 파일에 내용을 쓰고 난 후 close()
함수를 호출하는 이유는, 파일을 제대로 닫기 위해서이다.
파일을 열였을 때 메모리를 사용하게 되는데, 이 메모리를 정리하고 파일을 닫지 않으면 다른 부분에서 이 파일에 접근하지 못하거나 문제가 발생할 수 있다. 따라서 파일을 열었으면 작업 후에 항상 닫아줘야 한다.
C++
에서는 ofstream
의 소멸자가 파일을 자동으로 닫아 주긴 하지만, 명시적으로 close
함수를 호출하는 것이 코드를 해석하기 쉽게 하고, 파일을 여는 곳과 닫는 곳이 명확하게 구분되어 유지/보수
할 때도 좋다.
다음의 코드에서는 해당 디렉토리의 모든 파일 이름을 출력한다.
cout << "Files in MyDirectory: " << endl;
for (const fs:directory_entry& entry : fs::directory_iterator("MyDirectory")) {
if (entry.is_regular_file()) {
cout << entry.path().filename() << endl;
}
}
MyDirecotry 경로에 대한 디렉토리 반복자(directory_iterator)
를 생성한다. 이 반복자는 해당 디렉토리의 파일과 디렉토리 정보를 제공한다. 그리고 범위 기반 for문으로 반복자가 가리키는 모든 원소를 대상으로 반복한다. 여기서 entry
는 각 파일이나 디렉토리에 대한 참조를 나타낸다.
if 문에서는 현재 반복자가 가리키는 항목이 디렉토리가 아닌 파일인지 확인하고 현재 파일의 경로에서 파일 이름만 출력한다. entry.path
함수는 파일의 전체 경로를 나타내며, filename
함수는 파일 이름만 추출한다.
다음 코드는 ifstream
을 사용하여 파일을 열고 각 줄을 읽어 그 내용을 화면에 출력한다. 그리고 파일을 닫는다.
ifstream inFile("MyDirectory/MyFile.txt");
string line;
while (getline(inFile, line)) {
cout << line << endl;
}
inFile.close();
MyDirectory/MyFile.txt 파일을 읽기 모드
로 연다. 이때 ifstream
은 파일을 읽기 위한 입력 스트림
을 제공한다. 그리고 getline
함수로 파일에서 한 줄씩 읽어 line
에 저장한 후 화면에 출력하는 동작을 파일의 끝에 도달할 때까지 반복한다.
getline
함수는 첫 번째 인자로 전달한 스트림
에서 한 줄을 읽어 두 번째로 전달한 인자에 저장하는데, 스트림
에서 더 이상 읽을 내용이 없으면 false
를 반환한다.
마지막으로 close()
함수를 호출하여 파일을 닫는다. 파일을 닫음으로써 파일 자원을 해제하고 접근을 종료한다.
다음 코드는 remove_all
함수를 사용하여 디렉토리를 삭제한다. 즉, MyDirectory와 그 안에 있는 파일, 하위 디렉토리까지 모두 삭제한다.
fs::remove_all("MyDirectory");
remove_all
함수는 지정한 디렉토리와 그 안에 포함된 모든 요소를 삭제한다. 만약 디렉토리 안에 하위 디렉토리나 파일이 있더라도 재귀
하는 방법으로 모두 삭제한다.
remove_all
함수는 디렉토리와 파일을 완전히 삭제하므로 조심해서 사용해야 한다.