第29回
データ構造(8)~コマンドライン・パラメータの切り出し

オプション・スイッチを調べる構造

このような仕様では、ユーザーは目的に応じてオプション・スイッチを選択するため、その数と内容は不定になります。プログラムは、数も内容も分からない状態に対応しなければなりません。

ループで1つずつ文字列を調べる

プログラムに与えられたパラメータの数と内容が不定の場合、前回紹介した『[ ]内の添字でargvの要素番号を指定する』という以下のような方法は採れません。オプション・スイッチの登場する順序が定まっていないためです。
char *filename, *word;
filename = argv[1];
word = argv[2];

forまたはwhileループでコマンドラインからパラメータ文字列を1つずつ取り出し、その都度内容(オプション・スイッチの記号)を読み取っていく形を採ることになります。

先頭に“-”記号の付くオプション・スイッチが4種類あるため、それをifで判別します。先頭が“-”なら、続く文字がcかnかfかwかを調べ、c以外(n、f、w)ならさらに続く値(number、filename、word)を調べます。

この仕様を満たす処理は、リスト1のようになります。

リスト1:コマンドライン・パラメータを順に取り出して調べる処理(ex2901.c, ex2901.exe)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define bool int        /* 論理型を定義 */
#define _FALSE 0        /* 偽 */
#define _TRUE  !_FALSE  /* 真 */

int main(int argc, char * argv[])
{
  int i;                   /* ループカウンタ */
  char *p;                 /* 各パラメータ(argv[])の受け皿 */

  /* 各パラメータを保持する変数 */
  char *filename;
  char *word;
  bool sw_capt = _FALSE;
  int maxnum = 0;

  /* パラメータの数だけ繰り返す */
  for (i=1; i<argc; i++) {
    p = argv[i];          /* パラメータを順に受け取る */

    if (p[0] == '-') {    /* 先頭が'-'の場合 */
      p++;                /* 次の文字を調べる */

      if (p[0] == 'c') {  /* 1文字を調べるため添字を使う */
        sw_capt = _TRUE;
      }
      /* さらに次の文字を先頭に文字列として扱うため
         ++p と前置インクリメントする */
      else if (p[0] == 'n') {
        maxnum = atoi(++p);
      }
      else if (p[0] == 'f') {
        filename = ++p;
      }
      else if (p[0] == 'w') {
        word = ++p;
      }
      else {
        /* 先頭が'-'でなければ何もしない */
      }
    }
  }
  /* パラメータの示す内容を表示 */
  printf("Caps : %s, MaxNumber : %d, File name : %s, Word : %s\n",
          sw_capt ? "TRUE" : "FALSE", maxnum, filename, word);
}

argvの配列を順に調べていく

リスト1では、forループでパラメータの数(1~argcまで)だけ繰り返し処理を行い、ループを1回実行するごとに文字列を示すchar型ポインタpにargvの配列を順に代入して個々のパラメータを調べています。
for (i=1; i<argc; i++) {
  p = argv[i];

本来なら、調べたパラメータの内容によって実際の処理を切り替えていく訳ですが、リスト1では以下のような変数に値を代入するだけにし、最後にそれら変数の内容(=パラメータの示す設定)をprintf関数で画面に出力します。

-c:bool(int)型の記号定数_TRUE(真)または_FALSE(偽)
-n<number>:numberの値をint型のmaxnumに保存
-f<file name>:文字列file nameのアドレスをchar型ポインタfilenameに保存
-w<word>:文字列wordのアドレスをchar型ポインタwordに保存

サンプル・プログラムの実行結果

このプログラムに4個のコマンドライン・パラメータを与えて実行すると、以下のように表示されます。

実行例1)
C:\CLANG\EXE>ex2901 -c -n8 -fabc.txt -whello
Caps : TRUE, MaxNumber : 8, File name : abc.txt, Word : hello

実行例2)
C:\CLANG\EXE>ex2901 -n12 -fxyz.mem -wwindows
Caps : FALSE, MaxNumber : 12, File name : xyz.mem, Word : windows

先頭の'-'と続く1文字を調べる

forループで順次パラメータ文字列を取り出していく部分は、ポインタ配列argvの添字にカウンタ変数iを充てていきます。この仕組みはお分かりでしょう。

次に、if文で各パラメータ文字列の先頭が'-'かどうかを調べます。このプログラムの仕様では、先頭に'-'記号の付いていないパラメータは存在しません('-'の付かないパラメータは無視されます)。

先頭が'-'であれば、続く文字がc、n、f、wのいずれかであるかを調べるため、pをインクリメントします。

if (p[0] == '-') {    /* 先頭が'-'の場合 */
  p++;                /* 次の文字を調べる */

ポインタと配列の示す先

カウンタ変数iが1の場合、pがargv[1]──最初のパラメータを指しています。そのときp[0]はパラメータの先頭の1文字を示します。

そこでpをインクリメント(1増加)すると、*pは最初のパラメータの『2文字目』を指します。最初のパラメータ文字列が"-c"ならp[0]は'-'を、インクリメントされたpは'-'の次の'c'を示していることになります。

この状態で*pとすると文字列(char型配列)の"c"を示すため、1文字だけを調べるためにp[0]とします。

if (p[0] == 'c') {  /* 1文字を調べるため添字を使う */
  sw_capt = _TRUE;
}

'-n'に続く数値を取得する

パラメータ文字列が"-n"の場合、続いて「数値」が指定されています。そのため、さらにpをインクリメントして『数値を表す文字列』の銭湯を示させ、atoi関数でint型に変換します。

pをインクリメントしてからatoi関数の引数にするため、p++ではなく++pと前置インクリメントしているところに注意してください。p++ではインクリメントされる前のpをatoi関数の引数としますが、++pとするとインクリメントされたpが引数になります。

else if (p[0] == 'n') {
  maxnum = atoi(++p);
}

'-f'と'-w'に続く文字列を取得する

'-f'と'-w'では、続く文字列をそのままファイル名と単語を示す文字列(char型配列)として扱わなければなりません。このときも++pとpを前置インクリメントし、pが'f'あるいは'w'の次の文字を先頭とした文字列を示すようにします。

else if (p[0] == 'f') {
  filename = ++p;
}
else if (p[0] == 'w') {
  word = ++p;
}

便利な三項演算子「?」

リスト1の最後では、各パラメータの値をprintf関数で表示しています。第1引数の書式化文字列の中では、大文字/小文字の区別をするかどうかを示すsw_captの値を%sとして文字列で表示するようにしています。

printf("Caps : %s, MaxNumber : %d, File name : %s, Word : %s\n",
      sw_capt ? "TRUE" : "FALSE", maxnum, filename, word);

それに対応する第2引数には、以下のような式を充てています。
sw_capt ? "TRUE" : "FALSE"

?は次のような書式で用います。
<式> ? <値1> : <値2>

?は3つの項を採る三項演算子で、<式>の評価結果が「真」なら<値1>を、「偽」なら<値2>を返します。

リスト1の場合は、『bool型(実際にはint型)の変数sw_captの値が真(_TRUE)なら文字列"TRUE"を、偽(_FALSE)なら文字列"FALSE"を第1引数内の最初に現れる%sの値に対応させる』ことを意味しています。

三項演算子「?」は、if elseによる制御構造とよく似ています。しかし、if文が条件を調べた結果によって処理の切り替えを行うのに対して、?演算子は真偽を判定した結果によって『値を返し』ます。制御構造ではなく、あくまで演算子なのです。