プリント基板設計・シミュレーション

TOP > アポロレポート > コラム > 第126回 プログラミングについて『作っておくと便利なちょっとしたプログラム その10』
コラム
2026/02/27

第126回 プログラミングについて『作っておくと便利なちょっとしたプログラム その10』

アポロレポート

           プログラミングについて 第126回目

       『作っておくと便利なちょっとしたプログラム その10』


 今回も引き続き行数表示プログラムを作っていきましょう。今回からはファイルの行数表示の機能の追加です。

 ファイルの行数表示で一番問題なのが、指定するファイル名にワイルドカードを使用できることを許したことと、サブディレクトリの検索も行えるようにしたことです。

 ワイルドカードでのファイルの検索についてはいくつかの方法があります。使用するコンパイラが16ビット版のもののときには、コンパイラの関数にファイルを検索するものが用意されていればそれを使えば良いのですが、ないときにはDOSのシステムコールを使用するしか方法がありません。私の手元にある『MS-DOS 5.0A プログラマーズ リファレンスマニュアル』を開いてみると、 ファンクションリクエスト4EHと4FHがファイル検索を行うもので、それを使用するために1AHのファンクションリクエストも使用します。これらの使用方法についての説明はここでは行いませんので、必要な方はこのマニュアルを参照して下さい。

 このプログラムでは32ビット版とし、コンパイラ Visual C++ にある関数 _findfirst、_findnext、_findclose を使用することにします。

 _findfirst 最初のファイルを検索します。
 _findnext  次のファイルを検索します。
 _findclose 検索を終了します。

関数 _findfirst はワイルドカードを含んだ検索するファイル名を指定すると、構造体struct _finddata_t を返し、関数の戻り値として検索のハンドルを返します。ハンドルとは他のOSやコンパイラでは検索ストリームとか検索IDとか呼んでいるものです。こういったものをWindowsではハンドルと呼ぶのが一般的です。私の勝手な憶測ですが、マイクロソフトの人たちはかなりこれまでの他のOSにコンプレックスを持っているみたいで、呼び名を変えているものがかなりあるようです。それらの中の1つがこのハンドルで、いわゆる自動車のハンドルと同じ意味として呼んでいるようです。要するにハンドルを握ったものが主導権を握るということなんでしょうね。もし、日本人がこれを呼ぶとしたら『先棒』でしょうか。『先棒をかつぐ』と同じ意味ですので、関数 _findfirst は戻り値として先棒を返します、とでも説明するのでしょうか。
 話が外れてしまいました。関数 _findnext は次に検索したファイルの構造体と、見つかったか否かの値を戻り値として返します。関数 _findcose はハンドルを指定して検索を終了するものです。

まずこの検索機能を関数 display_total_lines に追加してみましょう。予めメイン関数の前にグローバルに、

 struct _finddata_t file_info;
 long        find_handle;

を定義しておきます。

 display_total_lines()
 {
  if(file_name_top==NULL){
   printf("\n===== 標準入力 =====\n\n");

   if(display_file_total_lines()== -1)return -1;

   printf("\n==================\n");
   printf("\n合計 %10u 行\n",grand_total_lines);
  }
  else{
   struct file_name_strct *fnc;
                /* ファイル名を順番に処理していきます */
   fnc=file_name_top;
   while(fnc){
                /* 関数 start_search の結果が真のときは
                  ファイルが検索されたときです */
    if(start_search(fnc->fname)){
     do{
                /* 検索されたファイルの属性と
                  名前を表示します */
      printf("%x %s\n",file_info.attrib,file_info.name);

                /* 次のファイルが検索されたときには
                  真になります */
     }while(next_search());
    }

    fnc=fnc->next;
   }
  }

  return 1;
 }

