C言語編 第45章 コマンドライン

この章の概要

この章の概要です。

引数のある main関数

これまでに登場した全てのプログラムにおいて、main関数には引数が存在しませんでした。 しかし、実は引数のある main関数というものが存在します。
C言語の規格上、次の2通りの main関数が使用できることになっています。

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

これ以外の形式の main関数が使われることもありますが、それは規格外のものです。 一応、この章の後ろの方で少し触れています。

もちろん、引数のある形式の方の、第2引数の型は char** と書いても構いません。 仮引数においてのみ、char* と char[] は同じ意味になります(第33章参照)。

この引数の意味を理解し、使いこなせるようになることが、この章の目的となります。
実際に試してみましょう。 まず、単純なプログラムを用意します。

#include <stdio.h>

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

	for( i = 1; i < argc; ++i ){
		puts( argv[i] );
	}

	return 0;
}

このプログラムを今まで通りに普通に実行しても、何も出力されません。 これは、コマンドライン引数を何も渡していないからです。 コマンドライン引数は、前の章で少しだけ登場した、 コマンドプロセッサと呼ばれるプログラムから引き渡される情報です。

コマンドライン引数を渡して確認するには、次のような方法があります。

VisualC++ のプロパティから指定する

VisualC++ のメニューバーから、「プロジェクト」→「プロパティ」を開きます。
「構成プロパティ」→「デバッグ」を選択し、「コマンド引数」のところに、引き渡したい引数を羅列していきます。 例えば、「xyz 100」のように、半角スペースで間を空けて、複数指定することができます。 半角スペースを含んだ文字列を指定したければ、「"xyz 100"」のように、全体を "" で囲んで下さい。

「xyz 100」を設定してから実行すると、次のような結果になります。

実行結果

xyz
100

コマンドプロンプトから実行する (Windows)

コマンドプロンプトを起動して、そこから実行するという方法があります。 むしろ、この方法が基本と言えます。

Windows のスタートメニューから、「ファイル名を指定して実行」を選択し、「名前」の欄に「cmd」と入力して決定します。 これでコマンドプロンプトが起動します。

ここから、cdコマンドを使って、実行ファイルのあるフォルダまで移動します。

C:\Users\master>cd "C:\myprogram\Debug"

そして、次のようにプログラムを実行します。

C:\myprogram\Debug>test xyz 100

"test" は、実行ファイルの名前です。自分のプログラム名(.exeファイルの名前)を指定して下さい。 "xyz" と "100" がコマンドライン引数です。

実行結果

xyz
100

ターミナルから実行する (OS X)

OS X の場合は、ターミナルを起動して、そこから実行できます。

ターミナル上で、cdコマンドを使って、実行ファイルのあるフォルダまで移動します。

username$ cd Desktop/myprogram

そして、次のようにプログラムを実行します。

myprogram username$ ./test xyz 100

"test" は、実行ファイルの名前です。自分のプログラム名(実行ファイルの名前)を指定して下さい。 拡張子が無いと、単に test と指定するだけでは実行できませんが、名前の前に ./ をつけることで、実行させることができます。
"xyz" と "100" がコマンドライン引数です。

実行結果

xyz
100

実行ファイルへのショートカットを経由する (Windows)

Windows のエクスプローラから、実行ファイルのショートカットを作成し、ショートカットの方のプロパティを開きます。 そして、「リンク先」という部分の最後尾に、コマンドライン引数を記述します。

コマンドライン引数の指定

これで、ショートカットの方からプログラムを実行すれば、コマンドライン引数を渡すことができます。

プログラムの説明に戻ります。
第1引数argc は、第2引数argv に含まれている要素数を表しています。 第2引数argv は、文字列を要素とする配列(のアドレス)です。
argv の中身は、argv[0] にプログラムの名前(=実行ファイルの名前から拡張子を抜いたもの)が格納されており、 argv[1] 以降に、コマンドライン引数が、指定された順番通りに格納されています。
なお、コマンドライン引数を渡さなくても、argv[0] にはプログラムの名前が格納されるため、第1引数argc は最低でも 1 になります

コマンドライン引数の利用法

プログラムの実行時に、コマンドライン引数を渡せるようになれば、 これまでに数章に渡って取り上げてきたファイル処理と組み合わせて、より実用性の高いプログラムが作成できるようになります。

ここでは、コマンドライン引数からファイル名を渡し、そのファイルの内容を標準出力に出力するプログラムを作成してみます。

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

