C 언어를 사용한 파일 유형 통계

CBeginner
지금 연습하기

소개

이 장은 Linux 의 파일 및 디렉토리 인터페이스를 기반으로 합니다. 이 프로젝트는 파일 시스템의 본질을 중심으로 하며, lstat 함수와 디렉토리 연산을 사용하여 파일 유형을 재귀적으로 계산하는 프로그램을 구현합니다. 이를 통해 Linux 파일 시스템의 파일 유형 구성을 깊이 있게 이해할 수 있는 편리한 방법을 제공합니다. 또한, 이 프로젝트에서 개발된 파일 유형 계산 프로그램은 실제 학습 및 작업 환경에서 활용될 수 있습니다.

👀 미리보기

$ ./file_type .
regular files = 2, 66.67 %
directories = 1, 33.33 %
block special = 0, 0.00 %
char special = 0, 0.00 %
FIFOs = 0, 0.00 %
symbolic links = 0, 0.00 %
sockets = 0, 0.00 %

🎯 과제

이 프로젝트에서는 다음을 배우게 됩니다:

  • Linux 파일 및 디렉토리 인터페이스를 사용하여 디렉토리 내의 파일 유형을 재귀적으로 계산하는 C 프로그램을 구현하는 방법.

🏆 성과

이 프로젝트를 완료하면 다음을 수행할 수 있습니다:

  • Linux 에서 lstat 함수를 사용하여 파일 정보를 얻을 수 있습니다.
  • 디렉토리 열기 및 디렉토리 항목 읽기와 같은 디렉토리 연산을 수행할 수 있습니다.
  • 일반 파일, 디렉토리, 블록 특수 파일, 문자 특수 파일, 명명된 파이프, 심볼릭 링크 및 소켓을 포함한 다양한 파일 유형을 재귀적으로 계산하는 프로그램을 만들 수 있습니다.
  • 디렉토리 내 각 파일 유형의 백분율을 계산하고 표시할 수 있습니다.

기본 지식 및 프로젝트 파일 생성

다음으로, 구상에서 구현까지의 단계를 소개하며, 주로 다음 C 언어 지식 포인트를 적용합니다:

  • stat 구조체 및 lstat 함수, opendir, readdir 함수, dirent 구조체, 재귀, 함수 호출 등.

전체 프로그램에서 main, myftw, dopath, myfunc, 그리고 path_alloc과 같은 함수를 구성합니다.

  • myfunc 함수는 주로 기준을 충족하는 파일 유형을 순회하고 계산합니다.
  • dopath 함수는 주로 경로를 재귀적으로 얻고 디렉토리인지 파일인지 판단합니다.
  • myftw 함수는 path_alloc에서 전체 경로를 저장하기 위한 메모리 공간의 시작 주소와 크기를 얻는 것부터 시작합니다.
  • path_alloc 함수는 주로 경로 (전체 경로) 에 대한 메모리 공간을 할당합니다.

~/project 디렉토리에 file_type.c라는 새 파일을 생성하고, 선호하는 코드 편집기에서 엽니다.

cd ~/project
touch file_type.c

main 함수 설계

main 함수의 주요 기능은 먼저 명령줄 인수를 받아 유효성을 검사하는 것입니다. 그런 다음 myftw 함수를 호출하여 다양한 유형의 파일 수를 계산합니다. 마지막으로, 파일 유형의 백분율을 계산하고 출력합니다.

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

#define FTW_F 1 /* Flag for non-directory files */
#define FTW_D 2 /* Flag for directory files */
#define FTW_DNR 3 /* Flag for unreadable directory files */
#define FTW_NS 4 /* Flag for files that cannot be accessed by stat */

static char *fullpath;
static size_t pathlen;

/* Define the function for handling files */
typedef int Myfunc(const char *, const struct stat *, int);
static Myfunc myfunc;
static int myftw(char *, Myfunc *);
static int dopath(Myfunc *);
char *path_alloc(size_t *size);

static long nreg, ndir, nblk, nchr, nfifo, nslink, nsock, ntot;

