컴퓨터/운영체제(주로 리눅스)

[리눅스] 디렉토리 읽기 구현 - 재귀적으로 하위 디렉토리 호출(lstat)

REAKWON 2022. 3. 30. 23:19

디렉토리, 파일과 관련한 더 많은 정보와 예제 코드 를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

디렉토리 열기, 읽기, 닫기

디렉토리를 읽는 것은 파일을 읽는것과는 다른 함수들을 사용합니다. 오늘은 관련 함수들 간단히 살펴보고 사용하는 예를 보도록 하겠습니다.

우선 관련 함수를 사용하려면 아래와 같은 헤더파일을 include해야합니다. 

 #include <sys/types.h>
 #include <dirent.h>

 

1) opendir

 DIR *opendir(const char *name);

 

opendir에 디렉토리 이름을 인자로 넣어주게 되면 정상적으로 종료시 DIR 포인터에 그 디렉토리에 대한 포인터가 반환이 됩니다. 에러시 NULL이 반환되고, errno에 에러 번호가 기록이 됩니다.

 

2) readdir

struct dirent *readdir(DIR *dirp);

 

디렉토리의 내용을 읽게 되면 그 디렉토리 안의 디렉토리나 파일이 dirent 구조체 포인터로 반환되게 됩니다. 이 함수는 주로 while문과 같이 사용됩니다.  dirent의 구조체 내용은 아래와 같습니다. 만약 읽을 파일이나 디렉토리가 없으면 NULL을 반환하게 됩니다.

struct dirent {
       ino_t          d_ino;       /* Inode number */
       off_t          d_off;       /* Not an offset; see below */
       unsigned short d_reclen;    /* Length of this record */
       unsigned char  d_type;      /* Type of file; not supported
                                      by all filesystem types */
       char           d_name[256]; /* Null-terminated filename */
};

 

주석만 잘 읽어도 위의 필드들이 무엇을 의미하는지는 알 수 있겠습니다. 

 

3. closedir

int closedir(DIR *dirp);

파일을 열고난 이후 close() 함수로 닫아주듯이 디렉토리 역시 마찬가지입니다. 이 함수가 closedir이고, opendir()에서 반환받은 DIR*를 전달해주면 됩니다. 에러없이 성공적으로 디렉토리를 닫았다면 0이 반환되고, 그렇지 않으면 -1이 반환됩니다. 이 역시 errno를 통해서 에러 번호를 확인할 수 있습니다.

 

디렉토리 내용 읽기

위의 함수들만을 이용해서 디렉토리의 내용들을 볼 수 있습니다. 아래는 그러한 예의 프로그램 코드입니다.

#include <dirent.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
void read_dirent(char *dir_name){

        DIR* dir = opendir(dir_name);
        if(dir == NULL){
                printf("디렉토리를 열수 없습니다.\n");
                return;
        }

        struct dirent *entry;
        while((entry=readdir(dir))!=NULL){
                if(strcmp(entry->d_name,".") == 0 || strcmp(entry->d_name,"..") == 0) continue;
                printf("%s\n",entry->d_name);
        }

        closedir(dir);
}
int main(int argc, char *argv[]){
        if(argc != 2){
                printf("1개의 directory path가 필요합니다. \n");
                return 1;
        }

        read_dirent(argv[1]);
}

read_dirent() 함수는 간단합니다. 먼저 디렉토리의 이름을 인자로 받고 있습니다. 1. 이름을 통해서 opendir을 통해 디렉토리를 열고, 이후 2.read_dir로 반복적으로 그 디렉토리의 내용을 읽습니다. 마지막은 3,closedir로 닫는 역할을 합니다. 

# gcc dir_test.c
# ./a.out /etc/logrotate.d/
ufw
unattended-upgrades
cups-daemon
rsyslog
wtmp
ubuntu-advantage-tools
speech-dispatcher
apt
apport
bootlog
dpkg
ppp
alternatives
btmp

 

재귀적으로 하위 디렉토리까지 읽기

위의 코드를 조금만 바꾸고 lstat을 이용하게 된다면 재귀적으로 호출할 수도 있습니다. lstat에 대해서 모르신다면 아래 포스팅을 참고해보세요.

https://reakwon.tistory.com/40

 

[리눅스] 파일 정보 얻어오기(lstat), 사용 예제

파일 정보 얻어오기 파일에 대한 정보를 얻어올때는 lstat시스템 콜을 사용하면 됩니다. 사실 stat, lstat, fstat등 여러가지 시스템 콜이 있는데요, 가장 간단하게 사용할 수 있는 시스템 콜이기 때문

reakwon.tistory.com

 

#include <dirent.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <sys/stat.h>

void concat_path(char *dest, char* src1, char* src2){
        strcpy(dest,src1);
        strcat(dest,"/");
        strcat(dest,src2);
}
void read_dirent(char *dir_name){

        DIR* dir = opendir(dir_name);
        if(dir == NULL){
                printf("디렉토리를 열수 없습니다.\n");
                return;
        }

        struct dirent *entry;
        while((entry=readdir(dir))!=NULL){
                if(strcmp(entry->d_name,".") == 0 || strcmp(entry->d_name,"..") == 0) continue;
                struct stat buf;
                char full_path[256];

                concat_path(full_path, dir_name, entry->d_name);

                printf("%s\n",full_path);

                if(lstat(full_path, &buf) <0)
                        printf("lstat error\n");
                else{
                        if(S_ISDIR(buf.st_mode))        //디렉토리일 경우 재귀 호출
                                read_dirent(full_path);

                }

        }

        closedir(dir);
}
int main(int argc, char *argv[]){
        if(argc != 2){
                printf("1개의 directory path가 필요합니다. \n");
                return 1;
        }
        //오른쪽에 /가 들어올 수 있음. ex) /etc/ -> /etc
        if(argv[1][strlen(argv[1])-1] == '/') argv[1][strlen(argv[1])-1]='\0';

        read_dirent(argv[1]);
}

 

코드가 길어보이지만, 어렵지 않은 코드입니다. 바로 위의 코드에 비해서 1. 완전한 경로를 구할 수 있는 코드(concat_path)를 추가한것2. 재귀적으로 호출하는 부분이 전부입니다. 이렇게 구현하게 되면 아래와 같이 재귀적으로 하위 디렉토리까지 출력하게 됩니다.

# ./a.out /usr/include
...
/usr/include/linux
/usr/include/linux/nilfs2_api.h
...
/usr/include/linux/netfilter
/usr/include/linux/netfilter/xt_HMARK.h
...

 

이제 디렉토리를 다루는 방법과 lstat에 대해 이해하셨다면 ls의 a, l, i, R 옵션등을 구현해낼 수 있을 것입니다. 그것은 여러분들이 직접 구현해보세요. 어렵지 않습니다.

반응형