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

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 옵션등을 구현해낼 수 있을 것입니다. 그것은 여러분들이 직접 구현해보세요. 어렵지 않습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

더 많은 정보를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

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

reakwon.tistory.com

 

파일 정보 얻어오기

 

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

 

우선 lstat에 관한 시스템 콜 원형부터 보겠습니다.

int lstat(const char *filename, struct stat * buf);

lstat은 두개의 인자를 받게 되어있군요.

filename : 말 그대로 파일의 이름입니다. 하지만 경로를 명시하지 않을때는 현재 디렉토리를 기준으로 파일을 찾습니다. 절대경로나 상대경로를 줄 수 있다는 점은 알고 잇으세요.

buf : 파일의stat구조체를 저장합니다. stat??

무엇일까요. 구조체부터 보도록 하지요. vi 편집기를 사용한다면 lstat에서 3+K(shift)를 입력하면 시스템 콜을 볼 수 있습니다. 조금 더 밑으로 내려가다 보면 stat구조체를 만나볼 수 있게 되는데요. 아래와 같습니다.

struct stat

{

dev st_dev;    /* device */

ino_t st_ino;    /* inode */

mode_t st_mode;    /* protection */

nlink_t st_nlink;    /* number of hard links */

uid_t st_uid;    /* user ID of owner */

gid_t st_gid;    /* group ID of owner */

dev_t st_rdev;    /* device type (if inode device) */

off_t st_size;    /* total size, in bytes */

unsigned long st_blksize;    /* blocksize for filesystem I/O */

unsigned long st_blocks;    /* number of blocks allocated *.

time_t st_atime;    /* time of last access */

time_t st_mtime;    /* time of last modification */

time_t st_ctime;    /* time of last change */

}

음.. 파일에 대한 여러정보를 알 수 있군요. 

st_ino는 inode를 말하는 것이고 st_mode는 파일이 디렉토리인가, 단순 파일인가, 케릭터 디바이스인가를 알 수 있는 것이겠구요.

uid, gid도 알 수 있고 파일의 사이즈도 알 수 있습니다. 더군다나 파일이 언제 수정되고 접근되었는지에 대한 시간 정보도 알 수 있군요! 굿!!

 

그 중에서 ls명령어에 사용되는 st_mode는 어떤 파일의 타입인지 저장되어 있습니다. 어떤 파일 타입인지 알아보기 위해서 매크로 함수를 제공하는데요. 이것에 대해서 조금 더 알아보기 위해서 표로 준비해보았습니다.

 

모드  설명 
 S_ISLNK(m) 심볼릭 링크 파일인지 확인합니다. 
 S_ISREG(m) 단순 파일인지 확인합니다. 
 S_ISDIR(m)  디렉토리인지 확인합니다. 
 S_ISCHR(m)  문자 디바이스인지 확인합니다. 
 S_ISBLK(m)  블록 디바이스인지 확인합니다. 
 S_ISFIFO(m)  FIFO파일인지 확인합니다. 선입선출 구조의 파일인지 확인하는 것이죠. 
 S_ISSOCK(m)  네트워크 통신에 필요한 소켓파일인지 확인합니다. 

 

반환값 : 성공시 0이 반환되고 실패시 -1이 반환됩니다. 에러가 발생했다면 적절한 값으로 설정된다고 하는데 에러에 대해서는 구글형님께 물어봅시다.

 

구현

이제 대충 알아보았으니 코드를 직접 짜보도록하겠습니다. 우선 코드부터 보시죠. 

#include <stdio.h> 
#include <fcntl.h> 
#include <stdlib.h> 
#include <sys/stat.h> 
#include <sys/types.h> 
#include <unistd.h> 
#include <time.h> 
#define BUF_SIZE 1024     
void printType(const char *name,const struct stat *buf){
        if(S_ISDIR(buf->st_mode))   
                printf("%s is DIR \n",name);  
        else if(S_ISREG(buf->st_mode))  
                printf("%s is FILE\n", name);  
        else if(S_ISSOCK(buf->st_mode))    
                printf("%s is SOCKET\n",name);   
        else if(S_ISCHR(buf->st_mode))   
                printf("%s is CHARACTER DEVICE\n",name);    
        else if(S_ISFIFO(buf->st_mode))    
                printf("%s is FIFO\n",name);     
        else if(S_ISBLK(buf->st_mode))   
                printf("%s is BLOCK DEVICE\n",name);   
        else if(S_ISLNK(buf->st_mode))   
                printf("%s is LINK\n",name); 
}  

void printTime(struct stat *buf){       
        struct tm *mtime;      
        mtime=localtime(&buf->st_mtime);     
        printf("%04d-%02d-%02d %02d:%02d\n",
                        mtime->tm_year+1900, mtime->tm_mon+1,
                        mtime->tm_mday, mtime->tm_hour,  
                        mtime->tm_min); 
}   

void printFileSize(const char *name, struct stat *buf){      
        printf("%s size: %ld\n",name,buf->st_size); 
}  

int main(){      
        char filename_dir[128]="dir";     
        char filename_file[128]="aaa";   
        struct stat file;       
        struct stat dir;     

        lstat(filename_dir,&dir);     
        lstat(filename_file,&file);  

        printType(filename_dir,&dir);     
        printTime(&dir); 
        printFileSize(filename_dir,&dir);    
        printf("\n");

        printType(filename_file,&file);    
        printTime(&file);  
        printFileSize(filename_file, &file); 
}

 

 

printType의 함수는 파일이 어떤 종류의 파일인지 알아오는 함수입니다. 그러니까 이 파일이 정규파일인지, 디렉토리인지, 블록 디바이스 파일인지 등등 알아오는 함수지요. 매개변수는 stat구조체의 포인터를 매개변수로 받아야 알 수 있겠죠? 파일의 이름과 같아 받게 됩니다.
 
printTime은 파일이 언제 수정되었는지에 대해서 알아오는 함수입니다. 역시 stat구조체를 매개변수로 받습니다. 시간을 우리가 더 잘알아보기 위해서 localtime이라는 함수를 사용합니다. 그래서 tm구조체의 포인터를 반환하죠.
 
printFileSize는 파일의 크기를 알아오는 함수입니다. 역시 파일의 이름과 stat구조체를 매개변수로 받고 있습니다. 파일의 크기를 알아오는 변수는 st_size입니다.
 
이제 코드를 컴파일시키고 파일을 만들어 실험해봅시다.
# gcc lstat.c
# mkdir dir
# touch aaa

 

 
이제 파일의 크기를 조금 변경시켜보도록 하겠습니다. vi 편집기를 열어 aaa파일에 ABCDEF라는 문자열을 입력하고 저장합니다.
# vi aaa
ABCDEF
:wq
 
이제 컴파일된 파일을 실행시킵니다.
# ./a.out
dir is DIR
2018-11-19 00:06
dir size : 6

aaa is FILE
2018-11-19 00:46
aaa size : 7
 
그 후에 ls 명령어로 두 파일과 디렉토리를 보게되면 같은 정보를 확인할 수가 있습니다.
반응형
블로그 이미지

REAKWON

와나진짜

,