ここで、関数 start_search と next_search は、

 start_search(search_fname)
 char *search_fname;
 {
  find_handle=_findfirst(search_fname,&file_info);

  if(find_handle== -1)return 0;
  else    return 1;
 }

 next_search()
 {
  if(_findnext(find_handle,&file_info)){
   _findclose(find_handle);
   return 0;
  }

  return 1;
 }

検索されたときには、struct _finddata_t 構造体である file_info にそのファイルの情報が書き込まれます。この構造体は io.h に宣言されていて、次のようになっています。

struct _finddata_t {
  unsigned   attrib;
  time_t time_create;  /* -1 for FAT file systems */
  time_t time_access;  /* -1 for FAT file systems */
  time_t time_write ;
  _fsize_t    size;
  char   name[260];
};

ここで、このメンバーは、

 attrib   ファイルの属性
 time_create 作成日付
 time_access 最終アクセス日付
 time_write 最終更新日付
 size    ファイルサイズ
 name    ファイル名

です。このプログラムで必要なのは、attrib と name の2つのメンバーですので、とりあえずそのメンバーを表示するようにしました。これを実行してみると、

 10 .
 10 ..
 10 STR
 10 PRG
 20 memo.txt
 20 1.1
 20 a.obj
 20 a.exe
 20 aaa.c
 20 LL.BAT
 20 aaa.obj
 20 a.c

と表示されました。ファイル名は問題はないと思いますが、属性については説明が必要です。この属性は1つの値の中に複数の情報を持っています。属性に従ってオンになるビットが決まっています。 io.h の中でこの属性は次のように定義されています。

 #define _A_NORMAL  0x00  /* Normal file - No read/write restrictions */
 #define _A_RDONLY  0x01  /* Read only file */
 #define _A_HIDDEN  0x02  /* Hidden file */
 #define _A_SYSTEM  0x04  /* System file */
 #define _A_SUBDIR  0x10  /* Subdirectory */
 #define _A_ARCH   0x20  /* Archive file */

ですので、10(16進数) は サブディレクトリ、20 はアーカイブ(要するに普通のファイル)ということになります。もし、非表示のサブディレクトリのときには、

 _A_SUBDIR | _A_HIDDEN

と論理ORの値になり、結果としては 12 になります。

この関数 display_total_lines もう少し改修して使えるものにしましょう。表示するものを、各検索ファイルのファイル数と行数の小計、最後にすべてのファイルの総数と行数の合計を表示するため、grand_total_lines も含めて次のように定義しておきます。

      int    total_files=0;  /* ファイル数の小計 */
 unsigned int    total_lines=0;  /* 行数の小計    */
      int grand_total_files=0;  /* ファイル数の総計 */
 unsigned int grand_total_lines=0;  /* 行数の総計    */

関数 display_total_lines は、

 display_total_lines()
 {
  if(file_name_top==NULL){
   printf("\n===== 標準入力 =====\n\n");

   if(display_file_total_lines()== -1)return -1;

   printf("\n==================\n");
   printf("\n合計 %10u 行\n",grand_total_lines);
  }
  else{
              /* *fnn はファイル名のリストを削除するときに使用し
                ます */
   struct file_name_strct *fnc,*fnn;

              /* 検索されたファイル名の表示用の変数 fname を、
                プログラムを簡単にするために初期化しておく */
   fname=new_strcat(NULL,"");

   fnc=file_name_top;
   while(fnc){
              /* 検索するファイル名を表示します */
    printf("\n===== %s =====\n",fnc->fname);

              /* 小計用の変数を初期化しておく */
    total_files=0;
    total_lines=0;

              /* 検索を開始 */
    if(start_search(fnc->fname)){
     do{
              /* 検索されたファイルがファイル(アーカイブ)でない
                ときには次のファイルを検索します */
      if(!is_file())continue;

              /* ファイル数の小計、総計を1つ増やす */
         total_files++;
      grand_total_files++;

              /* 検索されたファイル名を fname にセットし、
                ファイルをオープンする */
      fname[0]='\0';
      fname=new_strcat(fname,file_info.name);

      fp=fopen(fname,"r");
      if(fp==NULL){
       printf(
        "ファイル\n"
        "%s\n"
        "のオープン時にエラーが発生しました。\n"
        "%s",fname,strerror(errno));
      }
      else{
              /* 標準入力の行数でも使っている1つの入力単位の行数
               を表示する関数を呼び出して1つのファイルの行数を
               表示する */
       if(display_file_total_lines()== -1)return -1;

       fclose(fp);
      }
              /* 次のファイルを検索 */
     }while(next_search());
    }

              /* 小計を表示 */
    if(total_files){
     printf("\n小計 %10d ファイル\n",total_files);
     printf( "   %10d 行\n"   ,total_lines);
    }

              /* 使用し終わったファイル名リストを削除 */
    fnn=fnc->next;
    DELETE(fnc->fname);
    DELETE(fnc);
    fnc=fnn;
   }

              /* ファイル名表示用に使用したメモリを解放 */
   DELETE(fname);
   fname=NULL;
              /* 総数を表示 */
   printf("\n========================\n");
   printf("\n合計 %10d ファイル\n",grand_total_files);
   printf( "   %10u 行\n"   ,grand_total_lines);
  }

  return 1;
 }

