メインコンテンツにスキップ

コマンドラインアプリの作成

このチュートリアルでは、コマンドラインアプリの構築方法を学び、いくつかの小さなコマンドラインアプリケーションを紹介します。これらのプログラムは、標準出力、エラー、入力ストリーム、コマンドライン引数、ファイルやディレクトリなど、ほとんどのコマンドラインアプリケーションが必要とするリソースを使用します。

スタンドアロン Dart VM でアプリを実行する

#

Dart VM でコマンドラインアプリを実行するには、dart run を使用します。dart コマンドは Dart SDK に含まれています。

小さなプログラムを実行してみましょう。

  1. hello_world.dart という名前のファイルを作成し、次のコードを含めます

    dart
    void main() {
      print('Hello, World!');
    }
  2. 作成したファイルが含まれるディレクトリで、プログラムを実行します

    dart run hello_world.dart
    Hello, World!

Dart ツールは多くのコマンドとオプションをサポートしています。一般的に使用されるコマンドとオプションを表示するには、dart --help を使用します。すべてのオプションを表示するには、dart --verbose を使用します。

dcat アプリのコードの概要

#

このチュートリアルでは、コマンドラインにリストされたファイルのコンテンツを表示する dcat という名前の小さなサンプルアプリの詳細を扱います。このアプリは、コマンドラインアプリで利用可能なさまざまなクラス、関数、プロパティを使用します。アプリの各部分と使用されているさまざまな API については、チュートリアルの続きを読んでください。

dart
import 'dart:convert';
import 'dart:io';

import 'package:args/args.dart';

const lineNumber = 'line-number';

void main(List<String> arguments) {
  exitCode = 0; // Presume success
  final parser = ArgParser()..addFlag(lineNumber, negatable: false, abbr: 'n');

  ArgResults argResults = parser.parse(arguments);
  final paths = argResults.rest;

  dcat(paths, showLineNumbers: argResults[lineNumber] as bool);
}

Future<void> dcat(List<String> paths, {bool showLineNumbers = false}) async {
  if (paths.isEmpty) {
    // No files provided as arguments. Read from stdin and print each line.
    await stdin.pipe(stdout);
  } else {
    for (final path in paths) {
      var lineNumber = 1;
      final lines = utf8.decoder
          .bind(File(path).openRead())
          .transform(const LineSplitter());
      try {
        await for (final line in lines) {
          if (showLineNumbers) {
            stdout.write('${lineNumber++} ');
          }
          stdout.writeln(line);
        }
      } catch (_) {
        await _handleError(path);
      }
    }
  }
}

Future<void> _handleError(String path) async {
  if (await FileSystemEntity.isDirectory(path)) {
    stderr.writeln('error: $path is a directory');
  } else {
    exitCode = 2;
  }
}

依存関係の取得

#

dcat が args という名前のパッケージに依存していることに気づくかもしれません。args パッケージを取得するには、pub パッケージマネージャーを使用します。

実際のアプリには、テスト、ライセンスファイル、依存関係ファイル、例などが含まれます。しかし、最初のアプリでは、dart create コマンドを使用して、必要なものだけを簡単に作成できます。

  1. ディレクトリ内で、dart ツールを使用して dcat アプリを作成します。

    dart create dcat
  2. 作成されたディレクトリに移動します。

    cd dcat
  3. dcat ディレクトリ内で、dart pub add を使用して args パッケージを依存関係として追加します。これにより、pubspec.yaml ファイルにある依存関係のリストに args が追加されます。

    dart pub add args
  4. bin/dcat.dart ファイルを開き、前のコードをコピーします。

dcat の実行

#

アプリの依存関係が完了したら、コマンドラインから任意のテキストファイル(例: pubspec.yaml)に対してアプリを実行できます。

dart run bin/dcat.dart -n pubspec.yaml
1 name: dcat
2 description: A sample command-line application.
3 version: 1.0.0
4 # repository: https://github.com/my_org/my_repo
5 
6 environment:
7   sdk: ^3.8.0
8 
9 # Add regular dependencies here.
10 dependencies:
11   args: ^2.7.0
12   # path: ^1.8.0
13 
14 dev_dependencies:
15   lints: ^6.0.0
16   test: ^1.25.0

