본문 바로가기

개발👩‍💻/시스템프로그래밍

예제 프로그래밍: myrmdir, myls(9-2)

728x90

myrmdir

해당 디렉토리 포함해서 모든 디렉토리와 파일을 삭제하는 프로그래밍

+ 파일이 주어지면 디렉토리가 아니기 때문에 아무것도 삭제하지 않는다.

 

  1. 주어진 인자가 디렉토리인지 파일인지 확인한다
    stat()을 호출하여 S_ISDIR() 메르토 사용하여 해당 인자가 디렉토리인지 확인한다
    디렉토리인 경우 removeDir()을 호출한다.
  2. removeDir
    opendir(), readdir(), closedir()을 호출한다.
    readdir을 호출하여 해당 디렉토리에 있는 파일 및 디렉토리들을 확인한다.
    그리고 읽어들인 엔트리가 디렉토리인 경우 다시 removeDir을 호출하여 삭제한다.
    읽어들인 엔트리가 파일인 경우 unlink를 사용해서 해당 파일을 삭제한다.
  3. 디렉토리 내에 모든 파일/디렉토리가 삭제되면 rmdir을 호출해서 빈디렉토리를 삭제한다.

모든 디렉토리 내에 엔트리를 삭제한다고 하지만, . 와 ..는 삭제하면 안된다.

이 둘에 해당할 경우에는 그냥 스킵하면 된다.

 

엔트리가 디렉토리인 경우에는 하위 디렉토리를 chdir을 호출하여 현재디렉토리를 변경하여 removeDir을 호출한다.

-> 엔트리 내의 이름은 결대 경로가 아니라 이름만 나와 있다. 그렇기 때문에

하위 디렉토리에서 파일을 읽어와 삭제하려면, 현재 디렉토리에는 하위 디렉토리에 있는 파일이 없기 때문에 에러가 발생한다.

그 이름과 똑같은 파일이 있다면 그 파일이 삭제되긴 하지만 원하는 파일 삭제는 이뤄지지 않는다.

 

프로세스

1. 인자에 해당하는 디렉토리(파일)에 정상적으로 접근했는지 확인

struct stat buf;
if (stat(argv[1], &buf) < 0) exit(-1);
// stat을 통해 S_ISDIR로 디렉토리 확인

2. 인자의 개수가 정확히 전달됐는지 확인

If (argc < 2) exit(-1);

3. 디렉토리면 removeDir()실행, 파일이면 error 발생

if(S_ISDIR(buf.st_mode)) removeDir(dirent->d_name);
else exit(-1);

4. removeDir() 실행

void removeDir(char *dname) {
	DIR* dir;
    struct dirent* den;
    struc stat buf;
    
    // dir = oepn(dname); ⬇️
    if((dir = opendir(dname)) == NULL) exit(-1);
    // 해당 디렉토리의 접근하여 하위 파일 삭제위해
    //해당 디렉토리로 이동 (이동해서 파일 이름 받기)
    chdir(dir); //👈
    while((den = readdir(dir)) != NULL) { // 하나씩 읽으며 실행⭐️
    	// . & .. 은 무시 (삭제하면 x) ⭐️
        if(strcmp(den->d_name, ".") == 0) continue;
        else if (strcmp(den->d_name, "..") == 0) continue;
    	stat(den->d_name, &buf);
        if(S_ISDIR(buf.st_mode)) removeDir(den->d_name);
        else unlink(den->d_name)
    }
    closedir(dir);
    // 모두 끝나고 나서는 디렉토리 삭제 위해 상위로 이동
    chdir(..); // 👈
    rmdir(dname);
}

 

myls

1. 인자가 주어지지 않으면 현재 디렉토리의 파일 목록 보여준다

2. 인자가 주어지면 인자에 해당하는 디렉토리의 파일 목록 보여준다.

3. -a 옵션이 주어지면 히든 파일들도 모두 보여준다.

4. -l 옵션이 주어지면 자세한 파일 내용 보여준다.

5. -al 옵션으로 한번에 줄 수도 있다.

 

1과 2 에 해당하는 코드

int main(int argc, char* argv[]) {
	DIR* dir;
    struct dirent* den;
    char *dname = ".";
    // 인자가 있으면 그 인자를 dname으로 변경
    if(argc > 1) dname = argv[1]; 
    dir = open(dname);
    while((den = readdir(dir)) != NULL) {
    	printf("%s\n", den->d_name);
    }
    closedir(dp);
    
    return 0;
}

3, 4 를 하기 전

우선 해당 리스트를 ls 명령어처럼 sorting하기 위해 qsort()를 사용하는 방법을 보자.

#inlcude <stdlib.h>
void qsort(void *base, size_t nel, size_t width, int(*f)(const void*, const void*));
// void* val 은 generic type pointer
// int *p 는 포인터 변수를 선언
// int *foo()는 함수 프로토타입 선언 시 사용
// foo라는 함수는 파라미터가 없고, int 형태인 함수임을 나타내는 프로토타입
// int (*p)(int) 는 p 포인터가 가리키고 있는 곳에 함수가 있고
// int 형태의 인자를 받는 return int 함수 임을 나타냄
int cpmint(const void* a, cont void* b){
		return *((int*)a) - *((int*)b);
	}
