리눅스

과제 LS

정지홍 2023. 4. 9. 21:51
/*ls명령어 구현*/
//LS 
//LS -l 
//LS -s size 
//LS -l -s size
//LS -ls size 
//LS -F 
//LS -d -l 
//LS -R 
//LS -r
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
void printName(DIR* dp, int size, int a, int showDetail);//차례대로 DIR 포인터, 사이즈(만약 LS -s, LS -l -s , LS -ls의 명령어인 경우 사이즈를 넣어서 전달합니다. 이외의 명령어는 -1을 넣어줍니다.)
//showDetail은 파일이름뿐만 아니라 파일의 접근권한,링크,유저아이디,그룹아이디,사이즈,최종수정시간도 같이 출력해줍니다. (1인 경우에만 해당됩니다. 0인경우는 이름만 출력합니다.)
//int a는 정렬할지 안할지를 알려주는 변수입니다. 0인 경우는 정렬을 하지않으며 1인경우에만 정렬하여 출력해줍니다.
void printD(DIR* dp , int traversal);//순서대로 DIR파일포인터, 모든 파일을 출력할지 안할지 알려주는 변수입니다. 
//int traversal값이 1인 경우 현재 디렉토리에 하위 디렉토리 존재시 chdir하여 출력하는데 또 안에 하위 디렉토리 존재시 계속 순회합니다.
void reversePrint(DIR* dp);//현재 디렉토리의 내용을 알파벳 역순으로 출력하는 함수입니다.
int main(int argc, char* argv[]) {
	DIR* dp;	//열린 디렉토리에 관한 정보를 가진 구조체인 DIR포인터를 선언합니다.
	struct dirent* dent;//dirent는 디렉토리에 있는 항목의 정보를 담은 구조체이며 이를 dent로 선언합니다.
	int n;

	if ((dp = opendir(".")) == NULL) {//디렉토리를 오픈합니다.
		perror("opendir ");//오픈 에러 발생시 에러임을 알려줍니다.
		exit(1);//프로그램을 종료합니다.
	}


	if (argc == 1) {//전달받은 인자가 1개인 경우입니다.(LS명령어)
		if (strcmp(argv[0], "LS") == 0) {//전달받은 인자가 LS인 경우에만 실행합니다.
			printName(dp, -1, 0,0);//현재 디렉토리의 항목을 보여줍니다.(DIR포인터 , 사이즈 , 정렬여부를 결정하는 변수 ,자세히 출력할지 안할지를 나타내는 변수)
		}
	}
	else {
		while ((n = getopt(argc, argv, "ls:FdRr")) != -1) {//argv에 -l, -s , -F , -d , -R ,r존재시 반복합니다. s는 인자를 받아서 optarg에 저장합니다.


			if (argc == 2) {//전달받은 인자가 2개인 경우 입니다,
				switch (n) {
				case 'l'://LS -l명령어입니다.. (r:4 , w:2 , x:1)
					printName(dp, -1, 0, 1);//현재 디렉트리의 항목을 파일 사이즈에 관계없이 출력합니다.(size=-1) 정렬을 하지 않습니다.(a=0) 자세히 출력합니다.(showDetail=1) 
					break;
				case 'F'://LS -F명령어입니다.
					printD(dp,0);
					break;
				case 'R'://LS -R명령어입니다,
					printD(dp, 1);
					break;
				case 'r'://LS -r명령어입니다.
					reversePrint(dp);//현재 디렉토리의 내용을 알파벳 역순으로 출력합니다.
					break;
				}

			}

			if (argc == 3) {//전달받은 인자가 3개인 경우입니다.
				switch (n) {
				case 's'://LS -s size명령어
					int x = atoi(optarg);//-s옵션의 인자값을 int로 바꾸어서 저장합니다.
					
					if (strcmp(argv[1], "-ls") == 0) {//LS -ls size명령어인경우입니다.
						printName(dp, x, 1, 1);//전달받은 인자를 atoi로 int로 바꾸었습니다. 이 값을 함수의 int size에 넣어줍니다.
						//함수에서 x값 이상의 파일들만 출력해줍니다. a=1이고 showDetail=1이니 정렬하고 자세히 출력합니다.
					}
					else
						printName(dp, x, 1,0);//LS -s size명령어인 경우입니다. 위의 LS -ls size명령어와 다르게 showDetail=0이여서 정렬된 순서로 이름만 출력해줍니다.
					break;
				case 'd':
					if ((strcmp(argv[2] , "-l") == 0) && optarg == NULL) {//LS -d -l명령어인 경우입니다.
						printD(dp,0);
					}
					break;
				}



			}

			if (argc == 4) {//LS -l -s size 명령어인 경우입니다.	
				if (strcmp(argv[1], "-l") == 0) {//전달받은 옵션이 LS 다음에 -l인 경우입니다.
					if (strcmp(argv[2], "-s") == 0) {//-l 다음에 -s를 전달받은 경우입니다.
						switch (n) {
						case 's':
							int x = atoi(optarg);//-s옵션의 인자값을 int로 바꾸어서 저장합니다.
							printName(dp, x, 1, 1);//전달받은 인자를 atoi로 int로 바꾸었습니다. 이 값을 함수의 int size에 넣어줍니다. 사이즈가 x이상인 파일만 출력하며 정렬 및 자세히 출력합니다.
							break;
						}
					}
				}

			}
		}
	}
	closedir(dp);//DIR포인터가 가르키는 디렉토리를 닫습니다.
	return 0;
}