このコマンドは、指定されたファイルの各行を表示します。-n オプションを指定したため、各行の前に番号が表示されます。

コマンドライン引数の解析

#

args パッケージは、コマンドライン引数をオプション、フラグ、追加の値のセットに変換するためのパーサーサポートを提供します。パッケージの args ライブラリを次のようにインポートします。

dart
import 'package:args/args.dart';

args ライブラリには、その他にもこれらのクラスが含まれています。

クラス説明
ArgParserコマンドライン引数パーサー。
ArgResultsArgParser を使用してコマンドライン引数を解析した結果。

dcat アプリの次のコードは、これらのクラスを使用して指定されたコマンドライン引数を解析して格納します。

dart
void main(List<String> arguments) {
  exitCode = 0; // Presume success
  final parser = ArgParser()..addFlag(lineNumber, negatable: false, abbr: 'n');

  ArgResults argResults = parser.parse(arguments);
  final paths = argResults.rest;

  dcat(paths, showLineNumbers: argResults[lineNumber] as bool);
}

Dart ランタイムは、コマンドライン引数をアプリの main 関数に文字列のリストとして渡します。ArgParser-n オプションを解析するように構成されています。次に、コマンドライン引数の解析結果が argResults に格納されます。

次の図は、上記のように使用された dcat コマンドラインが ArgResults オブジェクトにどのように解析されるかを示しています。

Run dcat from the command-line

ArgResultsMap のように扱って、名前でフラグとオプションにアクセスできます。rest プロパティを使用して他の値にアクセスできます。

args ライブラリの API リファレンスには、ArgParser および ArgResults クラスの使用に役立つ詳細情報が記載されています。

標準入力、標準出力、標準エラー出力での読み書き

#

他の言語と同様に、Dart には標準出力、標準エラー出力、標準入力ストリームがあります。標準 I/O ストリームは dart:io ライブラリのトップレベルで定義されています。

ストリーム説明
標準出力標準出力
標準エラー出力標準エラー出力
標準入力標準入力

dart:io ライブラリを次のようにインポートします。

dart
import 'dart:io';

標準出力

#

dcat アプリの次のコードは、行番号を stdout-n オプションが指定されている場合)に書き込み、その後にファイルからの行の内容を書き込みます。

dart
if (showLineNumbers) {
  stdout.write('${lineNumber++} ');
}
stdout.writeln(line);

write() および writeln() メソッドは、任意の型のオブジェクトを受け取り、それを文字列に変換して出力します。writeln() メソッドは改行文字も出力します。dcat アプリは write() メソッドを使用して行番号を出力するため、行番号とテキストが同じ行に表示されます。

writeAll() メソッドを使用してオブジェクトのリストを出力したり、addStream() を使用してストリームのすべての要素を非同期で出力したりすることもできます。

stdoutprint() 関数よりも多くの機能を提供します。たとえば、ストリームの内容を stdout で表示できます。ただし、Web で実行されるアプリでは、stdout の代わりに print() を使用する必要があります。

標準エラー出力

#

stderr を使用してエラーメッセージをコンソールに出力します。標準エラー出力ストリームは stdout と同じメソッドを持ち、同じように使用します。stdoutstderr はどちらもコンソールに出力しますが、それらの出力は分離されており、コマンドラインまたはプログラムで異なる宛先にリダイレクトまたはパイプすることができます。

dcat アプリの次のコードは、ユーザーがファイルではなくディレクトリの行を出力しようとした場合にエラーメッセージを出力します。

dart
if (await FileSystemEntity.isDirectory(path)) {
  stderr.writeln('error: $path is a directory');
} else {
  exitCode = 2;
}

標準入力

#

標準入力ストリームは、通常、キーボードから同期的にデータを読み取りますが、非同期で読み取ったり、別のプログラムの標準出力からパイプされた入力を取得したりすることもできます。