int main() {
	int data[10] = {3,1,5,6,7,8,4,2,9};
	qsort(data, 10, sizeof(int), cmpint);
	for(int i = 0; i < size; i++) printf(%d", data[i]);
    printf("\n");
}

 

int(*foo)(int)로 선언하게 되면

해당 형태이 함수를 가리킬 수 있는 변수 foo를 선언하는 것이다.

그래서 qsort 4번째 파라미터에 해당하는 int(*f)(const void*, const void*)는 해당 함수를 가리키는 포인터를 받는것이다.

함수의 포인터는 배열과 마친가지로 시작주소, 해당 함수의 이름이다.

 

그렇기 때문에 해당 인자로 함수를 전달하기 위해서는 함수의 이름을 전달해야한다.

 

그리고 해당함수 cmpint를 보면 파라미터가

(const void*a, const void* b) 로 되어 있다.

이는 비교할 데이터의 주소를 참조해서 실제로 데이터의 위치르 변경해야 하기 때문에

참조를 위한 포인터가 사용된 것이다.

그런데, 해당 포인터가 가리켜야할 변수들의 타입이 불분명하므로

해당 제너릭 포인터인 void * 를 사용했다.

 

그리고 해당 함수를 실제로 정의할 때 

제너릭 포인터를 캐스팅 해주어 어떤 타입을 사용할 것인지를 명시해 주어야 한다.

그래서 return 할 때

(int*) a 를 사용한 것이다.

 

더하여, (int*)a인 주소를 따라가서 해당 주소(포인터)가 가리키고 있는 값을 가져와야하기 때문에

*(int*)a 가 되는 것이다.

 

l a 옵션, 인자의 개수로 나눠 코딩

해당 명령어가 가질 수 있는 인자의 개수는

myls -al abc :3

myls abc : 2

myls :1

mysl -a -l abc : 4

 

그러니까 인자의 개수가 한계이면 "."에 해당하는 디렉토리를 가져가서 디렉토리를 열면 된다.

인자의 개수가 2개 이면 두번째 인자에 해당하는 디렉토리로 변경하고 디렉토리를 열면 된다.

이제 시작할 때 --argc로 argc를 하나 깍고 시작하면 원래 3이었던 건 2로, 2였던 것은 1이 된다.

int main(int argc, char* argv[]) {
	DIR* dir;
    struct dirent* den;
    char *dname = ".", *entries[512];
    int i, n =0, allFlag = 0;, longFlag = 0;
    
    while(--argc > 0 // 인자의 개수가 1인 myls 만 루프 못들어옴
    	&& **++argv == "-") // 배열[1][0]의미, 근데 이렇게 배열 값으로 접근이 아닌, 진짜 주소값이 옮겨짐
       // 그래서 결국 argv의 주소값을 옮기면서 앞에 것들이 하나씩 사라지고 (옵션 값들)
       // argv[0]에 남는 것은 디렉토리 이름뿐 1️⃣ (1개 남아있는 argc)
        {
        	for(char *p = argv[0]+1; *p != "\0"; p++) {
            // 주소값을 받는 포인터가 +1증가는 char 타입 크기만큼 포인터 이동한다는 의미
            	if (*p == "a") allFlag = 1;
                else if (*p == "l") longFlag = 1;
            }
        }
        
   if(argc > 0) dname = argv[0]; // 그래서 여기에 남은 값인 이름 넣기 2️⃣
}

1️⃣

여기서 **++argv는 

++를 먼저 한다.

argv는 배열의 이름으로 배열의 시작주소이고, 이는 배열의[0]을 가리키고 있는데,

얘를 ++해주니까 배열[1]이 밀려서 배열 [0]이된다(엄밀히 배열[1]을 포인터가 가리키게된다)

여기서 별* 하나는 이 배열이 가리키고 있는 전체 문자열이고, **는 그 문자열의 사작 주소 [0]에 있는 문자이다.

 

...

while((den = readdir(dir))!==NULL) {
	if(!allFlag && den->d_name[0] == '.') continue;
    // 가리키고 있는 파일이 히든 파일(+ . , ..) 이면 패스
    entries[n++] = strdup(den->d_name); //⭐️1️⃣
}

1️⃣에서

char* entries[512]는 문자 타입의 주소를 가질 수 있는 배열이다.

strdup()은 인자로 받은 문자열을 동적으로 할당 복사해서, 해당 값의 시작 주소를 반환한다.

이 시작 주소를 받은 entries[n]은 해당 주소를 가리킨다.

 

그리고 문자열은 각 문자을 포인터로 가리키고 있는 애를 표현한 것이므로 char*이다.

그래서 해당 복사한 문자열을 가리키기 위해서는 char**를 사용한다. (문자 포인터가 가리키고 있는 곳에 있는 문자열)

qsort(entries, n, sizeof(char*), compare);

int compare(const void* a, const void* b) {
	return strcmp(*((char**)a) - *((char**)b))
}

그래서 해당 값의 포인터를 받아야하는 compare 함수 내에서는 위 코드와 같이 char**를 사용한다.

 

for(i = 0; i < n; i++) {
	printf("%s\n", entries[i]); free(entries[i]); // entries각각 free
}
반응형