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

TOP > アポロレポート > コラム > 第117回 プログラミングについて『作っておくと便利なちょっとしたプログラム その1』
コラム
2025/08/29

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

アポロレポート

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

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

 『ちょっとしたプログラムなんだけど、こんなプログラムが欲しい』と思うことがよくあります。そんな風に感じるのはデータを解析しているときや、プログラムを作っているときが殆どで、暇をもてあましているときには不思議とないものです。私もよくこんな風に思うことがあるのですが、忙しさにかまけて結局作らず仕舞になることが多く、返って時間のかかってしまうことがあります。できる限りはその場で作る余裕のないときにはメモを残しておき手の空いたときに作るようにしているのですが、それでも怠け癖が出てしまい作りそびれてしまうことが多々あります。

 そこで、今回からは作っておくと便利なちょっとしたプログラムをちょっと作ってみることにしましょう。初回の今回は時々私が作っておけばよかったと悔やむプログラムを作ることにします。
 それは、同じような文字列の連続している行の中からユニークな行のみを表示するもので、

 1234567890
 1234567890
 1234567890
 1234567890
 1234657890
 1234567890
 1234576890
 1234567890

といったデータがあったときに、

 1234567890
 1234657890
 1234576890

の行のみを表示するものです。可能ならばその数も表示できるといいかも知れません。
上の例のように人間にとっては非常に判別しづらいものを選択するという目的の他に、下のように判別しやすいもののときにでも、行数が千行、一万行といったときには人間ならばうんざりする上、見落としてしまうこともあるのです。

 1234567890
 1234567890
 1234567890
 abcdefghij
 1234567890
 1234567890
 1234567890

プログラムの開発中とかデータの解析のときに、このようなことに時間をかけてしまうことは決して少なくありません。

ということで今回からは上記の処理を行なうプログラムを作っていくことにします。機能としては、ユニークな行の表示とその個数、そしてもう1つ似たような機能なのですが、

 123456789
 123456789
 123456789
 abcdefghi
 abcdefghi
 abcdefghi
 ABCDEFGHI
 ABCDEFGHI
 123456789
 123456789
 123456789

といったときに連続している同一の行を削除して、

 123456789
 abcdefghi
 ABCDEFGHI
 123456789

と表示する機能も加えることにします。多分このようなことも時間をかけてテキストエディタなどで行なったことがあると思います。

作成するプログラムはDOSで動作するものとします(コンパイラはVisual Cで行ないます)。

プログラムの起動方法は、

 uline [-u|r] [-n] [-c] [file_name]

というように行なうものとします。[]で囲まれたパラメータはオプションで、省略可能とします。ここで各オプションは、

 -u|r は -u または -r の意味で、-u のときにはユニークな行を表示し、-r は連続す
    る同一文字列の行を削除することを指定します。-u も -r も指定されなかった
    ときには -u として動作することにします。もし、両方が指定されたときには後
    のものを優先させます。

 -n  は個数を表示することを意味します。

 -c  は文字列の先頭や末尾に空白があったり、改行のみの行のときに分かりづらいの
    で表示する文字列を () で囲むことを意味します。

 file_name
    は入力ファイル名で、省略されたときには標準入力からの読み込みを行なうこと
    にします。

 結果は常に標準出力に出力することにします。

さっそくプログラムを作っていきましょう。最初はコマンドライン(オプション)の解析を行い、処理の前準備を行なうまでの部分です。

関数外のグローバルな定義は、

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

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

 #define PROC_MODE_ULINE   1
 #define PROC_MODE_RLINE   2

 int proc_mode=PROC_MODE_ULINE;

 int disp_total=0;
 int enclose  =0;

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

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

です。インクルードするヘッダーファイルは、お決まりの stdio.h、stdlib.h、文字列関数の string.h、メモリー関数の malloc.h の3種類です。

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

マクロはこれまでに何度か説明してありますので覚えていると思います。

 #define PROC_MODE_ULINE   1
 #define PROC_MODE_RLINE   2

 int proc_mode=PROC_MODE_ULINE;

は処理の種類の定義で、PROC_MODE_ULINE はユニークな行を表示、PROC_MODE_RLINE は連続する行の削除を意味し、proc_mode=PROC_MODE_ULINE; としているのは初期値(省略値)としてユニーク行の表示を行なう設定にするためです。

 int disp_total=0;

は、個数の表示を行なうか否かの設定で、0 としているのは初期値(省略値)として非表示の設定とします。

 int enclose=0;

は、表示する文字列を () で囲むか否かの設定で、0 としているのは初期値(省略値)として囲まない設定とします。

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

の fname は入力ファイル名です。fp はファイルポインタで、初期値(省略値)としてstdin (標準入力)を設定しておきます。stdin は stdio.h に定義されている値です。

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

の説明は後で行ないます。

次にメイン関数です。

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

 end:
  exit(0);
 }

このプログラムは起動時のパラメータを受け付けますので、main(argc,argv) と記述して、パラメータを受け取るようにします。

 if(analize_argment(argc,argv)== -1)goto end;

は、プログラムに渡されたコマンドラインのパラメータを解析して、処理の種類、個数の表示、入力ファイル名の設定を行なう関数です。戻り値は -1 のときにはエラーがあったことを示すように作成しますので、エラーのときにはラベル end にジャンプし、プログラムを終了するようにします。現在の状態でのメイン関数ではこの goto ~ ラベルは無意味ですが、今後プログラムが成長していくと必要になりますので無意味でも記述しておきます。