としました。この改修に伴って1つの入力単位の行数を表示する関数を少し直します。

 display_file_total_lines()
 {
  unsigned int lines;

  char data[READ_BUF_SIZE];
  int ldata,i;

  lines=0;

  while(ldata=fread(data,1,READ_BUF_SIZE,fp)){
   for(i=0;i<ldata;i++){
    if(data[i]=='\n')lines++;
   }
  }

  if(fname)printf(" %7d 行 %s\n",lines,fname);
  else   printf(" %7d 行\n"  ,lines);

     total_lines+=lines; /* 小計に現在の入力単位の行数を加算 */
  grand_total_lines+=lines;

  return 1;
 }

そして、関数 is_file は、

 is_file()
 {
  if(file_info.attrib & _A_ARCH)return 1;

  return 0;
 }

とします。先に説明したように検索されたファイルの属性は attrib メンバーにありますので、このメンバーと _A_ARCH との論理積をとったときに真であれば、通常のアーカイバファイル、偽のときにはその他のものになります。

今回はここまでにしましょう。最後にここまでの全リストです。

 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <malloc.h>
 #include <io.h>

 #define READ_BUF_SIZE  8192

 #define NEW(type,element) (type *)malloc(sizeof(type)*(element))
 #define DELETE(addr)   free(addr)

 #define PROC_MODE_DISPLAY 1
 #define PROC_MODE_HELP  2
 int proc_mode=PROC_MODE_DISPLAY;

 int with_hide   =0;
 int search_sub_directory=0;

 char *fname=NULL;
 FILE *fp=stdin;

 struct file_name_strct{
  char *fname;
  struct file_name_strct *next;
 };
 struct file_name_strct *file_name_top=NULL;
 struct file_name_strct *file_name_end=NULL;

      int    total_files=0;
 unsigned int    total_lines=0;
      int grand_total_files=0;
 unsigned int grand_total_lines=0;

 struct _finddata_t file_info;
 long        find_handle;

 char *new_strcat(char *string1,char *string2);

 void main(argc,argv)
 int  argc;
 char *argv[];
 {
  if(analize_argment(argc,argv)== -1)goto end;

  if(proc_mode==PROC_MODE_HELP){
   /*
   display_help();
   */
  }
  else{
   if(display_total_lines()== -1)goto end;
  }

 end:
  exit(0);
 }

 analize_argment(argc,argv)
 int  argc;
 char *argv[];
 {
  int i;

  for(i=1;i<argc;i++){
   if(stricmp(argv[i],"-?")==0
   || stricmp(argv[i],"/?")==0){
    proc_mode=PROC_MODE_HELP;
    return 1;
   }
   else if(stricmp(argv[i],"-s")==0
      || stricmp(argv[i],"/s")==0)search_sub_directory=1;
   else if(stricmp(argv[i],"-h")==0
      || stricmp(argv[i],"/h")==0)with_hide=1;
   else{
    struct file_name_strct *fnc;

    fnc=NEW(struct file_name_strct,1);
    fnc->fname=new_strcat(NULL,argv[i]);
    fnc->next =NULL;

    if(file_name_end==NULL)file_name_top   =fnc;
    else          file_name_end->next=fnc;

    file_name_end=fnc;
   }
  }

  return 1;
 }

 char *new_strcat(char *string1,char *string2)
 {
  if(string1==NULL){
   string1=NEW(char,strlen(string2)+1);
   strcpy(string1,string2);
  }
  else{
   int l;

   l=strlen(string1)+strlen(string2)+1;

   if(l>_msize(string1))string1=realloc(string1,l);

   strcat(string1,string2);
  }

  return string1;
 }

 display_file_total_lines()
 {
  unsigned int lines;

  char data[READ_BUF_SIZE];
  int ldata,i;

  lines=0;

  while(ldata=fread(data,1,READ_BUF_SIZE,fp)){
   for(i=0;i<ldata;i++){
    if(data[i]=='\n')lines++;
   }
  }

  if(fname)printf(" %7d 行 %s\n",lines,fname);
  else   printf(" %7d 行\n"  ,lines);

     total_lines+=lines;
  grand_total_lines+=lines;

  return 1;
 }

 display_total_lines()
 {
  if(file_name_top==NULL){
   printf("\n===== 標準入力 =====\n\n");

   if(display_file_total_lines()== -1)return -1;

   printf("\n==================\n");
   printf("\n合計 %10u 行\n",grand_total_lines);
  }
  else{
   struct file_name_strct *fnc,*fnn;

   fname=new_strcat(NULL,"");

   fnc=file_name_top;
   while(fnc){
    printf("\n===== %s =====\n",fnc->fname);

    total_files=0;
    total_lines=0;

    if(start_search(fnc->fname)){
     do{
      if(!is_file())continue;

         total_files++;
      grand_total_files++;

      fname[0]='\0';
      fname=new_strcat(fname,file_info.name);

      fp=fopen(fname,"r");
      if(fp==NULL){
       printf(
        "ファイル\n"
        "%s\n"
        "のオープン時にエラーが発生しました。\n"
        "%s",fname,strerror(errno));
      }
      else{
       if(display_file_total_lines()== -1)return -1;

       fclose(fp);
      }
     }while(next_search());
    }

    if(total_files){
     printf("\n小計 %10d ファイル\n",total_files);
     printf( "   %10d 行\n"   ,total_lines);
    }

    fnn=fnc->next;
    DELETE(fnc->fname);
    DELETE(fnc);
    fnc=fnn;
   }

   DELETE(fname);
   fname=NULL;

   printf("\n========================\n");
   printf("\n合計 %10d ファイル\n",grand_total_files);
   printf( "   %10u 行\n"   ,grand_total_lines);
  }

  return 1;
 }

 start_search(search_fname)
 char *search_fname;
 {
  find_handle=_findfirst(search_fname,&file_info);

  if(find_handle== -1)return 0;
  else    return 1;
 }

 next_search()
 {
  if(_findnext(find_handle,&file_info)){
   _findclose(find_handle);
   return 0;
  }

  return 1;
 }

 is_file()
 {
  if(file_info.attrib & _A_ARCH)return 1;

  return 0;
 }

次回は非表示属性ファイルの処理、サブディレクトリの検索の処理機能を追加してなんとか完成させましょう。ではまた次回。

そのお悩み、
アポロ技研に話してみませんか?

アポロ技研でワンランク上の物創りへ!
そのお悩み、アポロ技研に話してみませんか?