본문 바로가기
개발/C, C++

C, C++ - Window Visual Studio에서 폴더의 모든 파일 통합하기

by 피로물든딸기 2022. 8. 9.
반응형

C, C++ 전체 링크

 

참고

- 폴더, 파일 관리 함수 정리 with sys/stat.h, dirent.h, fstream

 

isDirectory - 폴더 / 파일 체크 함수 

getAllFilePath - 해당 경로를 모두 읽어오는 함수

deleteDirectoryFiles - 폴더, 파일 삭제 함수

deleteAllDirectoryFiles - 모든 폴더, 파일 삭제 함수

fileCopy - 파일 복사 함수

numOfDigits - 자릿수를 세는 함수

padStart - 자릿수의 빈칸을 앞부분부터 특정 문자로 채우는 함수

split - 특정 문자열 기준으로 분해하는 함수

getExtension - 파일의 확장자를 얻는 함수


아래와 같이 MyFiles 폴더가 있다고 하자.

 

여기에 있는 파일들을 한꺼번에 하나로 통합하고 싶다.

직접 복사 / 붙여넣기를 해도 되지만,

폴더에 있는 파일들이 중복된 이름이 많은 경우에는 일일이 이름을 바꿔야 한다.

 

예를 들어 MyFiles의 Folder1에 a, b, z 파일이 있고, 또 그 안에 하위 폴더에도 같은 이름의 파일이 있다.

 

하위 폴더의 하위 폴더에도 a, b, z 파일이 있어 하나로 통합하기 번거롭다.

 

해당 폴더는 아래와 같다.

MyFiles.zip
0.00MB

 

MyFiles - Folder1, 2, 3 / a, b, z

Folder1 - a, b, z

Folder1 - Folder1-1 - a, b, z

Folder1 - Folder1-2 - a, b, c

Folder2 - a, b, c

Folder3 - a, b

 

여기에 있는 모든 파일 a, b, c, z(= 17개)를 아래와 같이 Result 폴더에 합치자.

 

순서는 폴더 / 파일 구분 없이 이름 순으로 찾게 된다.

먼저 해당 폴더의 폴더 / 파일을 모두 훑은 후, 다시 폴더를 탐색한다.

따라서 번호가 매겨지는 순서는 아래와 같은 순서가 된다.

 

MyFiles - Folder1, 2, 3 / a(00), b(01), z(02)

Folder1 - a(03), b(04), z(05)

Folder1 - Folder1-1 - a(06), b(07), z(08)

Folder1 - Folder1-2 - a(09), b(10), c(11)

Folder2 - a(12), b(13), c(14)

Folder3 - a(15), b(16)

 

위의 첨부 파일에 위의 순서대로 파일에 번호를 체크해뒀으니, Merge한 후에 순서가 맞는지 확인해보자.


필요한 함수를 하나씩 살펴보자.

 

isDirectory - 폴더 / 파일 체크 함수

 

전체 경로를 주면 파일인지 폴더인지 검사한다. 

#define ERROR (-1)
#define WINDOW_FILE (0)
#define DIRECTORY (1)

int isDirectory(string fullPath)
{
	int ret;
	_finddatai64_t fileData;
	const char* fp = fullPath.c_str();

	if (_findfirsti64(fp, &fileData) == ERROR) ret = ERROR;
	else
	{
		if (fileData.attrib & _A_SUBDIR) ret = DIRECTORY;
		else ret = WINDOW_FILE;
	}

	_findclose(ret);

	return ret;
}

 

vs를 출력하면 아래의 결과를 얻을 수 있다.

해당 폴더의 파일 / 폴더를 사전 순으로 먼저 탐색 후, 사전 순으로 먼저인 폴더를 탐색한다.


getAllFilePath - 해당 경로를 모두 읽어오는 함수

 

경로를 넘겨주면 해당 경로에 존재하는 모든 파일 / 폴더의 경로를 vs에 저장한다.

