プログラム環境

main()は関数であることがわかった。プログラムの実行はmain()から始まり、そこから他の関数に分岐する。これまで使用してきたmain()の定義は、次の通りである。

void main() {
    // ...
}

この定義によると、main()は引数を受け取らず、値を返さない。実際には、ほとんどのシステムでは、すべてのプログラムは終了時にその環境に値を返す。この値は、終了ステータスまたはリターンコードと呼ばれる。このため、main()の戻り値の型をvoidと指定することは可能だが、実際にはオペレーティングシステムまたは起動環境に値が返される。

main()の戻り値

プログラムは、常に特定の環境にあるエンティティによって起動される。プログラムを起動するエンティティは、ユーザーがプログラムの名前を入力してEnterキーを押すシェル、プログラマが[実行]ボタンをクリックする開発環境などである。

Dや他のいくつかのプログラミング言語では、プログラムはmain()の戻り値によって、その終了状態をその環境に伝える。

リターンコードの正確な意味は、アプリケーションやシステムによって異なる。ほとんどすべてのシステムでは、リターン値が0は正常終了を意味し、その他の値は一般的に何らかのエラーを意味する。ただし、これには例外がある。例えば、OpenVMSでは、偶数値はエラーを意味し、奇数値は成功を意味する。それでも、ほとんどのシステムでは、[0, 125]の範囲の値は安全に使用でき、1から125までの値は、そのプログラムに固有の意味を持つ。

例えば、ディレクトリの内容を表示するために使用される一般的なUnixプログラムlsは、成功すると0、軽微なエラーが発生すると1、重大なエラーが発生すると2を返す。

多くの環境では、ターミナルで最後に実行されたプログラムの戻り値は、$?環境変数で確認できる。例えば、lsに存在しないファイルの一覧を表示するように指示すると、その戻り値が0以外であることが、以下のように$?で確認できる。

注釈:以下のコマンドラインでのやり取りでは、#で始まる行は、ユーザーが入力した行を表している。同じコマンドを試してみたい場合は、#以外の文字を入力する。また、以下のコマンドはdenemeという名前のプログラムを実行している。この名前を、テストするプログラムの名前に置き換える。

さらに、以下の例はLinuxターミナルでの対話操作を示しているが、他のオペレーティングシステムのターミナルでも同様だが、まったく同じとは限らない。

ls a_file_that_does_not_exist
ls: cannot access a_file_that_does_not_exist: No such file
or directory
echo $?
2      ← lsの戻り値
Bash
main()は常に値を返す

これまで作成したプログラムの中には、タスクを続行できない場合に例外をスローするものがあった。これまで見てきたように、例外がスローされると、プログラムは"object.Exception"というエラーメッセージで終了する。

その場合、main()voidを返すように定義されていても、プログラムの環境に非ゼロのステータスコードが自動的に返される。次の例外で終了するシンプルなプログラムで、この動作を確認しよう。

void main() {
    throw new Exception("There has been an error.");
}

戻り値の型はvoidと指定されているが、戻り値は0以外だ。

./deneme
object.Exception: エラーが発生した。
...
echo $?
1
Bash

同様に、正常に終了するvoid main()関数も、自動的に0を戻り値として返す。これを、正常に終了する次のプログラムで見てみよう。

import std.stdio;

void main() {
    writeln("Done!");
}

プログラムは0を返す:

./deneme
完了!
echo $?
0
Bash
戻り値の指定

特定の戻りコードを選択するには、他の関数と同じように、main()から値を返す。戻り値の型はintと指定し、値はreturn文で返す必要がある。

import std.stdio;

int main() {
    int number;
    write("Please enter a number between 3 and 6: ");
    readf(" %s", &number);

    if ((number < 3) || (number > 6)) {
        stderr.writefln("ERROR: %s is not valid!", number);
        return 111;
    }

    writefln("Thank you for %s.", number);

    return 0;
}
D

入力した数値が有効範囲内にある場合、プログラムの戻り値は0になる。

./deneme
3から6までの数字を入力してください: 5
5、ありがとう。
echo $?
0
Bash

数値が有効範囲外の場合、プログラムの戻り値は111になる。

./deneme
3から6までの数字を入力してください: 10
エラー: 10は有効ではない!
echo $?
111
Bash

