C 言語を使ったファイルタイプ統計

CCBeginner
今すぐ練習

💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください

はじめに

この章は、Linuxのファイルとディレクトリインターフェイスを基にしています。このプロジェクトは、ファイルシステムの性質を中心に、lstat関数とディレクトリ操作を使って、ファイルタイプを再帰的に数えるプログラムを実装します。これは、Linuxファイルシステムにおけるファイルタイプの構成を深く理解するための便利な方法を提供します。また、このプロジェクトで開発されたファイルタイプ数えプログラムは、実際の学習や作業環境で使用できます。

👀 予想結果

$./file_type.
通常ファイル = 2, 66.67 %
ディレクトリ = 1, 33.33 %
ブロック特殊ファイル = 0, 0.00 %
キャラクタ特殊ファイル = 0, 0.00 %
FIFO = 0, 0.00 %
シンボリックリンク = 0, 0.00 %
ソケット = 0, 0.00 %

🎯 タスク

このプロジェクトでは、以下のことを学びます。

  • Linuxのファイルとディレクトリインターフェイスを使って、ディレクトリ内のファイルタイプを再帰的に数えるC言語のプログラムを実装する方法。

🏆 成果

このプロジェクトを完了すると、以下のことができるようになります。

  • Linuxでlstat関数を使ってファイル情報を取得する。
  • ディレクトリを開いてディレクトリエントリを読むなどのディレクトリ操作を行う。
  • 通常ファイル、ディレクトリ、ブロック特殊ファイル、キャラクタ特殊ファイル、名前付きパイプ、シンボリックリンク、ソケットを含む、さまざまなファイルタイプを再帰的に数えるプログラムを作成する。
  • ディレクトリ内の各ファイルタイプの割合を計算して表示する。

Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL c(("C")) -.-> c/BasicsGroup(["Basics"]) c(("C")) -.-> c/FunctionsGroup(["Functions"]) c(("C")) -.-> c/ControlFlowGroup(["Control Flow"]) c(("C")) -.-> c/CompoundTypesGroup(["Compound Types"]) c(("C")) -.-> c/PointersandMemoryGroup(["Pointers and Memory"]) c(("C")) -.-> c/UserInteractionGroup(["User Interaction"]) c/BasicsGroup -.-> c/operators("Operators") c/ControlFlowGroup -.-> c/switch("Switch") c/CompoundTypesGroup -.-> c/strings("Strings") c/CompoundTypesGroup -.-> c/structures("Structures") c/PointersandMemoryGroup -.-> c/memory_address("Memory Address") c/FunctionsGroup -.-> c/function_declaration("Function Declaration") c/FunctionsGroup -.-> c/function_parameters("Function Parameters") c/FunctionsGroup -.-> c/recursion("Recursion") c/UserInteractionGroup -.-> c/output("Output") subgraph Lab Skills c/operators -.-> lab-298832{{"C 言語を使ったファイルタイプ統計"}} c/switch -.-> lab-298832{{"C 言語を使ったファイルタイプ統計"}} c/strings -.-> lab-298832{{"C 言語を使ったファイルタイプ統計"}} c/structures -.-> lab-298832{{"C 言語を使ったファイルタイプ統計"}} c/memory_address -.-> lab-298832{{"C 言語を使ったファイルタイプ統計"}} c/function_declaration -.-> lab-298832{{"C 言語を使ったファイルタイプ統計"}} c/function_parameters -.-> lab-298832{{"C 言語を使ったファイルタイプ統計"}} c/recursion -.-> lab-298832{{"C 言語を使ったファイルタイプ統計"}} c/output -.-> lab-298832{{"C 言語を使ったファイルタイプ統計"}} end

基本知識とプロジェクトファイルの作成

次に、コンセプトから実装までの手順を紹介します。主に以下のC言語の知識ポイントを適用します。

  • stat構造体とlstat関数、opendirreaddir関数、dirent構造体、再帰、関数呼び出しなど。

プログラム全体では、mainmyftwdopathmyfuncpath_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 /* ディレクトリでないファイル用のフラグ */
#define FTW_D 2 /* ディレクトリファイル用のフラグ */
#define FTW_DNR 3 /* 読み取り不可なディレクトリファイル用のフラグ */
#define FTW_NS 4 /* statによってアクセスできないファイル用のフラグ */

static char *fullpath;
static size_t pathlen;