int main(int argc, char *argv[])
{
	FILE* fp;
	char buf[80];

	if( argc < 2 ){
		fputs( "コマンドライン引数が不足しています。\n", stderr );
		exit( EXIT_FAILURE );
	}

	fp = fopen( argv[1], "r" );
	if( fp == NULL ){
		fprintf( stderr, "%s のオープンに失敗しました。\n", argv[1] );
		exit( EXIT_FAILURE );
	}

	while( 1 ){
		fgets( buf, sizeof(buf), fp );

		if( feof(fp) ){
			break;
		}
		if( ferror(fp) ){
			fputs( "ファイルの読み取りに失敗しました。\n", stderr );
			exit( EXIT_FAILURE );
		}

		fputs( buf, stdout );
	}

	if( fclose( fp ) == EOF ){
		fputs( "ファイルクローズに失敗しました。\n", stderr );
		exit( EXIT_FAILURE );
	}

	return 0;
}

入力ファイル (test.txt)

aaaaa
bb  b

dddddd

実行結果

aaaaa
bb  b

dddddd

コマンドライン引数には "test.txt" を指定しています。

コマンドライン引数を扱う場合、想定した個数の引数が存在していることを確認するようにしましょう。 今回のプログラムであれば、コマンドライン引数でファイル名が渡されることを想定しているので、 main関数の仮引数argc は 2 になっているはずです( 前述したように、argv[0] にプログラム名が入っているので、最低でも 1 はあります)から、 もし、argc が 2未満であればエラーとしています。
逆に、引数が多すぎるというケースもあり得ますが、今回は単純に無視しています。 これも必要に応じて、処置を施しましょう。

また、今回のようにファイル名が渡されることを想定していても、100 のような整数が渡されるかも知れません。 そういった、誤った入力があっても正しく動作する(あるいはエラーとして処理する)ようなプログラムを書かなければいけません。

リダイレクト

突然ですが、先ほどのサンプルプログラムを、標準入力の内容を出力するように書き換えると次のようになります。

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

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

	while( 1 ){
		fgets( buf, sizeof(buf), stdin );

		if( feof(stdin) ){
			break;
		}
		if( ferror(stdin) ){
			fputs( "ファイルの読み取りに失敗しました。\n", stderr );
			exit( EXIT_FAILURE );
		}

		fputs( buf, stdout );
	}

	return 0;
}

実行結果

aaaaa
aaaaa
bb  b
bb  b


dddddd
dddddd

前の項では、データを、コマンドライン引数で指定したファイルから入力していましたが、これを標準入力に変えただけです。 実は、このように標準入力からデータを受け取るように書かれたプログラムは、ソースコードを変更することなく、 ファイルからデータを受け取るように実行できます。
その方法が、リダイレクトというものです。

リダイレクトとは、標準入力ストリームや標準出力ストリームを、ユーザが任意の位置(通常、何らかのファイル)に切り替える機能です。 例えば、先ほどのプログラムは、標準入力ストリームを test.txt に切り替えてしまえば、前の項のプログラムと同じ結果を生みます。

なお、リダイレクトという機能はC言語のものではなく、コマンドプロセッサが持っているものです。 ほとんどのコマンドプロセッサでは、この機能が使えるはずです。

本章の最初で確認したように、コマンドラインアプリケーションは次のように実行できます。

myprogram

コマンドライン引数があれば、次のようになります。

myprogram test.txt 100

リダイレクトによって、標準入力を test.txt に切り替えるなら、次のようにします。

myprogram < test.txt

対象となるプログラム名に続けて、< を入力し、更に切り替え先を入力します。 この状態で、先ほどの標準入力版のプログラムを実行すると、

入力ファイル (test.txt)

aaaaa
bb  b

dddddd

実行結果

aaaaa
bb  b

dddddd

このように、test.txt の内容が出力されます。


今度は標準出力の方を out.txt に切り替えてみましょう。 次のように実行します。

myprogram > out.txt

こうすると、標準入力(今度はデフォルトの状態)から入力されたデータが、out.txt へ出力されるようになります。

実行結果

aaaaa
bb  b

dddddd

出力ファイル (out.txt)

aaaaa
bb  b

dddddd


標準入力と標準出力のリダイレクトを同時に行うことも可能です。

myprogram < test.txt > out.txt

入力ファイル (test.txt)

aaaaa
bb  b

dddddd

実行結果


出力ファイル (out.txt)

aaaaa
bb  b

dddddd


なお、コマンドライン引数も与える場合には、

myprogram 100 200 < test.txt > out.txt

