Contents

Write command-line apps

What's the point?

  • Command-line applications need to do input and output.
  • dart:io库提供I / O功能.
  • args软件包有助于定义和解析命令行参数.
  • 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

Once you have your app’s dependencies, you can run the app from the command line over any text file, like pubspec.yaml or quote.txt (downloadable file):

$ 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解析命令行参数的结果.

这是使用这些类来解析和存储命令行参数的dcat代码:

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程序中的代码,该代码将行号写入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类提供了属性和静态方法,可帮助您检查和操作文件系统.

For example, if you have a path, you can determine whether the path is a file, a directory, a link, or not found by using the type() method from the FileSystemEntity class. Because the type() method accesses the file system, it performs the check asynchronously.

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);

writeAsString()方法异步写入数据. 它将在写入之前打开文件,并在完成后关闭文件. 要将数据追加到现有文件,可以使用可选参数mode并将其值设置为FileMode.append . 否则,模式为FileMode.write并且文件的先前内容(如果有)将被覆盖.

如果要写入更多数据,可以打开文件进行写入. openWrite()方法返回一个IOSink(与stdin和stderr相同的类型). 您可以继续写入文件直到完成,此时,您必须关闭文件. close()方法是异步的,并返回将来.

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