순서는 위에 정의한 대로 폴더 → 폴더 내부의 폴더 → ... → 파일 순으로 경로가 저장된다.

void getAllFilePath(string path, vector<string>& vs)
{
	FILE* pipe = NULL;
	string prompt = "dir /s /b " + path;
	char buf[500];

	pipe = _popen(prompt.c_str(), "rt");

	if (pipe == NULL)
	{
		printf("%Pipe is Null\n");
		return;
	}

	while (!feof(pipe))
	{
		if (fgets(buf, 500, pipe) != NULL)
		{
			int len = 0;
			for (; buf[len++];);
			if (buf[len - 2] == 10 /* Line Feed */) buf[len - 2] = 0;

			vs.push_back(string(buf));
		}
	}

	_pclose(pipe);
}

deleteDirectoryFiles - 폴더, 파일 삭제 함수

deleteAllDirectoryFiles - 모든 폴더, 파일 삭제 함수

void deleteDirectoryFiles(string path)
{
	if (isDirectory(path) == DIRECTORY) _rmdir(path.c_str());
	else if (isDirectory(path) == WINDOW_FILE) _unlink(path.c_str());
}

void deleteAllDirectoryFiles(string path)
{
	if (isDirectory(path) != DIRECTORY) return;

	vector<string> allPath;
	vector<string>::iterator inverse, iter;

	getAllFilePath(path, allPath);

	if (allPath.size())
	{
		for (inverse = allPath.begin(); inverse != allPath.end(); inverse++);
		--inverse;

		for (iter = inverse; iter != allPath.begin(); iter--) deleteDirectoryFiles(*iter);
		deleteDirectoryFiles(*iter);
	}

	_rmdir(path.c_str());
}

fileCopy - 파일 복사 함수

 

src 파일을 dest에 복사한다.

int fileCopy(const char* src, const char* dest)
{
	FILE* fsrc;
	FILE* fdest;
	char buff[1024];
	size_t n_size;

	if (!strcmp(src, dest)) return -1; // 원본 사본 파일이 동일하면 에러
	
	fsrc = fopen(src, "rb"); //원본 파일 열기
	if (fsrc == NULL) return -2;

	fdest = fopen(dest, "wb"); //대상 파일 열기
	if (fdest == NULL)
	{
		fclose(fsrc);
		return -3;
	}

	while (0 < (n_size = fread(buff, 1, sizeof(buff), fsrc)))
	{
		if (0 == fwrite(buff, 1, n_size, fdest))
		{
			fclose(fsrc);
			fclose(fdest);
			unlink(dest); //에러 파일 지우고 종료
			return -4;
		}
	}

	fclose(fsrc);
	fclose(fdest);
		
	return 0;
}

numOfDigits - 자릿수를 세는 함수

 

파일의 번호를 붙이기 위해 필요하다. 

만약 파일이 123개라면

001 ~ 123이 되어야 하므로 자릿수가 3이라는 것을 알아야 한다.

int numOfDigits(int number)
{
	int ret = 0;
	while (number)
	{
		ret++;
		number /= 10;
	}

	return ret;
}

padStart - 자릿수의 빈칸을 앞부분부터 특정 문자로 채우는 함수

 

파일이 123개라면 파일이름이 000, 001, 002, ...로 붙는다. pad = '0'을 주면 자릿수만큼 0을 채운다.

string padStart(int number, int padSize, char pad)
{
	string strNum = to_string(number);
	string ret = "";

	for (int i = 0; i < padSize - strNum.size(); i++) ret = ret + pad;

	return ret + strNum;
}

split - 특정 문자열 기준으로 분해하는 함수

링크 참고.


getExtension - 파일의 확장자를 얻는 함수

 

a.txt라면 txt를 리턴한다. 즉, 확장자를 리턴한다.

string getExtension(string str)
{
	vector<string> vs = split(str, '.');
	return vs[vs.size() - 1];
}

main 분석

 

위의 함수들을 이용하여

 

