예제 프로그래밍: myrmdir, myls(9-2)
myrmdir
해당 디렉토리 포함해서 모든 디렉토리와 파일을 삭제하는 프로그래밍
+ 파일이 주어지면 디렉토리가 아니기 때문에 아무것도 삭제하지 않는다.
- 주어진 인자가 디렉토리인지 파일인지 확인한다
stat()을 호출하여 S_ISDIR() 메르토 사용하여 해당 인자가 디렉토리인지 확인한다
디렉토리인 경우 removeDir()을 호출한다. - removeDir
opendir(), readdir(), closedir()을 호출한다.
readdir을 호출하여 해당 디렉토리에 있는 파일 및 디렉토리들을 확인한다.
그리고 읽어들인 엔트리가 디렉토리인 경우 다시 removeDir을 호출하여 삭제한다.
읽어들인 엔트리가 파일인 경우 unlink를 사용해서 해당 파일을 삭제한다. - 디렉토리 내에 모든 파일/디렉토리가 삭제되면 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
}