编写命令行应用程序
- 使用独立 Dart VM 运行应用程序
- dcat 应用程序代码概述
- 解析命令行参数
- 使用 stdin、stdout 和 stderr 读取和写入
- 获取有关文件的信息
- 读取文件
- 写入文件
- 获取环境信息
- 设置退出代码
- 总结
- 接下来的步骤?
本教程将教你如何构建命令行应用程序,并向你展示一些小型命令行应用程序。 这些程序使用大多数命令行应用程序所需的资源,包括标准输出、错误和输入流、命令行参数、文件和目录等等。
使用独立 Dart VM 运行应用程序
#要在 Dart VM 中运行命令行应用程序,请使用 dart run
。 dart
命令包含在 Dart SDK 中。
让我们运行一个小程序。
创建一个名为
hello_world.dart
的文件,其中包含以下代码:dartvoid 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; // 假设成功
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) {
// 未提供任何文件作为参数。从 stdin 读取并打印每一行。
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
包添加为依赖项。 这会将args
添加到pubspec.yaml
文件中找到的依赖项列表中。$ 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.6.0
8
9 # Add regular dependencies here.
10 dependencies:
11 args: ^2.5.0
12 # path: ^1.8.0
13
14 dev_dependencies:
15 lints: ^5.0.0
16 test: ^1.24.0
此命令显示指定文件的每一行。 因为您指定了 -n
选项,所以在每一行之前都会显示一个行号。
解析命令行参数
#args 包 提供解析器支持,用于将命令行参数转换为一组选项、标志和其他值。 导入包的 args 库 如下所示:
import 'package:args/args.dart';
args
库包含以下类,除此之外还有其他类:
类 | 描述 |
---|---|
ArgParser | 命令行参数解析器。 |
ArgResults | 使用 ArgParser 解析命令行参数的结果。 |
dcat
应用程序中的以下代码使用这些类来解析和存储指定的命令行参数:
void main(List<String> arguments) {
exitCode = 0; // 假设成功
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
属性访问其他值。
API 参考 args
库提供了详细的信息,以帮助您使用 ArgParser
和 ArgResults
类。
使用 stdin、stdout 和 stderr 读取和写入
#与其他语言一样, Dart 具有标准输出、标准错误和标准输入流。 标准 I/O 流在 dart:io
库的顶层定义:
导入 dart:io
库,如下所示:
import 'dart:io';
stdout
#dcat
应用程序中的以下代码将行号写入 stdout
(如果指定了 -n
选项),然后写入文件中的行内容。
if (showLineNumbers) {
stdout.write('${lineNumber++} ');
}
stdout.writeln(line);
write()
和 writeln()
方法接受任何类型的对象,将其转换为字符串并打印它。 writeln()
方法还会打印一个换行符。 dcat
应用程序使用 write()
方法打印行号,以便行号和文本出现在同一行上。
您还可以使用 writeAll()
方法打印对象列表,或使用 addStream()
异步打印流中的所有元素。
stdout
提供比 print()
函数更多的功能。 例如,您可以使用 stdout
显示流的内容。 但是,对于在 Web 上运行的应用程序,必须使用 print()
而不是 stdout
。
stderr
#使用 stderr
将错误消息写入控制台。 标准错误流具有与 stdout
相同的方法,使用方法也相同。 尽管 stdout
和 stderr
都打印到控制台,但它们的输出是分开的,并且可以在命令行或以编程方式重定向或管道传输到不同的目标。
dcat
应用程序中的以下代码在用户尝试输出目录的行而不是文件的行时打印错误消息。
if (await FileSystemEntity.isDirectory(path)) {
stderr.writeln('error: $path is a directory');
} else {
exitCode = 2;
}
stdin
#标准输入流通常从键盘同步读取数据,尽管它可以异步读取并从另一个程序的标准输出中获取管道输入。
这是一个从小程序中读取一行 stdin
的小程序:
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
块中可用之前转换数据。 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';
写入文件
#将文本写入文件的 easiest 方法是创建一个 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
提供其他有用的属性,这些属性提供有关机器、操作系统和当前运行应用程序的信息。例如:
设置退出代码
#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()
函数可以调用 exit(2)
而不是将 exitCode
设置为 2, 但 exit()
将退出程序,并且它可能不会处理运行命令指定的全部文件。
虽然您可以为退出代码使用任何数字,但按照约定,下表中的代码具有以下含义:
代码 | 含义 |
---|---|
0 | 成功 |
1 | 警告 |
2 | 错误 |
总结
#本教程描述了 dart:io
库中以下类中找到的一些基本 API:
API | 描述 |
---|---|
IOSink | 用于从流中使用数据的对象的辅助类 |
File | 表示本机文件系统上的文件 |
Directory | 表示本机文件系统上的目录 |
FileSystemEntity | File 和 Directory 的超类 |
Platform | 提供有关机器和操作系统的 信息 |
stdout | 标准输出流 |
stderr | 标准错误流 |
stdin | 标准输入流 |
exitCode | 访问和设置退出代码 |
exit() | 设置退出代码并退出 |
此外,本教程还介绍了 package:args
中的两个类,它们有助于解析和使用命令行参数: ArgParser
和 ArgResults
。
有关更多类、函数和属性,请查阅 dart:io
、 dart:convert
和 package:args
的 API 文档。
有关命令行应用程序的另一个示例,请查看 command_line
示例。
接下来的步骤?
#如果您对服务器端编程感兴趣,请查看 下一个教程 。
除非另有说明,否则本网站上的文档反映的是 Dart 3.6.0。页面最后更新于 2025-02-05。 查看源代码 或 报告问题.