標準入力から1行読み取る簡単なプログラムを次に示します。

dart
import 'dart:io';

void main() {
  stdout.writeln('Type something');
  final input = stdin.readLineSync();
  stdout.writeln('You typed: $input');
}

readLineSync() メソッドは、標準入力ストリームからテキストを読み取ります。ユーザーがテキストを入力してリターンキーを押すまでブロックされます。この小さなプログラムは、入力されたテキストを出力します。

dcat アプリでは、ユーザーがコマンドラインでファイル名を指定しなかった場合、代わりに pipe() メソッドを使用して stdin から読み取ります。pipe() は非同期であるため(このコードでは戻り値を使用していませんが、Future を返します)、それを呼び出すコードは await を使用します。

dart
await stdin.pipe(stdout);

この場合、ユーザーがテキスト行を入力し、アプリがそれらを stdout にコピーします。ユーザーは Control+D (Windows では Control+Z)を押して入力の終了を通知します。

dart run bin/dcat.dart
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.

ファイルに関する情報の取得

#

dart:io ライブラリの FileSystemEntity クラスは、ファイルシステムを検査および操作するのに役立つプロパティと静的メソッドを提供します。

たとえば、パスがある場合、FileSystemEntity クラスの type() メソッドを使用して、そのパスがファイル、ディレクトリ、リンク、または見つからないかどうかを判断できます。type() メソッドはファイルシステムにアクセスするため、非同期でチェックを実行します。

dcat アプリの次のコードは、FileSystemEntity を使用して、コマンドラインで提供されたパスがディレクトリかどうかを判断します。返される Future は、パスがディレクトリかどうかを示すブール値で完了します。チェックは非同期であるため、コードは await を使用して isDirectory() を呼び出します。

dart
if (await FileSystemEntity.isDirectory(path)) {
  stderr.writeln('error: $path is a directory');
} else {
  exitCode = 2;
}

FileSystemEntity クラスのその他の興味深いメソッドには、isFile()exists()stat()delete()rename() があり、これらもすべて Future を使用して値を返します。

FileSystemEntity は、FileDirectoryLink クラスのスーパークラスです。

ファイルの読み込み

#

dcat アプリは、コマンドラインで指定された各ファイルを openRead() メソッドで開きます。このメソッドは Stream を返します。await for ブロックは、ファイルが非同期で読み取られ、デコードされるのを待ちます。データがストリームで利用可能になると、アプリはそれを stdout に出力します。

dart
for (final path in paths) {
  var lineNumber = 1;
  final lines = utf8.decoder
      .bind(File(path).openRead())
      .transform(const LineSplitter());
  try {
    await for (final line in lines) {
      if (showLineNumbers) {
        stdout.write('${lineNumber++} ');
      }
      stdout.writeln(line);
    }
  } catch (_) {
    await _handleError(path);
  }
}

次の部分は、await for ブロックで利用可能になる前にデータを変換する2つのデコーダーを使用するコードの残りの部分を強調しています。UTF8 デコーダーはデータを Dart 文字列に変換します。LineSplitter は改行でデータを分割します。

dart
for (final path in paths) {
  var lineNumber = 1;
  final lines = utf8.decoder
      .bind(File(path).openRead())
      .transform(const LineSplitter());
  try {
    await for (final line in lines) {
      if (showLineNumbers) {
        stdout.write('${lineNumber++} ');
      }
      stdout.writeln(line);
    }
  } catch (_) {
    await _handleError(path);
  }
}

dart:convert ライブラリは、これらおよびその他のデータコンバーター(JSON 用のコンバーターも含む)を提供します。これらのコンバーターを使用するには、dart:convert ライブラリをインポートする必要があります。

dart
import 'dart:convert';

ファイルへの書き込み

#

ファイルにテキストを書き込む最も簡単な方法は、File オブジェクトを作成し、writeAsString() メソッドを使用することです。

dart
final quotes = File('quotes.txt');
const stronger = 'That which does not kill us makes us stronger. -Nietzsche';