関数 analize_argment は、

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

  for(i=1;i<argc;i++){
      if(stricmp(argv[i],"-u")==0)proc_mode=PROC_MODE_ULINE;
   else if(stricmp(argv[i],"-r")==0)proc_mode=PROC_MODE_RLINE;
   else if(stricmp(argv[i],"-n")==0)disp_total=1;
   else if(stricmp(argv[i],"-c")==0)enclose  =1;
   else{
    for(;i<argc;i++){
     if(fname && fname[0])fname=new_strcat(fname," ");
     fname=new_strcat(fname,argv[i]);
    }
   }
  }

  return 1;
 }

パラメータを先頭から順番に検査していき、-u や -n などのオプションのときにはそれらの設定を行い、それ以外のときにはそれ以降のものをファイル名とすることにします。本来DOSはパス名には空白文字は含まれないのですが、WindowsのDOSプロンプト内で使用することも考慮して、空白の含まれている長いファイル名も扱えるようにしておきます。Windowsのパス名やファイル名で連続した2つ以上の空白は1つの空白としても構わないので2つ以上のパラメータに分かれているファイル名は、1つの空白を挟んで連結するようにしています。

 ちょっと余談ですが、WindowsのDOSプロンプト内ではWindowsの長いファイル名は使用できるのですが、空白の入ったファイル名やパス名は基本的に使用できません。これはUNIXでも同じことで、コマンドを解釈するときのパラメータの区切りを空白文字で行なっているからです。ですので、a b というファイルがあったときに、

 dir a b

と入力しても、パラメータが多すぎると叱られることになってしまうのです。ところが、プログラムは a b というファイルを、fopen("a b","r"); などとすることで問題なくオープンすることができるのです。DOSプロンプトのコマンドとして受け付けてくれないからといって、DOSプロンプト内で動作するプログラムがこのようなファイルを扱えないということではないことを覚えておいて下さい。

    if(stricmp(argv[i],"-u")==0)proc_mode=PROC_MODE_ULINE;
 else if(stricmp(argv[i],"-r")==0)proc_mode=PROC_MODE_RLINE;
 else if(stricmp(argv[i],"-n")==0)disp_total=1;
 else if(stricmp(argv[i],"-c")==0)enclose  =1;

の部分の stricmp 関数は string.h に定義されているもので、文字ケースを無視して文字列の比較を行なう関数です。もし、他のコンパイラなどを使うか、UNIXなどで使うときにはこの関数がないときがありますので、

    if(strcmp(argv[i],"-u")==0
    || strcmp(argv[i],"-U")==0)proc_mode=PROC_MODE_ULINE;
 else if(strcmp(argv[i],"-r")==0
    || strcmp(argv[i],"-R")==0)proc_mode=PROC_MODE_RLINE;
 else if(strcmp(argv[i],"-n")==0
    || strcmp(argv[i],"-N")==0)disp_total=1;
 else if(strcmp(argv[i],"-c")==0
    || strcmp(argv[i],"-C")==0)enclose  =1;

と記述するか、stricmp に相当する関数を自作する必要があります。

入力ファイル名を取り込む部分、

 for(;i<argc;i++){
  if(fname && fname[0])fname=new_strcat(fname," ");
  fname=new_strcat(fname,argv[i]);
 }

は、先に new_strcat の説明が必要と思います。この関数は以前テキストファイルを読み込む話しをしたときに、メモリーを再確保して文字列をメモリーに展開したことを思い出して下さい。それを関数化したものです。

string1 に string2 を連結する関数なのですが、この関数が勝手にメモリーを確保してくれるようにしてあります。

 char *new_strcat(char *string1,char *string2)
 {
                /* string1 の値がヌルのときには新規に必要なメモ
                 リーを確保し、代入します */
  if(string1==NULL){
   string1=NEW(char,strlen(string2)+1);
   strcpy(string1,string2);
  }
  else{           /* string1 の値がヌルでないときには、その文字列
                 に string2 を連結しますが、確保してあるメモリ
                 ーサイズが十分でないときには再確保してから連
                 結します */
   int l;

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

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

   strcat(string1,string2);
  }

  return string1;
 }

関数 _msize は確保されているメモリーのサイズを取得する関数で、Visual C固有の関数です。もし移植するときにはこれに相当する関数があればそれを使用し、そうでないときにはプログラムを工夫する必要があります。

この関数のメモリーを再確保する部分でちょっと理解できないような部分があると思います。analize_argment ではこの関数は単純に文字列の連結しか行なわないので、いちいち確保されているメモリーのサイズを検査する必要がないのですが、他の処理のときにでも使用することがあることの他に、もう1つ別の理由があるのです。

 malloc(5);

としてメモリーを確保したときに、OSは本当に5バイトしか確保しないのではないのです。実際には5バイト以上を確保しているのです。試しにWindows95のDOSプロンプトで確認したところ16バイトでした。OSはメモリーを確保するとき、要求されたサイズ以上で、ある値の整数倍のサイズを確保するのです。実際には数バイトなのですが、これを無駄にしないようにするために実際に確保されているバイト数を検査している訳です。
 OSが要求に応じてメモリーに確保するサイズはこのちょっと大き目のサイズの他に、再確保や解放の要求時に正確にそのバイト数を処理するために、何バイト確保したのかという情報を記憶するメモリーも同時に確保しているのです。通常OSが教えてくれたアドレスの数バイト前のアドレスにその情報が含まれています。

今回はここまでです。また次回。

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

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