このように書きます。

環境変数

C言語の規格上は、非標準という扱いですが、main関数に第3の引数を持たせることができる環境もあります。 3つ目の仮引数は、慣習的に envp という名前が付けられ、ここには環境変数が渡されます。

環境変数とは、OS が、複数のプログラム間でデータを共有するための手段の1つとして用意しているもので、 言うなれば、システム全体で使えるグローバル変数のようなものです。 ですから、何に使われているか分からない環境変数を、適当に書き換える行為は非常に危険です。

当サイトとしては、非標準の機能を詳しく説明するつもりはありませんから、main関数の第3引数についてはこれ以上触れませんが、 環境変数を取得するための手段は、れっきとした標準機能として存在していますので、そちらを説明します。

環境変数を取得するためには、getenv関数(⇒リファレンス)を使用します。 この関数は、引数で環境変数の名前を渡すと、その値が返されます。失敗すると NULL が返されます。 なお、使用するには stdlib.h のインクルードが必要です。

char* getenv(const char* name);

実際にこの関数を使って、環境変数PATH の値を出力してみます。

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

int main(int argc, char *argv[])
{
	char* str = getenv( "PATH" );
	if( str == NULL ){
		fputs( "環境変数の取得に失敗しました。\n", stderr );
		exit( EXIT_FAILURE );
	}

	puts( str );

	return 0;
}

実行結果は省略しますが、ファイルパスが幾つか出力されると思います。

この記事を書いているとき、このプログラムを実行してみて何も出力されずに一瞬戸惑いました。 前の項のプログラムで、VisualC++ のプロパティからリダイレクトの設定をしたままになっていたのが原因でした。 コマンドライン引数のテストをした後、よく設定を戻すのを忘れるので、1つのプロジェクトを使いまわしている場合はご注意を。

環境変数PATH は、相対パスでファイルを検索する際に起点となる場所が保存されている変数です。 すべての OS に存在するという保証はありませんが、少なくとも、Windows や OS X には存在しています。

文字列を数値に変換する

コマンドライン引数は char*型として渡されるため、数値を扱う場合に困ります。
例えば、1つ目のコマンドライン引数にファイル名を、2つ目の引数に行数を渡すと、その行の内容を出力するプログラムを作るとします。 その場合、次のように実行することになります。

myprogram test.txt 7

これは、test.txt の 7行目を出力するという意味になります。 このとき「7」の部分は、argv[2] に入っている訳ですが、これは char*型の文字列ですから、なんとか整数として扱えるようにしなければいけません。

結論としては、文字列を整数に変換する標準関数が存在するので、それを使うだけです。 atoi関数(⇒リファレンス)を使えば一発で変換できます。

int atoi(const char* str);

この関数は、引数に、数字として解釈できるような文字で構成された文字列を渡すと、それを整数に変換して返します。 "123abc" のように、数字として解釈できない部分を含む場合、変換できる部分だけを返します。 この場合なら 123 が返されます。

ところが atoi関数には問題があります。 例えば、"abc" のように明らかに数字として解釈できない文字列を渡したとき、それと分かるようなエラー値を返してくれません。 つまり、エラーの検出が不可能なのです。
お手軽な関数ではありますが、エラーになる可能性がある場面では使うべきではありません。 代わりに、strtol関数(⇒リファレンス)を使います。

long int strtol(const char* str, char** endptr, int radix);

第1引数に、変換元となる文字列を渡します。 第2引数に、変換不能な部分の先頭のアドレスを受け取るポインタ変数を渡します。 第3引数に、数値を何進数であるとみなすかを指定します。 戻り値には、変換結果が返されますが、変換が行えなかった場合には 0 が返されます。 また、変換結果が long int型で表現できる範囲を超えてしまう場合には、LONG_MAX(⇒リファレンス)や LONG_MIN(⇒リファレンス) が返され、 更に、errno(⇒リファレンス)に ERANGE(⇒リファレンス)がセットされます。

第2引数が分かりづらいですが、これは例えば、"123abc" を変換しようとした場合には、"123" は変換可能で、"abc" は変換不能ですから、 "abc" の先頭アドレスが返されるということです。
もし、"abc" のように完全に変換不能な場合、第2引数には "abc" の先頭アドレスが返されることになるので、 次の例のように、第1引数に渡したアドレスと、第2引数で受け取ったアドレスが一致します。

char* end;
num = strtol( str, &end, 10 );
if( str == end ){
	/* 変換できなかった */
}