上記のプログラムでは、値111は任意だが、通常は1が失敗コードとして適している。

標準エラーストリームstderr

上記のプログラムは、stderrストリームを使用している。このストリームは、標準ストリームの3番目のもので、エラーメッセージの書き込みに使用される:

プログラムをターミナルで実行すると、通常、stdoutstderrに書き込まれるメッセージはどちらもターミナルウィンドウに表示される。必要に応じて、これらの出力を個別にリダイレクトすることも可能だ。このテーマは、この章の焦点外であり、シェルプログラムごとに詳細が異なる場合がある。

パラメーターmain()

プログラムは、それを起動した環境からパラメータを受け取るのが一般的だ。例えば、先ほど、lsにファイル名をコマンドラインオプションとして渡した。次の行には2つのコマンドラインオプションがある。

ls -l deneme
-rwxr-xr-x 1 acehreli ユーザー 460668 11月6日 20:38 deneme
Bash

コマンドラインパラメーターのセットとその意味は、プログラムによって完全に定義される。すべてのプログラムは、各パラメーターの意味を含む使用方法を文書化している。

Dプログラムを開始するときに使用される引数は、stringのスライスとしてそのプログラムのmain()に渡される。main()を、string[]型のパラメータを受け取るものとして定義すれば、プログラムの引数にアクセスできる。このパラメータの名前は、通常、argsと略される。次のプログラムは、起動時に指定されたすべての引数を表示する。

import std.stdio;

void main(string[] args) {
    foreach (i, arg; args) {
        writefln("Argument %-3s: %s", i, arg);
    }
}

任意の引数でプログラムを実行しよう:

./deneme some arguments on the command line 42 --an-option
引数 0  : ./deneme
引数 1  : some
引数 2  : arguments
引数 3  : on
引数 4  : the
引数 5  : command
引数 6  : line
引数 7  : 42
引数 8  : --an-option
Bash

ほとんどすべてのシステムでは、最初の引数は、ユーザーが入力したとおりにプログラムの名前になる。他の引数は、入力した順番で表示される。

引数の使用方法はプログラムに完全に依存する。次のプログラムは、2つの引数を逆順で表示する:

import std.stdio;

int main(string[] args) {
    if (args.length != 3) {
        stderr.writefln("ERROR! Correct usage:\n" ~
                        "  %s word1 word2", args[0]);
        return 1;
    }

    writeln(args[2], ' ', args[1]);

    return 0;
}

プログラムは、正確に2つの単語を入力しない場合、正しい使用方法を表示する:

./deneme
エラー! 正しい使い方:
  ./deneme word1 word2
./deneme world hello
こんにちは、世界
Bash
コマンドラインオプションとstd.getoptモジュール

main()のパラメータと戻り値について知っておくべきことは、以上だ。しかし、引数の解析は繰り返しの作業だ。std.getoptモジュールは、プログラムのコマンドラインオプションの解析を支援するために設計されている。

上記の"world"や"hello"のようなパラメータは、プログラムが使用する純粋なデータだ。他の種類のパラメータはコマンドラインオプションと呼ばれ、プログラムの動作を変更するために使われる。コマンドラインオプションの例としては、上記のlsに渡された-lオプションがある。

コマンドラインオプションは、人間ユーザーがプログラムとインタラクションせずに特定の動作を実行できるようにすることで、プログラムの利便性を高める。コマンドラインオプションを使用すると、プログラムをスクリプトプログラムから起動し、その動作をコマンドラインオプションで指定できる。

各プログラムのコマンドライン引数の構文と意味は、そのプログラムに固有のものだが、その形式は多少標準化されている。例えば、POSIXでは、コマンドラインオプションは--で始まり、その後にオプションの名前、そして=文字の後に値が続く。

./deneme --an-option=17
Bash

std.getoptモジュールは、このようなオプションの解析を簡素化する。このセクションで説明する機能よりも多くの機能を備えている。

乱数を表示するプログラムを作成しよう。これらの乱数の最小値、最大値、および総数をプログラムの引数として受け取る。コマンドラインからこれらの値を取得するには、次の構文を使用しよう。

./deneme --count=7 --minimum=10 --maximum=15
Bash

