コマンドラインアプリの作成
- スタンドアロン Dart VM でアプリを実行する
- dcat アプリのコードの概要
- コマンドライン引数の解析
- 標準入力、標準出力、標準エラー出力での読み書き
- ファイルに関する情報の取得
- ファイルの読み込み
- ファイルへの書き込み
- 環境情報の取得
- 終了コードの設定
- まとめ
- 次は何をしますか?
このチュートリアルでは、コマンドラインアプリの構築方法を学び、いくつかの小さなコマンドラインアプリケーションを紹介します。これらのプログラムは、標準出力、エラー、入力ストリーム、コマンドライン引数、ファイルやディレクトリなど、ほとんどのコマンドラインアプリケーションが必要とするリソースを使用します。
スタンドアロン Dart VM でアプリを実行する
#Dart VM でコマンドラインアプリを実行するには、dart run を使用します。dart コマンドは Dart SDK に含まれています。
小さなプログラムを実行してみましょう。
- hello_world.dartという名前のファイルを作成し、次のコードを含めますdart- void main() { print('Hello, World!'); }
- 作成したファイルが含まれるディレクトリで、プログラムを実行します - dart run hello_world.dart Hello, World!
Dart ツールは多くのコマンドとオプションをサポートしています。一般的に使用されるコマンドとオプションを表示するには、dart --help を使用します。すべてのオプションを表示するには、dart --verbose を使用します。
dcat アプリのコードの概要
#このチュートリアルでは、コマンドラインにリストされたファイルのコンテンツを表示する dcat という名前の小さなサンプルアプリの詳細を扱います。このアプリは、コマンドラインアプリで利用可能なさまざまなクラス、関数、プロパティを使用します。アプリの各部分と使用されているさまざまな API については、チュートリアルの続きを読んでください。
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 コマンドを使用して、必要なものだけを簡単に作成できます。
- ディレクトリ内で、dart ツールを使用して dcat アプリを作成します。 - dart create dcat
- 作成されたディレクトリに移動します。 - cd dcat
- dcatディレクトリ内で、- dart pub addを使用して- argsパッケージを依存関係として追加します。これにより、- pubspec.yamlファイルにある依存関係のリストに- argsが追加されます。- dart pub add args
- 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 ライブラリを次のようにインポートします。
import 'package:args/args.dart';args ライブラリには、その他にもこれらのクラスが含まれています。
| クラス | 説明 | 
|---|---|
| ArgParser | コマンドライン引数パーサー。 | 
| ArgResults | ArgParserを使用してコマンドライン引数を解析した結果。 | 
dcat アプリの次のコードは、これらのクラスを使用して指定されたコマンドライン引数を解析して格納します。
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 オブジェクトにどのように解析されるかを示しています。
ArgResults を Map のように扱って、名前でフラグとオプションにアクセスできます。rest プロパティを使用して他の値にアクセスできます。
args ライブラリの API リファレンスには、ArgParser および ArgResults クラスの使用に役立つ詳細情報が記載されています。
標準入力、標準出力、標準エラー出力での読み書き
#他の言語と同様に、Dart には標準出力、標準エラー出力、標準入力ストリームがあります。標準 I/O ストリームは dart:io ライブラリのトップレベルで定義されています。
dart:io ライブラリを次のようにインポートします。
import 'dart:io';標準出力
#dcat アプリの次のコードは、行番号を stdout(-n オプションが指定されている場合)に書き込み、その後にファイルからの行の内容を書き込みます。
if (showLineNumbers) {
  stdout.write('${lineNumber++} ');
}
stdout.writeln(line);write() および writeln() メソッドは、任意の型のオブジェクトを受け取り、それを文字列に変換して出力します。writeln() メソッドは改行文字も出力します。dcat アプリは write() メソッドを使用して行番号を出力するため、行番号とテキストが同じ行に表示されます。
writeAll() メソッドを使用してオブジェクトのリストを出力したり、addStream() を使用してストリームのすべての要素を非同期で出力したりすることもできます。
stdout は print() 関数よりも多くの機能を提供します。たとえば、ストリームの内容を stdout で表示できます。ただし、Web で実行されるアプリでは、stdout の代わりに print() を使用する必要があります。
標準エラー出力
#stderr を使用してエラーメッセージをコンソールに出力します。標準エラー出力ストリームは stdout と同じメソッドを持ち、同じように使用します。stdout と stderr はどちらもコンソールに出力しますが、それらの出力は分離されており、コマンドラインまたはプログラムで異なる宛先にリダイレクトまたはパイプすることができます。
dcat アプリの次のコードは、ユーザーがファイルではなくディレクトリの行を出力しようとした場合にエラーメッセージを出力します。
if (await FileSystemEntity.isDirectory(path)) {
  stderr.writeln('error: $path is a directory');
} else {
  exitCode = 2;
}標準入力
#標準入力ストリームは、通常、キーボードから同期的にデータを読み取りますが、非同期で読み取ったり、別のプログラムの標準出力からパイプされた入力を取得したりすることもできます。
標準入力から1行読み取る簡単なプログラムを次に示します。
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 を使用します。
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() を呼び出します。
if (await FileSystemEntity.isDirectory(path)) {
  stderr.writeln('error: $path is a directory');
} else {
  exitCode = 2;
}FileSystemEntity クラスのその他の興味深いメソッドには、isFile()、exists()、stat()、delete()、rename() があり、これらもすべて Future を使用して値を返します。
FileSystemEntity は、File、Directory、Link クラスのスーパークラスです。
ファイルの読み込み
#dcat アプリは、コマンドラインで指定された各ファイルを openRead() メソッドで開きます。このメソッドは Stream を返します。await for ブロックは、ファイルが非同期で読み取られ、デコードされるのを待ちます。データがストリームで利用可能になると、アプリはそれを stdout に出力します。
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 は改行でデータを分割します。
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 ライブラリをインポートする必要があります。
import 'dart:convert';ファイルへの書き込み
#ファイルにテキストを書き込む最も簡単な方法は、File オブジェクトを作成し、writeAsString() メソッドを使用することです。
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 を返します。
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) を使用できます。
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() 関数で終了コードを設定して、実行中にエラーが発生したことを示します。
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 | ネイティブファイルシステム上のディレクトリを表現します。 | 
| FileSystemEntity | File および Directory のスーパークラス | 
| プラットフォーム | マシンとオペレーティングシステムに関する情報を提供します | 
| 標準出力 | 標準出力ストリーム | 
| 標準エラー出力 | 標準エラー出力ストリーム | 
| 標準入力 | 標準入力ストリーム | 
| exitCode | 終了コードにアクセスして設定します | 
| exit() | 終了コードを設定して終了します | 
さらに、このチュートリアルでは、コマンドライン引数の解析と使用に役立つ package:args の2つのクラス、ArgParser および ArgResults を紹介しました。
その他のクラス、関数、プロパティについては、dart:io、dart:convert、および package:args の API ドキュメントを参照してください。
コマンドラインアプリの別の例については、command_line サンプルを確認してください。
次は何をしますか?
#サーバーサイドプログラミングに興味がある場合は、次のチュートリアルを確認してください。