void printName(DIR* dp, int size, int a, int showDetail) {

	struct dirent* dent;//dirent는 디렉토리에 있는 항목의 정보를 담은 구조체이며 이를 dent로 선언합니다.

	struct stat sbuf;//파일에 대한 정보를 가지고 있는 stat구조체를 선언합니다.
	int inode_size[100][2];//inode번호와 사이즈를 저장하는 배열입니다. 정렬을 위해서 사용하는 배열입니다.
	int cnt = 0;//정렬에 사용할 배열에 몇개의 요소가 들어갔는지 카운트하는 변수입니다.

	while ((dent = readdir(dp))) {//readdir함수를 이용하여 차례대로 디렉토리 정보를 읽어옵니다.

		stat(dent->d_name, &sbuf);//현재 dent가 가르키는 디렉토리안의 항목의 이름을 가져옵니다.(while문 안이니 차례대로 불러옵니다). 그리고 이 파일명과 일치하는 파일을 찾아서 stat구조체에 저장합니다. 

		if (dent->d_name[0] == '.')continue;//가르키는 항목의 이름이 .으로 시작하면 다음 반복으로 실행합니다.


		if ((int)sbuf.st_size < size) {//현재가르키는 파일의 사이즈가 전달받은 사이즈(int size)미만인 경우 해당 항목은 출력하지않고 다음 반복으로 실행합니다.
			continue;
		}
		else {
			inode_size[cnt][0] = (unsigned int)sbuf.st_ino;//현재 가르키는 항목의 inode값을 첫번째 열에 저장합니다
			inode_size[cnt][1] = (int)sbuf.st_size;//현재 가르키는 항목의 사이즈를 두번째 열에 저장합니다.
			//위 과정을 통해서 [ [inode,size] , [inode,size] ,[inode,size].....[inode,size] ]의 2차원 배열이 저장됩니다.
			cnt += 1;//현재 요소가 몇개 들어갔는지 카운트하는 변수에 1을 더합니다.
		}

		if (a == 0) {//a==0인 경우는 정렬을 하지 않고 출력함을 나타냅니다.
			
			if (showDetail == 1) {//LS -l size , LS -ls size , LS -l -s size명령어인 경우 입니다.
				//현재 stat구조체에 저장된 정보를 차례로 출력합니다.
				printf("mode:%o ", (unsigned int)sbuf.st_mode);//접근권한을 출력합니다.
				printf("link:%d  ", (unsigned int)sbuf.st_nlink);//링크개수를 출력합니다.
				printf("uid:%d  ", (int)sbuf.st_uid);//유저 아이디를 출력합니다
				printf("gid:%d  ", (int)sbuf.st_gid);//그룹 아이디를 출력합니다.
				printf("size:%d  ", (int)sbuf.st_size);//파일의 사이즈를 출력합니다.
				printf("time:%d  ", (int)sbuf.st_ctime);//최종 수정시간을 출력합니다.
			}
			printf("Name : %s\n", dent->d_name);//이름을 출력합니다.
		}


	}

	if (a == 1) {//a==1인 경우는 정렬 후 출력함을 나타냅니다.
		for (int i = 0; i < cnt; i++) {
			for (int j = i + 1; j < cnt; j++) {
				if (inode_size[i][1] < inode_size[j][1]) {//만약 다음 행에 저장되어있는 항목의 사이즈가 큰 경우에 실행합니다.
					//현재 행과 다음행의 값을 바꾸는 정렬을 실행합니다.
					int tmpA = inode_size[i][1];
					inode_size[i][1] = inode_size[j][1];
					inode_size[j][1] = tmpA;
					//inode의 값을 현재 행과 다음행의 위치를 바꾸어줍니다.
					int tmpB = inode_size[i][0];
					inode_size[i][0] = inode_size[j][0];
					inode_size[j][0] = tmpB;
				}
			}
		}
	
		for (int i = 0; i < cnt; i++) {//2차원 배열에 저장된 요소의 갯수만큼 반복을 실행합니다.
			rewinddir(dp);//출력을 위해서 현재 열었는 디렉토리의 오프셋을 제일 처음으로 감아줍니다.
			while ((dent = readdir(dp))) {//위에서 디렉토리를 다시 처음으로 이동시켰습니다. 그리고 정렬된 순서대로 출력을 위해 다시 앞에서부터 읽습니다.

\
				if (((int)dent->d_ino) == inode_size[i][0]) {//현재 dirent에 저장된 inode와 정렬되어있는 배열의 첫번째 열의 값(inode번호)과 같은 경우에 실행됩니다.
					if (showDetail == 1) {//자세히 출력할지 안할지 결정하는 변수의 값이 1인 경우에 해당 조건문으로 들어갑니다.
						stat(dent->d_name, &sbuf);//dent->d_name가 가르키는 파일명과 일치하는 파일을 찾아서 stat구조체에 저장합니다. 
						printf("mode:%o ", (unsigned int)sbuf.st_mode);
						printf("link:%d  ", (unsigned int)sbuf.st_nlink);
						printf("uid:%d  ", (int)sbuf.st_uid);
						printf("gid:%d  ", (int)sbuf.st_gid);
						printf("size:%d  ", (int)sbuf.st_size);
						printf("time:%d  ", (int)sbuf.st_ctime);
					}
					
					printf("Name : %s\n", dent->d_name);
				}
			}
		}
	}
}