int main(int argc, char *argv[])
{
 int ret;

 // Perform input validity check
 if (argc != 2)
 {
  printf("Invalid command input! \n");
  exit(1);
 }

 /* Calculate the number of various types of files */
 ret = myftw(argv[1], myfunc);

 /* Calculate the total number of files */
 ntot = nreg + ndir + nblk + nchr + nfifo + nslink + nsock;

 /* Avoid division by 0 to improve program stability */
 if (ntot == 0)
  ntot = 1;

 /* Print the percentage of various types of files */
 printf("regular files  = %7ld, %5.2f %%\n", nreg,
  nreg*100.0 / ntot);
 printf("directories    = %7ld, %5.2f %%\n", ndir,
  ndir*100.0 / ntot);
 printf("block special  = %7ld, %5.2f %%\n", nblk,
  nblk*100.0 / ntot);
 printf("char special   = %7ld, %5.2f %%\n", nchr,
  nchr*100.0 / ntot);
 printf("FIFOs          = %7ld, %5.2f %%\n", nfifo,
  nfifo*100.0 / ntot);
 printf("symbolic links = %7ld, %5.2f %%\n", nslink,
  nslink*100.0 / ntot);
 printf("sockets        = %7ld, %5.2f %%\n", nsock,
  nsock*100.0 / ntot);
 exit(ret);
}
  • *fullpath: 파일의 전체 경로를 저장하는 데 사용됩니다.
  • pathlen: 파일 경로의 길이를 저장하는 데 사용됩니다.
  • nreg: 일반 파일의 수.
  • ndir: 디렉토리 파일의 수.
  • nblk: 블록 특수 파일의 수.
  • nchr: 문자 특수 파일의 수.
  • nfifo: 명명된 파이프의 수.
  • nslink: 심볼릭 링크 파일의 수.
  • nsock: 소켓 파일의 수.
  • ntot: 총 파일 수.

myftw 함수 설계

이 함수는 pathname을 처리하고 이를 전역 문자 배열에 저장한 다음 dopath를 호출하는 데 사용됩니다. path_alloc 함수는 공간을 할당하는 데 사용되며, fullpath가 전역 변수이므로 다른 함수에서 편리하게 사용할 수 있다는 점이 중요합니다. 그런 다음, dopath 함수를 호출하여 pathname 을 추가로 처리합니다 (디렉토리인지 여부).

static int myftw(char *pathname, Myfunc *func)
{
 /* Allocate space for the string array to save the path */
 fullpath = path_alloc(&pathlen);

 /* If the allocated space is not enough to save the path, use realloc to reallocate */
 if (pathlen <= strlen(pathname)) {
  pathlen = strlen(pathname) * 2;
  if ((fullpath = realloc(fullpath, pathlen)) == NULL);
  printf("realloc failed!\n");
 }

 /* Save the pathname parameter in the full path. Pay attention: fullpath is a global variable
    and can be called by dopath */
 strcpy(fullpath, pathname);

 /* Call the dopath function */
 return(dopath(func));
}

/* Path array allocation */
char *path_alloc(size_t* size)
{
 char *p = NULL;
 if (!size)
  return NULL;
 p = malloc(256);
 if (p)
  *size = 256;
 else
  *size = 0;
 return p;
}

'dopath' 함수 설계

  • int lstat(const char *path, struct stat *buf) 함수: 파일이 심볼릭 링크인 경우, lstat는 심볼릭 링크 자체에 대한 정보를 반환하는 반면, stat는 링크가 가리키는 파일에 대한 정보를 반환합니다. 여기서 사용되는 데이터 구조 중 하나는 stat 구조체입니다. 이 구조체의 정의는 다음과 같습니다.
struct stat {
dev_t           st_dev;
ino_t           st_ino;
mode_t          st_mode;
nlink_t         st_nlink;
uid_t           st_uid;
gid_t           st_gid;
dev_t           st_rdev;
off_t           st_size;
timestruc_t     st_atim;
timestruc_t     st_mtim;
timestruc_t     st_ctim;
blksize_t       st_blksize;
blkcnt_t        st_blocks;
char            st_fstype[_ST_FSTYPSZ];
};
  • DIR* opendir (const char * path ) 함수: 디렉토리를 열고, 실패 시 빈 포인터를 반환합니다.

  • struct dirent *readdir(DIR *dir) 함수: readdir()는 디렉토리 스트림 dir에서 다음 디렉토리 항목을 반환합니다. 반환 값: 성공 시, readdir()는 다음 디렉토리 항목에 대한 포인터를 반환합니다. 디렉토리 스트림의 끝에 도달하면 readdir()는 NULL 을 반환합니다.

