C言語講座 第4回

コマンドラインからの引数

第1回の練習問題1で、自分の名前をローマ字表記で出力してもらいました。今回は、あらかじめプログラムの中に名前を書いておくのではなく、後から自由に指定できるプログラムを作成してみましょう

名前の指定の仕方はいろいろ考えられますが、プログラムを実行する時に指定するようにしてみましょう。例えば、コマンドラインで

$ showname Nagahama Taro

と入力すると、

Nagahama Taro

と出力されるようにします。ここでのshownameは、これから作るプログラムの名前です。

以下のプログラムを入力し、実行してみましょう。

#include <stdio.h>

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

    for (i = 1; i < argc; i++) {
        printf("%s ", argv[i]);
    }
    printf("\n");

    return 0;
}

プログラムの内容を説明していきます。

まず、3行目のmain()関数のところで、これまでと違い丸括弧の中に引数が入っています。この書き方は、プログラムを実行する時に指定された文字列(先に挙げた例では“Nagahama Taro”になり、これを“コマンドラインの引数”と言います)をプログラムの中で使うための常套句です。

3行目の意味は、「main()関数の中で、整数型の“argc”変数と、文字型へのポインタの配列“argv”を使います」になります。“int argc”が「整数型の“argc”という変数を使う」という意味で、“char *argv[]”が「文字型へのポインタの配列“argv”を使う」という意味になります。関数の定義の部分の丸括弧内では、“,”で区切ることで、この2つのことを同時に指定することができます。

変数argcには、“コマンドラインの引数の数+1”の数が入っています。コマンドラインの引数の数は、スペースで区切られた単語の数になるので、例えば引数が“Nagahama Taro”の場合にはargcには3が入ります。

変数argvには、“実行プログラム名とコマンドラインの引数で指定された文字列”が入っています。例えば引数が“Nagahama Taro”の場合には、argv[0]に実行プログラム名である“showname”、argv[1]に“Nagahama”、argv[2]に“Taro”が入ります。

7行目から9行目ではargcとargvを用いて、実行プログラム名を除いたコマンドラインの引数のみを表示しています。

以上のことを応用し、第1回の練習問題3で作成したプログラム(名前のアルファベット表記をアミノ酸残基の一文字表記に見立て、対応する三文字表記を表示させる)を、入力した任意の名前に対して行うプログラムを作ってみましょう。

プログラムでの大雑把な方針は、

  • コマンドラインの引数から名前の文字列を受け取る。
  • アミノ酸の1文字表記と3文字表記を格納した文字列を作る。
  • 名前のアルファベット表記を一文字ずつ表示する。
  • コマンドラインの引数それぞれについて1文字ずつに分解し、その文字に対応する3文字表記を出力する。

となります。

この方針をプログラム化したものを下に示します。このプログラムを入力し、name2amino.cと名前を付けて保存しましょう。

#include <stdio.h>
#include <string.h>
#include <ctype.h>

int main(int argc, char *argv[])
{
    int  i, j, k;
    char one[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    char *three[] = {"Ala", "Asx", "Cys", "Asp", "Glu",
                     "Phe", "Gly", "His", "Ile", "Xaa",
                     "Lys", "Leu", "Met", "Asn", "Xaa",
                     "Pro", "Gln", "Arg", "Ser", "Thr",
                     "Sec", "Val", "Trp", "Xaa", "Tyr", "Glx"};

    for (i = 1; i < argc; i++) {
        for (j = 0; j < strlen(argv[i]); j++) {
            printf(" %c  ", toupper(argv[i][j]));
        }
    }
 
    printf("\n");
    for (i = 1; i < argc; i++) {
        for (j = 0; j < strlen(argv[i]); j++) {
            for (k = 0; k < strlen(one); k++) {
                if (toupper(argv[i][j]) == one[k]) {
                    printf("%s", three[k]);
                    break;
                }
            }
            printf(" ");
         }
    }
    printf("\n");

    return 0;
 }

上記のプログラムで新しいところは、まず17行目および25行目のtoupper()関数です。この関数はchar型の変数を引数とし、引数で指定したアルファベットを大文字に変換します。ちなみに小文字に変換する関数はtolower()関数になります。これらの関数を使うためには、ctype.hをincludeする必要があります(3行目)。

また、27行目には“break”があります。これはPythonにおける“break”と同じで、forまたはwhileなどの繰り返しから抜け出ます。

22行目からは少し複雑なので簡単に解説します。

22行目のfor文でコマンドラインの引数の数だけループをまわし、23行目のfor分でi番目のコマンドラインの引数の文字列長だけループをまわします。これにより、i番目のコマンドラインの引数における1文字1文字が得られます。

24行目から29行目までで、i番目のコマンドラインの引数におけるj番目の文字に対応する3文字表記を見つけ出して出力します。まず、24行目のfor文で、1文字表記を格納している文字列の長さだけループをまわし、25行目では、i番目のコマンドラインの引数におけるj番目の文字と、k番目の1文字表記を比較します。このとき、コマンドラインの引数は大文字に変換されます(入力された文字列が小文字であるとうまく比較できないため)。25行目の判定式が成り立てば、26行目においてi番目のコマンドラインの引数におけるj番目の文字に対応する3文字表記を出力し、27行目で24行目から始まるループを抜け出ます。これは、一度対応する3文字表記を出力したら、この24行目から始まるループをまわし続ける必要がないためです。

練習問題

  1. 上記のプログラムは、コマンドラインの引数を全く指定しないと改行だけが出力されて終了してしまい、何が起きたかわかりにくい。そこで、コマンドラインの引数を全く指定していない場合はそのことを注意する文章を出力し、実行途中で終了するようにプログラムを改変しなさい。なお、プログラムの実行を途中で終了させるには、exit()関数を用いる(exit()関数の引数には1を指定すること)。
  2. コマンドラインの引数に身長(cm)と体重(kg)を指定すると、BMI値を計算して出力するプログラムを作成しなさい。なお、コマンドラインの引数に数字を指定しても文字列として扱われてしまい、そのままでは計算に用いることはできない。文字列を整数に変換するにはatoi()関数を使い、文字列を小数に変換するにはatof()関数を用いる。これらの関数はいずれもstdlib.hをincludeする必要がある。atof()関数を使ったプログラムの例を下記に示す。
    #include <stdio.h>
    #include <stdlib.h>
     
    int main()
    {
        char  ensyuritsu[] = "3.14";
        float pi, r;
     
        pi = atof(ensyuritsu);
        r  = 3.0;
     
        printf("menseki: %6.2f\n", pi * r * r);
    
        return 0; 
    }