void printD(DIR* dp, int traversal) {
	
	struct dirent *dent;//dirent는 디렉토리에 있는 항목의 정보를 담은 구조체이며 이를 dent로 선언합니다.
	struct stat sbuf;//파일에 대한 정보를 가지고 있는 stat구조체를 선언합니다.
	int kind;//파일의 종류를 저장하는 변수입니다.
	char* cwd;//디렉토리 위치를 저장하는 변수입니다.

	while ((dent = readdir(dp))) {//readdir함수를 이용하여 차례대로 디렉토리 정보를 읽어옵니다.
		
		if (dent->d_name[0] == '.')continue;//가르키는 항목의 이름이 .으로 시작하면 다음 반복으로 실행합니다.
		
		stat(dent->d_name, &sbuf);//현재 dent가 가르키는 디렉토리안의 항목의 이름을 가져옵니다.그리고 이 파일명과 일치하는 파일을 찾아서 stat구조체에 저장합니다. 
		
		kind = sbuf.st_mode & S_IFMT;//AND연산을 하여 파일 종류를 확인합니다.
		
		
		if (kind == S_IFDIR && traversal==0) {//LS -F명령어에서 현재 가르키는 항목이 디렉토리인 경우입니다.(LS -d -l)
			//현재 가르키는 항목이 디렉토리이니 이름 다음에 '/'를 같이 출력합니다.
			printf("%s", dent->d_name);
			printf("/");
			printf("\n");
			continue;
		}
		
		else if ( kind != S_IFDIR) {//LS -F명령어에서 현재 가르키는 항목이 디렉토리가 아닌 경우입니다.(LS -d -l)
			//아니면 LS -R명령어에서 디렉토리가 아닌 경우 입니다.
			
			if (traversal == 1) {//LS -R명령어인 경우에만 실행합니다.
				cwd = getcwd(NULL, 256);//현재 디렉토리의 위치를 가져와서 cwd에 저장합니다.
				printf("%s/", cwd);//현재 파일이 존재하는 디렉토리 위치를 출력해줍니다.
			}
			printf("%s ", dent->d_name);//이름을 출력해줍니다.
			printf("\n");
			continue;
		}

		else {//LS -R 명령어

			cwd = getcwd(NULL, 256);//현재 디렉토리의 위치를 가져와서 cwd에 저장합니다.


			if (kind == S_IFDIR) {//현재 stat이 가르키는 항목이 디렉토리인 경우입니다. 
	
				char preDir[1024] = "";//기존의 디렉토리위치를 저장하는 변수입니다.
				char nextDir[1024]="";//이동할 경로를 저장하는 변수입니다.

				strcat(preDir,cwd);//현재 위치를 저장합니다.

				strcat(nextDir, cwd);//현재 경로를 저장합니다.
				strcat(nextDir,"/");//이동을 위해서 '/'를 문자열에 붙혀줍니다.
				strcat(nextDir, dent->d_name);//이동을 위해서 현재 가르키는 항목의 이름을 붙혀줍니다.
				//위의 과정을 통해서 이동할 path가 만들어집니다.
				printf("%s\n", nextDir);//현재 가르키는 항목의 위치와 이름이며 이동할 path를 의미하기도 하는 nextDir을 출력합니다.

				if (chdir(nextDir) == 0) {//디렉토리 변경를 변경합니다.

					DIR* newdp;//이동한 디렉토리에서 새로운 DIR포인터를 선언합니다.
					
					if ((newdp = opendir(".")) == NULL) {//디렉토리를 오픈하는데 오류 발생시 널을 리턴합니다.
						perror("opendir ");//오류가 발생하면 오류가 발생함을 알려줍니다.
						exit(1);//프로그램을 종료합니다.
					}

					cwd = getcwd(NULL, 256);//현재 디렉토리의 위치를 저장합니다.
					
					printD(newdp, 1);//다시 printD함수를 호출 합니다.
					//다시 호출한 함수안에 디렉토리 존재시 또 디렉토리 안으로 들어가며 또 존재하면 계속 들어가는 과정이 반복됩니다.
					//들어간 디렉토리 안에 더이상 디렉토리가 없으면 존재하는 다른 파일들의 이름과 경로를 함께 출력합니다.(빈 디렉토리인 경우는 출력하는 것이 없습니다.)
					//그리고 재귀호출에서 돌아와서 또 현재 디렉토리에서 디렉토리 존재하는지 확인하고 또 존재시 위의 과정이 반복됩니다.
					//다음 과정을 통해서 계속 순회하며 현재 디렉토리 뿐만아니라 하위 디렉토리의 파일까지 모두 출력합니다.
					closedir(newdp);//열었던 디렉토리를 닫아줍니다.
					if (chdir(preDir) == -1) {//디렉토리 이동 실패시 -1을 리턴 받습니다.
						printf("preDir chdir error \n");//디렉토리 이동 오류임을 알려줍니다.
						exit(1);//프로그램을 종료합니다.
					}
				};	
			}
		}
	}
}