/* dopath 는 디렉토리인지 여부를 결정하고, 카운팅을 위해 myfunc 함수를 직접 호출할지, 아니면 dopath 함수를 재귀적으로 호출할지를 선택하는 데 사용됩니다. */
static int dopath(Myfunc* func)
{
 struct stat statbuf;
 struct dirent *dirp;
 DIR *dp;
 int ret, n;

 /* Call the lstat function to obtain the stat information of the pathname. If it fails, call the func function and pass FTW_NS */
 if (lstat(fullpath, &statbuf) < 0)
  return(func(fullpath, &statbuf, FTW_NS));

 /* Check the st_mode of the file stat structure. If it is not a directory, call the func function and pass FTW_F, and then determine the file type by myfunc */
 if (S_ISDIR(statbuf.st_mode) == 0)
  return(func(fullpath, &statbuf, FTW_F));

 /* The last case is that the pathname represents a directory. The normal return value of func is 0, so after executing func, it will not return and will continue to recursively call func */
 if ((ret = func(fullpath, &statbuf, FTW_D)) != 0)
  return(ret);
 /* Path processing, expand the length of the path space */
 n = strlen(fullpath);
 if (n + NAME_MAX + 2 > pathlen) {
  pathlen *= 2;
  if ((fullpath = realloc(fullpath, pathlen)) == NULL)
   printf("realloc failed!\n");
 }
 fullpath[n++] = '/';
 fullpath[n] = 0;

 /* Handle each entry in the directory */
 if ((dp = opendir(fullpath)) == NULL)
  return(func(fullpath, &statbuf, FTW_DNR));
 while ((dirp = readdir(dp)) != NULL) {
  if (strcmp(dirp->d_name, ".") == 0  ||
      strcmp(dirp->d_name, "..") == 0)
    continue;  /* Ignore the current directory (.) and the parent directory (..) to avoid infinite loop */
  strcpy(&fullpath[n], dirp->d_name); /* Append the name of the current directory entry after "/" */
  if ((ret = dopath(func)) != 0) /* Then recursively call dopath with the new pathname */
   break;
 }
 fullpath[n-1] = 0;

 /* Close the directory */
 if (closedir(dp) < 0)
  printf("can't close directory %s", fullpath);
 return(ret);
}

myfunc 함수 설계

myfunc 함수의 주요 목적은 stat을 기반으로 파일 유형을 결정하고 이를 카운트하는 것입니다. S_IFMTst_mode 플래그를 해석하는 데 사용되는 마스크입니다.

파일 유형을 결정하는 데 도움이 되는 몇 가지 매크로 정의가 있습니다. 이들은 함수 호출과 유사한 매개변수화된 매크로입니다.

  • S_ISBLK: 특수 블록 장치 파일인지 테스트합니다.
  • S_ISCHR: 특수 문자 장치 파일인지 테스트합니다.
  • S_ISDIR: 디렉토리인지 테스트합니다.
  • S_ISFIFO: FIFO 장치인지 테스트합니다.
  • S_ISREG: 일반 파일인지 테스트합니다.
  • S_ISLNK: 심볼릭 링크인지 테스트합니다.
  • S_ISSOCK: 소켓인지 테스트합니다.
static int myfunc(const char *pathname, const struct stat *statptr, int type)
{
 switch (type) {

 /* Handling for non-directory files */
 case FTW_F:
  switch (statptr->st_mode & S_IFMT) {
  case S_IFREG: nreg++;  break;
  case S_IFBLK: nblk++;  break;
  case S_IFCHR: nchr++;  break;
  case S_IFIFO: nfifo++; break;
  case S_IFLNK: nslink++; break;
  case S_IFSOCK: nsock++; break;
  case S_IFDIR:
   printf("for S_IFDIR for %s", pathname);
  }
  break;

 /* Handling for directory files */
 case FTW_D:
  ndir++;
  break;

 /* Handling for unreadable directories */
 case FTW_DNR:
  printf("%s directory is unreadable", pathname);
  break;
 case FTW_NS:
  printf("%s error in stat", pathname);
  break;
 default:
  printf("Type %d is unrecognized, pathname is %s", type, pathname);
 }
 return(0);
}

컴파일 및 테스트

터미널에서 다음 명령을 입력하여 컴파일하고 실행합니다.

cd ~/project
gcc -o file_type file_type.c
./file_type .
labex:project/ $ ls
file_type file_type.c

labex:project/ $ gcc -o file_type file_type.c

labex:project/ $ ./file_type .
regular files = 2, 66.67 %
directories = 1, 33.33 %
block special = 0, 0.00 %
char special = 0, 0.00 %
FIFOs = 0, 0.00 %
symbolic links = 0, 0.00 %
sockets = 0, 0.00 %

결과에 따르면 현재 디렉토리에서 일반 파일이 66.67% 를 차지하고 디렉토리가 33.33% 를 차지합니다.

시스템 디렉토리의 권한을 계산해야 하는 경우, 명령 실행 전에 sudo를 사용할 수 있습니다. 그럼에도 불구하고, 여전히 권한을 계산할 수 없는 파일이 일부 존재합니다.

요약

이 프로젝트의 훈련을 통해 Linux 파일 시스템에 대한 이해도를 높일 수 있습니다. 디렉토리 작업을 수행하는 방법을 배우고 파일 정보를 저장하는 stat 구조에 대한 더 깊은 이해를 얻게 될 것입니다. 이 프로젝트를 완료함으로써 실용적인 Linux 도구를 개발할 수 있습니다.

✨ 솔루션 확인 및 연습✨ 솔루션 확인 및 연습✨ 솔루션 확인 및 연습✨ 솔루션 확인 및 연습✨ 솔루션 확인 및 연습✨ 솔루션 확인 및 연습