1. RESULT_PATH(결과를 저장할 경로)를 모두 삭제(안에 있는 파일을 삭제)하고, 다시 새로 만든다.

2. PATH(파일을 복사할 루트 폴더)에서 모든 파일 / 폴더의 경로를 files에 저장한다.

3. isDirectory로 파일만 센다.

4. 파일의 개수를 바탕으로 몇 자리 수인지, pad를 얼마나 채울지 정한다.

5. 파일인 경우 순서대로 복사한다. (destPath에 파일을 복사하고, changeName으로 변경한다.

int main()
{
	cout << "Start" << endl;

	deleteAllDirectoryFiles(RESULT_PATH);
	_mkdir(RESULT_PATH);

	vector<string> files;

	getAllFilePath(PATH, files);

	vector<string>::iterator it = files.begin();

	int fileCount = 0;
	while (it != files.end())
	{
		if (isDirectory(*it) == WINDOW_FILE) fileCount++;
		it++;
	}

	it = files.begin();
	int padSize = numOfDigits(fileCount);
	int fileIndex = 0;
	while (it != files.end())
	{
		if (isDirectory(*it) == WINDOW_FILE)
		{
			cout << *it << endl;

			vector<string> vs = split(*it, '\\');
			string fileName = vs[vs.size() - 1];
			string destPath = string(RESULT_PATH) + "\\" + fileName;

			fileCopy(it->c_str(), destPath.c_str());

			string changeName 
				= string(RESULT_PATH) + 
				"\\file_" + padStart(fileIndex++, padSize, '0') + "." + getExtension(fileName);

			rename(destPath.c_str(), changeName.c_str());
		}

		it++;
	}

	return 0;
}

 

아래의 define에 원하는 폴더 경로를 넣어두자.

#define PATH "C:\\Users\\username\\Desktop\\MyFiles"
#define RESULT_PATH "C:\\Users\\username\\Desktop\\Result"

 

그러면 MyFiles모든 파일Result에 순서대로 저장된다.

 

이러한 방식으로 원하는 파일이 이름은 엉망이지만 순서는 지켜져있는 경우(저장된 사진을 옮긴다거나 할 때)

파일의 이름을 정리하고 라벨링을 할 수 있다.

 

아래는 오늘 하루 찍은 사진 파일을 MyFiles 폴더에 옮긴 것이다.

숫자가 엉망이라서 정리하기 힘들 때 순서대로 라벨링을 붙여주었다.

 

changeName을 변경하면 원하는 파일 이름을 설정할 수 있다.

string changeName 
	= string(RESULT_PATH) + 
	"\\file_" + padStart(fileIndex++, padSize, '0') + "." + getExtension(fileName);

 

전체 코드는 다음과 같다.

#include <string>
#include <vector>
#include <iostream>
#include <io.h>
#include <direct.h>

using namespace std;

#define PATH "C:\\Users\\username\\Desktop\\MyFiles"
#define RESULT_PATH "C:\\Users\\username\\Desktop\\Result"

#define ERROR (-1)
#define WINDOW_FILE (0)
#define DIRECTORY (1)

int isDirectory(string fullPath)
{
	int ret;
	_finddatai64_t fileData;
	const char* fp = fullPath.c_str();

	if (_findfirsti64(fp, &fileData) == ERROR) ret = ERROR;
	else
	{
		if (fileData.attrib & _A_SUBDIR) ret = DIRECTORY;
		else ret = WINDOW_FILE;
	}

	_findclose(ret);

	return ret;
}

void getAllFilePath(string path, vector<string>& vs)
{
	FILE* pipe = NULL;
	string prompt = "dir /s /b " + path;
	char buf[500];

	pipe = _popen(prompt.c_str(), "rt");

	if (pipe == NULL)
	{
		printf("%Pipe is Null\n");
		return;
	}

	while (!feof(pipe))
	{
		if (fgets(buf, 500, pipe) != NULL)
		{
			int len = 0;
			for (; buf[len++];);
			if (buf[len - 2] == 10 /* Line Feed */) buf[len - 2] = 0;

			vs.push_back(string(buf));
		}
	}

	_pclose(pipe);
}

void deleteDirectoryFiles(string path)
{
	if (isDirectory(path) == DIRECTORY) _rmdir(path.c_str());
	else if (isDirectory(path) == WINDOW_FILE) _unlink(path.c_str());
}

void deleteAllDirectoryFiles(string path)
{
	if (isDirectory(path) != DIRECTORY) return;

	vector<string> allPath;
	vector<string>::iterator inverse, iter;

	getAllFilePath(path, allPath);

	if (allPath.size())
	{
		for (inverse = allPath.begin(); inverse != allPath.end(); inverse++);
		--inverse;

		for (iter = inverse; iter != allPath.begin(); iter--) deleteDirectoryFiles(*iter);
		deleteDirectoryFiles(*iter);
	}

	_rmdir(path.c_str());
}

//이름 변경
int fileCopy(const char* src, const char* dest)
{
	FILE* fsrc;
	FILE* fdest;
	char buff[1024];
	size_t n_size;

	if (!strcmp(src, dest)) return -1; // 원본 사본 파일이 동일하면 에러
	
	fsrc = fopen(src, "rb"); //원본 파일 열기
	if (fsrc == NULL) return -2;

	fdest = fopen(dest, "wb"); //대상 파일 열기
	if (fdest == NULL)
	{
		fclose(fsrc);
		return -3;
	}

	while (0 < (n_size = fread(buff, 1, sizeof(buff), fsrc)))
	{
		if (0 == fwrite(buff, 1, n_size, fdest))
		{
			fclose(fsrc);
			fclose(fdest);
			unlink(dest); //에러 파일 지우고 종료
			return -4;
		}
	}

	fclose(fsrc);
	fclose(fdest);
		
	return 0;
}

int numOfDigits(int number)
{
	int ret = 0;
	while (number)
	{
		ret++;
		number /= 10;
	}

	return ret;
}

string padStart(int number, int padSize, char pad)
{
	string strNum = to_string(number);
	string ret = "";

	for (int i = 0; i < padSize - strNum.size(); i++) ret = ret + pad;

	return ret + strNum;
}

vector<string> split(string str, char delimiter)
{
	vector<string> vs;
	string temp;

	for (int i = 0; !(str[i] == 13 /* Line Feed */ || str[i] == '\n' || str[i] == 0); i++)
	{
		if (str[i] == delimiter)
		{
			vs.push_back(temp);
			temp.clear();

			continue;
		}

		temp.push_back(str[i]);
	}

	vs.push_back(temp);

	return vs;
}

string getExtension(string str)
{
	vector<string> vs = split(str, '.');
	return vs[vs.size() - 1];
}

int main()
{
	cout << "Start" << endl;

	deleteAllDirectoryFiles(RESULT_PATH);
	_mkdir(RESULT_PATH);

	vector<string> files;

	getAllFilePath(PATH, files);

	vector<string>::iterator it = files.begin();

	int fileCount = 0;
	while (it != files.end())
	{
		if (isDirectory(*it) == WINDOW_FILE) fileCount++;
		it++;
	}

	it = files.begin();
	int padSize = numOfDigits(fileCount);
	int fileIndex = 0;
	while (it != files.end())
	{
		if (isDirectory(*it) == WINDOW_FILE)
		{
			cout << *it << endl;

			vector<string> vs = split(*it, '\\');
			string fileName = vs[vs.size() - 1];
			string destPath = string(RESULT_PATH) + "\\" + fileName;

			fileCopy(it->c_str(), destPath.c_str());

			string changeName 
				= string(RESULT_PATH) + 
				"\\file_" + padStart(fileIndex++, padSize, '0') + "." + getExtension(fileName);

			rename(destPath.c_str(), changeName.c_str());
		}

		it++;
	}

	return 0;
}

 

참고 : 유니티 C# version

반응형

댓글