Contents

Write command-line apps

What's the point?

  • 命令行应用程序需要输入和输出.
  • dart:io库提供I / O功能.
  • The args package helps define and parse command-line arguments.
  • Future对象表示将来某个时候可用的值.
  • 流提供了一系列异步数据事件.
  • 大多数输入和输出需要使用流.

本教程教您如何构建命令行应用程序,并向您展示一些小型命令行应用程序. 这些程序使用大多数命令行应用程序所需的资源,包括标准输出,错误和输入流,命令行参数,文件和目录等.

Running an app with the standalone Dart VM

要运行命令行应用程序,需要安装Dart SDK时随附的Dart VM( dart ).

让我们运行一个小程序.

  1. 创建一个名为helloworld.dart的文件,其中包含以下代码:

    void main() {
      print('Hello, World!');
    }
    
  2. 在包含刚创建的文件的目录中,运行程序:

    $ dart helloworld.dart
    Hello, World!
    

Dart VM支持许多选项. 使用dart --help查看常用选项. 使用dart --verbose查看所有选项.

Overview of the dcat app code

本教程介绍了一个名为dcat的小型示例应用程序的详细信息,该应用程序显示了命令行上列出的所有文件的内容. 此应用程序使用命令行应用程序可用的各种类,函数和属性. 有关应用程序关键功能的简要说明,请单击下面突出显示的代码.

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

import 'package:args/args.dart';

const lineNumber = 'line-number';

ArgResults argResults;

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

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

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

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

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

Getting dependencies

您可能会注意到dcat依赖于名为args的软件包. 要获取args软件包,请使用pub软件包管理器 .

真正的应用程序具有文件分层结构中的测试,许可证文件等,例如Dart命令行应用程序模板中的文件分层结构. 但是,对于第一个应用程序,让我们做最少的工作来使代码运行:

  1. 创建一个名为dcat的目录,然后切换到该目录.
  2. dcat ,创建一个名为dcat.dart的文件,并将前面的代码复制到其中.
  3. dcat ,使用以下代码创建一个名为pubspec.yaml的文件:

    name: dcat
    
    environment:
      sdk: '>=2.6.0 <3.0.0'
    
    dependencies:
      args: ^1.5.0
    
  4. 仍然在dcat目录中,运行pub get获取args包:

    $ pub get
    Resolving dependencies... 
    + args 1.5.2
    Changed 1 dependency!
    

Running dcat

有了应用程序的依赖关系后,就可以在任何文本文件(例如pubspec.yamlquote.txt (可下载文件 ))上从命令行运行该应用程序:

$ dart dcat.dart -n quote.txt
1 Be yourself. Everyone else is taken. -Oscar Wilde
2 Don't cry because it's over, smile because it happened. -Dr. Seuss
3 You only live once, but if you do it right, once is enough. -Mae West
...

此命令显示指定文件的每一行. 因为存在-n参数,所以在每行之前显示行号.

Parsing command-line arguments

args软件包提供了解析器支持,用于将命令行参数转换为一组选项,标志和其他值. 导入软件包的args库 ,如下所示:

import 'package:args/args.dart';

args库包含以下类,其中包括:

Class Description
ArgParser 命令行参数解析器.
ArgResults 使用ArgParser解析命令行参数的结果.

Here is the dcat code that uses these classes to parse and store command-line arguments:

ArgResults argResults;

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


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

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

运行时将命令行参数作为字符串列表传递给应用程序的main()函数. ArgParser配置为解析-n标志. 解析命令行参数的结果存储在argResults .

下图显示了如何将上面使用的dcat命令行解析为ArgResults对象.

Run dcat from the command-line

您可以按名称访问标志和选项,将ArgResults Map . 您可以使用rest属性访问其他值.

args库的API参考提供了详细信息,以帮助您使用ArgParserArgResults类.

Reading and writing with stdin, stdout, and stderr

与其他语言一样,Dart具有标准输出,标准错误和标准输入流. 标准I / O流在dart:io库的顶层定义:

Stream Description
stdout 标准输出
stderr 标准误差
stdin 标准输入

导入dart:io库,如下所示:

import 'dart:io';

stdout

这是dcat程序中的代码, dcat行号写入stdout (如果设置了-n标志),然后是文件中的行.

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

write()writeln()方法采用任何类型的对象,将其转换为字符串,然后打印. writeln()方法还会打印换行符. dcat使用write()方法打印行号,以便行号和文本出现在同一行上.