void reversePrint(DIR* dp) {

	struct dirent* dent;//dirent는 디렉토리에 있는 항목의 정보를 담은 구조체이며 이를 dent로 선언합니다.
	
	int* ino = malloc(1 * sizeof(int));//요소 1개만 들어있는 배열을 동적으로 할당합니다.
	int idx = 0;//위의 배열의 요소 개수를 카운트하는 변수입니다.

	while ((dent = readdir(dp))) {//readdir함수를 이용하여 차례대로 디렉토리 정보를 읽어옵니다.

		if (dent->d_name[0] == '.')continue;//가르키는 항목의 이름이 .으로 시작하면 다음 반복으로 실행합니다.
	
		ino = realloc(ino, (idx + 1) * sizeof(int));//realloc으로 배열의 크기를 증가시켜줍니다, 각각 가르키는 항목의 inode를 저장해야하니 idx+1를 해줍니다.
		ino[idx] = (int)dent->d_ino;//현재 항목의 inode번호를 저장합니다.
		idx += 1;//배열요소 개수를 카운트하는 변수를 1더해줍니다.
	}

	rewinddir(dp);//다음에 이름을 저장하기 위해 포인터를 제일 앞으로 감아줍니다.

	//char name [idx][128]의 동적할당입니다.
	char** name = (char**)malloc(idx*sizeof(char*));//위에 int배열의 요소 개수만큼의 행을 가지는 2차원배열을 동적으로 할당합니다.
	for (int i = 0; i < idx; i++) {//열에 대한 할당입니다.
		name[i] = (char*)malloc(128 * sizeof(char));//열 사이즈를 128으로 할당합니다. char name [idx][128]의 배열이 동적으로 할당합니다.
	}

	while ((dent = readdir(dp))) {//readdir함수를 이용하여 차례대로 디렉토리 정보를 읽어옵니다.

		if (dent->d_name[0] == '.')continue;//가르키는 항목의 이름이 .으로 시작하면 다음 반복으로 실행합니다.
		for (int i = 0; i < idx; i++) {//요소 개수만큼 반복합니다.
			if (ino[i] == (int)dent->d_ino) {//현재 항목의 inode와 int ino배열의 i번째 값이 같은 경우 실행합니다. 
				name[i] = dent->d_name;//i번째 위치에 name배열에 이름을 저장합니다.
				
			}
			
		}
	}

	for (int i = 0; i < idx; i++) {//현재 요소와 다음요소의 파일이름을 비교합니다.
		for (int j = i + 1; j < idx; j++) {
			if (strcmp(name[i], name[j]) <= 0) {//현재 인덱스가 아닌 다음에 저장된 이름이 더 큰 경우(아스키코드 기준) -1을 리턴받아 조건문에 들어감니다.
			//항목 이름이 저장된 배열에서 각각의 위치를 바꾸어줍니다.
				char tmp[1024];
				strcpy(tmp, name[i]);
				strcpy(name[i], name[j]);
				strcpy(name[j], tmp);
			}

		}
	}
	for (int i = 0; i < idx; i++) {
		printf("%s\n", name[i]);//알파벳 역순으로 정렬된 배열을 출력합니다.
	}

	free(name);//메모리를 해제해줍니다.
	free(ino);//메모리를 해제해줍니다,
}

'리눅스' 카테고리의 다른 글

sol4  (0) 2023.04.16
sol3  (0) 2023.04.11
sol2  (0) 2023.04.05
sol1  (1) 2023.04.03
1차 과제 완료  (0) 2023.03.29