atoi関数と比べると途端に複雑になった感がありますが、エラーが検出できるという面から言えば、strtol関数の方が推奨されます。 エラー検出の方法まで含めると、次のように使うことになります。

char* end;

errno = 0;
num = strtol( str, &end, 10 );
if( errno == ERANGE && (num == LONG_MAX || num == LONG_MIN) ){
	/* 変換結果を表現できない */
}
else if( str == end ){
	/* 変換できない */
}
else{
	/* 成功。num に変換結果が、end に変換できなかった部分の先頭アドレスが入っている */
}

ここで errno は、一部の標準ライブラリ関数が、内部で起こしたエラーを伝えるために使うグローバル変数です。 errno を利用している関数を呼び出す直前で 0 で初期化しておき、関数呼び出しの後で、errno の値を調べれば、エラーの有無が確認できます。
なお、errno を利用する場合は errno.h を include しておきます。

ややこしいのは、strtol関数が 0、LONG_MAX、LONG_MIN のいずれかを返すことが、エラーの発生を意味してはいないという点です。
1文字も変換できずに 0 を返したのか、本当に "0" という文字列を変換したのかは、戻り値だけでは判断できないので、 必ず、第2引数の結果と、第1引数との比較によって判断しなければなりません。
同様に、LONG_MAX や LONG_MIN を返した場合に、それが変換結果が表現不能なのか、たまたま LONG_MAX や LONG_MIN と同じ値の文字列形式が渡されたのかは、 戻り値だけでは判断できません。 こちらは、errno が ERANGE になっていることとセットで判断しなければなりません。

このように、非常に面倒であるため、ついつい atoi関数を使いがちですが、面倒なものは簡単に使えるようにラップすることを考える方が良いでしょう。 例えば、結局のところ、成功したのか失敗したのかにしか興味が無いのであれば、次のように自作関数でラップすれば、簡単に使えるようになります。

/*
	strtol関数のラップ。
	引数:
		str:	変換元の文字列。strtol関数の第1引数と同じ。
		radix:	基数。strtol関数の第3引数と同じ。
		result:	変換結果を受け取るアドレス。NULL は不可。変換に失敗した場合には、何も格納されない。
	戻り値:
		変換が成功したら 0以外、失敗したら 0 が返される。
*/
int my_strtol(const char* str, int radix, long int* result)
{
	long int num;
	char* end;

	assert( str != NULL );
	assert( result != NULL );

	errno = 0;
	num = strtol( str, &end, radix );
	if( errno == ERANGE && (num == LONG_MAX || num == LONG_MIN) ){
		return 0;    /* 変換結果が表現できない */
	}
	else if( str == end ){
		return 0;    /* 1文字も変換できない */
	}

	*result = num;
	return 1;
}

これを使う際には、

long int num;

if( my_strtol( str, 10, &num ) == 0 ){
	/* 失敗 */
}

これだけの記述で済むようになります。


なお、strtol関数の同類として、unsigned long型に変換する strtoul関数(⇒リファレンス)と、 double型に変換する strtod関数(⇒リファレンス)があります。

C99 では更に、float型に変換する strtof関数(⇒リファレンス)、 long double型に変換する strtold関数(⇒リファレンス)、 long long型に変換する strtoll関数(⇒リファレンス)、 unsigned long long型に変換する strtoull関数(⇒リファレンス)といったように、 更に多くの同類が追加されています。

また、逆に、数値から文字列へ変換したいときには、sprintf関数(⇒リファレンス)を使うのが普通です。


練習問題

問題@ エラーメッセージは、標準出力ストリームではなく、標準エラーストリームへ出力すべきです。 その理由を答えて下さい。

問題A コマンドライン引数から、0個以上の整数を受け取り、その合計を標準出力に出力するプログラムを作成して下さい。

問題B コマンドライン引数に、

myprogram 15 * -3

のように、「整数」「演算子」「整数」を渡したとき、全体を計算式とみなして計算結果を標準出力へ出力するプログラムを作成して下さい。 (環境によっては、* の部分がうまく解釈されないかも知れません。その場合は、

myprogram 15 '*' -3

のように、' ' で囲むことを試してみてください。


解答ページはこちら

参考リンク

更新履歴

'2017/4/19 誤字を修正(singed -> signed)

'2015/8/29 flose関数の戻り値もチェックするようにした。

'2014/1/19 OS X に対応。

'2010/7/24 新規作成。



前の章へ

次の章へ

C言語編のトップページへ

Programming Place Plus のトップページへ