您还可以使用writeAll()方法打印对象列表,或使用addStream()异步打印流中的所有元素.

stdout提供了比print()函数更多的功能. 例如,您可以使用stdout显示流的内容. 但是,对于转换为并在JavaScript中运行的程序,必须使用print()而不是stdout .

stderr

使用stderr将错误消息写入控制台. 标准错误流具有与stdout相同的方法,并且您以相同的方式使用它. 尽管stdoutstderr打印到控制台,但是它们的输出是独立的,可以在命令行上重定向或通过管道传输,也可以通过编程将其重定向到其他目标.

如果用户尝试列出目录,则来自dcat此代码将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');
  String input = stdin.readLineSync();
  stdout.writeln('You typed: $input');
}

readLineSync()方法从标准输入流中读取文本,直到用户键入文本并按回车键为止,该方法一直处于阻塞状态. 这个小程序将打印出键入的文本.

dcat程序中,如果用户未在命令行上提供文件名,则该程序将使用pipe()方法从标准输入中读取. 因为pipe()是异步的(即使此代码不使用该返回值,也返回一个future),所以调用它的代码使用await .

await stdin.pipe(stdout);

在这种情况下,用户键入文本行,程序将它们复制到stdout. 用户通过按Control + D指示输入结束.

$ dart dcat.dart
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.

Getting info about a file

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()所有这些都还可以使用未来的返回值.

FileSystemEntityFileDirectoryLink类的超类.

Reading a file

dcat使用openRead()方法打开命令行上列出的每个文件,该方法返回一个流. await for块等待文件被异步读取. 当数据在流中可用时,数据将打印到stdout.

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

下面显示了代码的其余部分,该代码使用两个解码器对数据进行转换,然后再将数据用于await for块. UTF8解码器将数据转换为Dart字符串. LineSplitter在换行符LineSplitter拆分数据.

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

dart:convert库包含这些和其他数据转换器,其中包括一个JSON转换器. 要使用这些转换器,您需要导入dart:convert库:

import 'dart:convert';

Writing a file

将文本写入文件的最简单方法是创建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);

The writeAsString() method writes the data asynchronously. It opens the file before writing and closes the file when done. To append data to an existing file, you can use the optional parameter mode and set its value to FileMode.append. Otherwise, the mode is FileMode.write and the previous contents of the file, if any, are overwritten.

如果要写入更多数据,可以打开文件进行写入. openWrite()方法返回一个IOSink(与stdin和stderr相同的类型). 您可以继续写入文件直到完成,此时,您必须关闭文件. 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();

Getting environment information

使用Platform类可获取有关程序正在运行的计算机和操作系统的信息.

Platform.environment在不可变映射中提供了环境变量的副本. 如果需要可变映射(可修改副本),则可以使用Map.from(Platform.environment) .

final envVarMap = Platform.environment;

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

Platform提供了其他有用的属性,这些属性提供了有关计算机,操作系统和当前正在运行的程序的信息. 例如:

Setting exit codes

dart:io库定义一个顶级属性exitCode ,您可以更改该属性以设置Dart VM当前调用的退出代码. 退出代码是从Dart程序传递到父进程的数字,用于指示程序执行的成功,失败或其他状态.

dcat程序在_handleError()函数中设置退出代码,以指示在执行过程中发生错误.

Future _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()会退出程序,并且它可能不会处理命令行上的所有文件.

尽管可以将任何数字用作退出代码,但按照惯例,下表中的代码具有以下含义:

Code Meaning
0 Success
1 Warnings
2 Errors

Summary

本教程描述了在dart:io库的这些类中找到的一些基本API:

API Description
IOSink 消耗流数据的对象的帮助程序类.
File 表示本机文件系统上的文件
Directory 表示本机文件系统上的目录
FileSystemEntity 文件和目录的超类
Platform 提供有关机器和操作系统的信息
stdout 标准输出
stderr 标准误差
stdin 标准输入
exitCode 设置退出代码
exit() 设置退出代码并退出

此外,本教程还介绍了两个可帮助使用命令行参数的类: ArgParserArgResults.

有关更多的类,函数和属性,请参阅dart:io, dart:convertargs包的API参考.

What next?

如果您对服务器端编程感兴趣,请查看下一个教程,其中涵盖了HTTP客户端和服务器 .

by  ICOPY.SITE