/* ファイルを処理する関数を定義 */
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;

 // 入力の妥当性チェック
 if (argc!= 2)
 {
  printf("無効なコマンド入力! \n");
  exit(1);
 }

 /* さまざまな種類のファイルの数を計算 */
 ret = myftw(argv[1], myfunc);

 /* ファイルの総数を計算 */
 ntot = nreg + ndir + nblk + nchr + nfifo + nslink + nsock;

 /* ゼロ割り算を回避してプログラムの安定性を向上させる */
 if (ntot == 0)
  ntot = 1;

 /* さまざまな種類のファイルの割合を表示 */
 printf("通常ファイル  = %7ld, %5.2f %%\n", nreg,
  nreg*100.0 / ntot);
 printf("ディレクトリ    = %7ld, %5.2f %%\n", ndir,
  ndir*100.0 / ntot);
 printf("ブロック特殊  = %7ld, %5.2f %%\n", nblk,
  nblk*100.0 / ntot);
 printf("キャラクタ特殊   = %7ld, %5.2f %%\n", nchr,
  nchr*100.0 / ntot);
 printf("FIFO          = %7ld, %5.2f %%\n", nfifo,
  nfifo*100.0 / ntot);
 printf("シンボリックリンク = %7ld, %5.2f %%\n", nslink,
  nslink*100.0 / ntot);
 printf("ソケット        = %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関数を呼び出して、パス名(ディレクトリかどうか)をさらに処理します。

static int myftw(char *pathname, Myfunc *func)
{
 /* パスを保存するための文字列配列の空間を割り当てる */
 fullpath = path_alloc(&pathlen);

 /* 割り当てられた空間がパスを保存するのに十分でない場合、reallocを使って再割り当てする */
 if (pathlen <= strlen(pathname)) {
  pathlen = strlen(pathname) * 2;
  if ((fullpath = realloc(fullpath, pathlen)) == NULL);
  printf("realloc failed!\n");
 }

 /* パス名パラメータを完全パスに保存する。注意:fullpathはグローバル変数であり、dopathによって呼び出される */
 strcpy(fullpath, pathname);

 /* dopath関数を呼び出す */
 return(dopath(func));
}

/* パス配列の割り当て */
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はリンクが指すファイルに関する情報を返します。ここで使用される1つのデータ構造は、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;

 /* lstat関数を呼び出して、pathnameのstat情報を取得します。失敗した場合は、func関数を呼び出してFTW_NSを渡します。 */
 if (lstat(fullpath, &statbuf) < 0)
  return(func(fullpath, &statbuf, FTW_NS));

 /* ファイルstat構造のst_modeをチェックします。ディレクトリでない場合は、func関数を呼び出してFTW_Fを渡し、その後、myfuncによってファイルタイプを判断します。 */
 if (S_ISDIR(statbuf.st_mode) == 0)
  return(func(fullpath, &statbuf, FTW_F));

 /* 最後のケースは、pathnameがディレクトリを表す場合です。funcの通常の戻り値は0です。したがって、funcを実行した後は戻らず、再帰的にfuncを呼び続けます。 */
 if ((ret = func(fullpath, &statbuf, FTW_D))!= 0)
  return(ret);
 /* パス処理、パス空間の長さを拡張します。 */
 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;

 /* ディレクトリ内の各エントリを処理します。 */
 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;  /* 現在のディレクトリ (.) と親ディレクトリ (..) を無視して、無限ループを回避します。 */
  strcpy(&fullpath[n], dirp->d_name); /* "/"の後に現在のディレクトリエントリの名前を追加します。 */
  if ((ret = dopath(func))!= 0) /* その後、新しいパス名で再帰的にdopathを呼び出します。 */
   break;
 }
 fullpath[n-1] = 0;

 /* ディレクトリを閉じます。 */
 if (closedir(dp) < 0)
  printf("can't close directory %s", fullpath);
 return(ret);
}
✨ 解答を確認して練習

myfunc関数を設計する

myfunc関数の主な目的は、statに基づいてファイルタイプを判断し、それらをカウントすることです。S_IFMTは、st_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) {

 /* ディレクトリでないファイルの処理 */
 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;

 /* ディレクトリファイルの処理 */
 case FTW_D:
  ndir++;
  break;

 /* 読み取り不可なディレクトリの処理 */
 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.
通常ファイル = 2, 66.67 %
ディレクトリ = 1, 33.33 %
ブロック特殊 = 0, 0.00 %
キャラクタ特殊 = 0, 0.00 %
FIFO = 0, 0.00 %
シンボリックリンク = 0, 0.00 %
ソケット = 0, 0.00 %

結果から、現在のディレクトリでは、通常ファイルが66.67%、ディレクトリが33.33%を占めていることがわかります。

システムディレクトリ内のパーミッションをカウントする必要がある場合は、コマンドを実行する前にsudoを使用できます。ただし、それでもカウントできないファイルもいくつかあります。

✨ 解答を確認して練習

まとめ

このプロジェクトのトレーニングを通じて、Linuxファイルシステムに対する理解を深めることができます。ディレクトリ操作を行う方法を学び、ファイル情報を格納するstat構造についてより深く理解することができます。このプロジェクトを完了することで、実用的なLinuxツールを開発することができます。