await quotes.writeAsString(stronger, mode: FileMode.append);

writeAsString() メソッドはデータを非同期で書き込みます。書き込む前にファイルを開き、完了したらファイルを閉じます。既存のファイルにデータを追加するには、オプションの名前付きパラメータ mode を使用し、その値を FileMode.append に設定します。それ以外の場合、モードはデフォルトで FileMode.write となり、ファイルの内容(存在する場合)は上書きされます。

より多くのデータを書き込みたい場合は、ファイルを開いて書き込むことができます。openWrite() メソッドは IOSink を返します。これは stdin および stderr と同じ型です。openWrite() から返された IOSink を使用すると、完了するまでファイルに書き込みを続けることができます。その場合、ファイルを閉じる必要があります。close() メソッドは非同期であり、Future を返します。

dart
final quotes = File('quotes.txt').openWrite(mode: FileMode.append);

quotes.write("Don't cry because it's over, ");
quotes.writeln('smile because it happened. -Dr. Seuss');
await quotes.close();

環境情報の取得

#

Platform クラスを使用して、アプリが実行されているマシンとオペレーティングシステムに関する情報を取得します。

静的 Platform.environment プロパティは、環境変数のコピーを不変マップとして提供します。変更可能なマップ(変更可能なコピー)が必要な場合は、Map.of(Platform.environment) を使用できます。

dart
final envVarMap = Platform.environment;

print('PWD = ${envVarMap['PWD']}');
print('LOGNAME = ${envVarMap['LOGNAME']}');
print('PATH = ${envVarMap['PATH']}');

Platform は、マシン、OS、および現在実行中のアプリに関する情報を提供するその他の便利なプロパティを提供します。たとえば次のようになります。

終了コードの設定

#

dart:io ライブラリは、トップレベルプロパティ exitCode を定義します。これを変更すると、Dart VM の現在の呼び出しの終了コードを設定できます。終了コードは、アプリの実行の成功、失敗、またはその他の状態を示すために、Dart アプリから親プロセスに渡される数値です。

dcat アプリは、_handleError() 関数で終了コードを設定して、実行中にエラーが発生したことを示します。

dart
Future<void> _handleError(String path) async {
  if (await FileSystemEntity.isDirectory(path)) {
    stderr.writeln('error: $path is a directory');
  } else {
    exitCode = 2;
  }
}

終了コード 2 は、アプリがエラーに遭遇したことを示します。

exitCode を使用する代わりに、トップレベルの exit() 関数を使用することもできます。これは終了コードを設定してアプリをすぐに終了します。たとえば、_handleError() 関数は exitCode を 2 に設定する代わりに exit(2) を呼び出すことができますが、exit() はプログラムを終了し、実行中のコマンドで指定されたすべてのファイルを処理できない可能性があります。

終了コードには任意の数値を使用できますが、慣例により、以下の表のコードは次の意味を持ちます。

コード意味
0成功
1警告
2エラー

まとめ

#

このチュートリアルでは、dart:io ライブラリの次のクラスにある基本的な API について説明しました。

API説明
IOSinkストリームからデータを利用するオブジェクトのヘルパークラス
Fileネイティブファイルシステム上のファイルを表現します。
Directoryネイティブファイルシステム上のディレクトリを表現します。
FileSystemEntityFile および Directory のスーパークラス
プラットフォームマシンとオペレーティングシステムに関する情報を提供します
標準出力標準出力ストリーム
標準エラー出力標準エラー出力ストリーム
標準入力標準入力ストリーム
exitCode終了コードにアクセスして設定します
exit()終了コードを設定して終了します

さらに、このチュートリアルでは、コマンドライン引数の解析と使用に役立つ package:args の2つのクラス、ArgParser および ArgResults を紹介しました。

その他のクラス、関数、プロパティについては、dart:iodart:convert、および package:args の API ドキュメントを参照してください。

コマンドラインアプリの別の例については、command_line サンプルを確認してください。

次は何をしますか?

#

サーバーサイドプログラミングに興味がある場合は、次のチュートリアルを確認してください。