getopt()関数は、これらの値を解析して変数に代入する。readf()と同様に、変数のアドレスは&演算子で指定する必要がある。

import std.stdio;
import std.getopt;
import std.random;

void main(string[] args) {
    int count;
    int minimum;
    int maximum;

    getopt(args,
           "count", &count,
           "minimum", &minimum,
           "maximum", &maximum);

    foreach (i; 0 .. count) {
        write(uniform(minimum, maximum + 1), ' ');
    }

    writeln();
}
./deneme --count=7 --minimum=10 --maximum=15
11 11 13 11 14 15 10
Bash

ほとんどのプログラムのコマンドラインオプションには、より短い構文も存在する。例えば、-c--countと同じ意味になる。各オプションのこのような代替構文は、|文字の後にgetopt()で指定する。各オプションには複数のショートカットを設定することができる。

getopt(args,
       "count|c", &count,
       "minimum|n", &minimum,
       "maximum|x", &maximum);
D

短いバージョンには単一のダッシュを使用するのが一般的で、=文字は通常、省略されるか、スペースに置き換えられる。

./deneme -c7 -n10 -x15
11 13 10 15 14 15 14
./deneme -c 7 -n 10 -x 15  
11 13 10 15 14 15 14
Bash

getopt()は、引数をstringから各変数の型に変換する。例えば、上記のcountintであるため、getopt()は、--count引数で指定された値をintに変換する。必要に応じて、このような変換はtoで明示的に実行することもできる。

これまで、stringへの変換にのみstd.conv.toを使用してきた。toは、実際には、変換が可能であれば、あらゆる型からあらゆる型に変換することができる。例えば、次のプログラムは、引数をsize_tに変換する際にtoを利用している。

import std.stdio;
import std.conv;

void main(string[] args) {
    // デフォルトのカウントは10
    size_t count = 10;

    if (args.length > 1) {
        // 引数がある
        count = to!size_t(args[1]);
    }

    foreach (i; 0 .. count) {
        write(i * 2, ' ');
    }

    writeln();
}

引数が指定されていない場合、プログラムは10個の数字を出力する:

./deneme
0 2 4 6 8 10 12 14 16 18
./deneme 3
0 2 4
Bash
環境変数

プログラムが起動される環境は、通常、そのプログラムが利用できるいくつかの変数を提供している。環境変数には、std.process.environmentの連想配列インターフェースからアクセスできる。例えば、次のプログラムは、PATH環境変数を表示する。

import std.stdio;
import std.process;

void main() {
    writeln(environment["PATH"]);
}

出力:

./deneme
/usr/local/bin:/usr/bin
Bash

std.process.environmentは、連想配列の構文を使用して環境変数にアクセスする。ただし、environment自体は連想配列ではない。必要に応じて、toAA()を使用して環境変数を連想配列に変換することができる:

string[string] envVars = environment.toAA();
D
他のプログラムの起動

プログラムは他のプログラムを開始し、そのプログラムの環境になることができる。これを可能にする関数は、std.processモジュールにあるexecuteShellだ。

executeShellは、そのパラメータを、ターミナルでコマンドが入力されたかのように実行する。そして、そのコマンドの戻りコードと出力をタプルとして返す。タプルは配列のような構造体で、タプルに関する章で後で説明する。

import std.stdio;
import std.process;

void main() {
    const result = executeShell("ls -l deneme");
    const returnCode = result[0];
    const output = result[1];

    writefln("ls returned %s.", returnCode);
    writefln("Its output:\n%s", output);
}

出力:

./deneme
lsは0を返した。
その出力:
-rwxrwxr-x. 1 acehreli acehreli 1359178 4月21日 15:01 deneme
Bash
要約
演習
  1. 演算子と2つのオペランドをコマンドライン引数として受け取る計算機プログラムを作成しよう。プログラムは、以下の使用方法をサポートしよう。
    ./deneme 3.4 x 7.8
    26.52
    Bash

    注釈: *文字は、ほとんどの端末(より正確には、ほとんどのシェル)で特別な意味を持つため、代わりにxを使用している。\*とエスケープすれば*を使用しても問題はない。

  2. ユーザーに起動するプログラムを尋ね、そのプログラムを起動し、その出力を表示するプログラムを作成しよう。