[리눅스] 디렉토리 읽기 구현 - 재귀적으로 하위 디렉토리 호출(lstat)
디렉토리, 파일과 관련한 더 많은 정보와 예제 코드 를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.
https://reakwon.tistory.com/233
디렉토리 열기, 읽기, 닫기
디렉토리를 읽는 것은 파일을 읽는것과는 다른 함수들을 사용합니다. 오늘은 관련 함수들 간단히 살펴보고 사용하는 예를 보도록 하겠습니다.
우선 관련 함수를 사용하려면 아래와 같은 헤더파일을 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
#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 옵션등을 구현해낼 수 있을 것입니다. 그것은 여러분들이 직접 구현해보세요. 어렵지 않습니다.