Dart笔记:glob 文件系统遍历

Dart笔记 文件系统遍历工具:glob 模块


作者李俊才 (jcLee95)blog.csdn.net/qq_28550263
邮箱 : 291148484@163.com
本文地址blog.csdn.net/qq_28550263...


很久前介绍过一个 NodeJS 中的类似工具,叫做 fast-glob,可参见《NodeJS文件系统遍历工具:fast-glob》一文(地址:jclee95.blog.csdn.net/article/det...)。这类库对于自己开发底层工具来说是比较常用的。比如在前端中自己去做一些脚手架。本文介绍的 glob库就是 Dart 语言中这样一个类似的工具,在很多常用的命令行工具中,都有它的使用,比如 build_runner 库(一个用于 Dart 代码生成和模块化编译的构建系统),再比如 very_good_cli 等等。当需要自己做类似的 Flutter/Dart 项目的工程化工具时,可以回过头来参考本文中介绍的相关知识。

目 录


1. 概述

glob 库是一个强大的文件系统遍历工具,它提供了一种简洁的方式来描述和匹配文件路径模式。这种模式被称为 glob 模式,它可以包含各种通配符,使得我们可以轻松地匹配多个文件或目录。

glob 库的主要功能是根据给定的 glob 模式来查找和匹配文件系统中的文件和目录。它支持各种通配符,包括 *(匹配任意数量的字符)、?(匹配任意一个字符)、[abc](匹配任意一个列出的字符)等等。此外,它还支持使用 {} 来指定多个模式,以及使用 ** 来匹配任意深度的目录。

在 Dart 和 Flutter 的项目中,glob 库被广泛用于各种工程化工具中,如 build_runner (一个用于 Dart 代码生成和模块化编译的构建系统)和一些cli 工具中。当我们需要开发自己的工程化工具时,glob 库将是一个非常有用的工具。

2. glob 库入门

2.1 安装 glob 库

和安装其它的 Dart/Flutter 模块一样。首先,你需要在项目的 pubspec.yaml 文件中添加 glob 库的依赖。在 dependencies 部分添加以下代码:

yaml 复制代码
  dependencies:
    glob: ^2.1.2

然后,运行 flutter或dart pub get 命令来下载和安装 glob 库:

arduino 复制代码
dart pub get

这样,glob 库就被成功安装到你的项目中了。

或者直接使用 pub add 命令安装当前发布的最新版本:

csharp 复制代码
flutter pub add glob

着将自动添加依赖到 pubspec.yaml 文件中并隐式运行 pub get命令。

2.2 初体验:使用 glob 库进行文件匹配

使用 glob 库进行文件匹配也非常简单。首先,你需要导入 glob 库:

arduino 复制代码
import 'package:glob/glob.dart';

然后,你可以创建一个 Glob 对象,并使用 matches 方法来检查一个路径是否匹配给定的 glob 模式:

go 复制代码
var glob = Glob('**.dart');
if (glob.matches('lib/main.dart')) {
  print('The path matches the glob pattern');
} else {
  print('The path does not match the glob pattern');
}

3. glob 模式的基本语法

3.1 glob 模式中的 通配符

在 glob 模式中,* 字符匹配除 / 之外的零个或多个任何字符。这意味着它可以用来匹配给定目录中匹配模式的所有文件,而不会匹配子目录中的文件。例如,lib/*.dart 将匹配 lib/glob.dart,但不匹配 lib/src/utils.dart

** 是类似 *,但也匹配 /。它对于匹配文件或递归列出目录很有用。例如,lib/**.dart 将匹配 lib/glob.dartlib/src/utils.dart

? 字符匹配除 / 之外的单个字符。与 * 不同,它不会匹配多于或少于一个字符。例如,test?.dart 将匹配 test1.dart,但不匹配 test10.darttest.dart

3.2 glob 模式中的 字符集

[...] 构造匹配几个字符中的一个。它可以包含单个字符,如 [abc],在这种情况下,它将匹配任何这些字符;它可以包含范围,如 [a-zA-Z],在这种情况下,它将匹配任何落在范围内的字符;或者它可以包含两者的混合。它只会匹配一个字符。例如,test[a-zA-Z_].dart 将匹配 testx.darttestA.darttest_.dart,但不匹配 test-.dart

如果它以 ^! 开头,构造将匹配所有未提到的字符。例如,test[^a-z].dart 将匹配 test1.dart,但不匹配 testa.dart

3.3 glob 模式中的 选择器

{...,...} 构造匹配几个选项中的一个,每个选项都是一个 glob。例如,lib/{*.dart,src/*} 匹配 lib/glob.dartlib/src/data.txt。它可以包含大于一个的任何数量的选项,甚至可以包含嵌套的选项。

3.4 glob 模式中的 目录匹配

所有 globs 使用 POSIX 路径语法,包括使用 / 作为目录分隔符,无论它们在哪个平台上。这对于 Windows 根目录也是如此;例如,匹配 C 驱动器中所有文件的 glob 将是 C:/*

默认情况下,globPosix 系统和浏览器上是 区分大小写的,在 Windows 上不区分大小写。

4. 使用 glob 库进行文件系统遍历

4.1 如何使用 glob 库查找文件

使用 glob 库查找文件,你可以使用 Glob.list()Glob.listSync() 方法列出所有匹配 glob 的文件:

javascript 复制代码
import 'package:glob/glob.dart';
import 'package:glob/list_local_fs.dart';

final dartFile = Glob("**.dart");

// 列出当前目录中的所有 Dart 文件。
void main(List<String> arguments) {
  for (var entity in dartFile.listSync()) {
    print(entity.path);
  }
}

在这个例子中,我们创建了一个 glob 模式 **.dart,它会匹配所有的 Dart 文件。然后,我们使用 listSync() 方法列出所有匹配这个模式的文件。

下面是一个新建的项目,运行 glob_demo.dart:

从图中可以看到,其输出结果为:

bash 复制代码
.\bin\glod_demo.dart
.\lib\glod_demo.dart
.\test\glod_demo_test.dart

4.2 如何使用 glob 库查找目录

使用 glob 库查找目录,你可以创建一个 glob 模式来匹配目录,然后使用 Glob.list()Glob.listSync() 方法列出所有匹配 glob 的目录:

javascript 复制代码
import 'package:glob/glob.dart';
import 'package:glob/list_local_fs.dart';

final directory = Glob("**/");

// 列出当前目录中的所有子目录。
void main(List<String> arguments) {
  for (var entity in directory.listSync()) {
    print(entity.path);
  }
}

还是那个demo项目,其运行结果如图:

可以看到输出为:

bash 复制代码
..dart_tool
..dart_tool\package_config.json
..gitignore
.\analysis_options.yaml
.\bin
.\bin\glod_demo.dart
.\CHANGELOG.md
.\lib
.\lib\glod_demo.dart
.\pubspec.lock
.\pubspec.yaml
.\README.md
.\test
.\test\glod_demo_test.dart

在这个例子中,我们创建了一个 glob 模式 **/,它会匹配所有的子目录。然后,我们使用 listSync() 方法列出所有匹配这个模式的目录。

4.3 关于在 glob 库中 是否递归的说明

glob 库中,递归查找是通过 glob 模式中的 ** 来控制的。** 表示匹配任意深度的目录,因此,如果你在 glob 模式中使用了 **,那么 glob 库将会递归地查找所有子目录。

例如,以下代码将递归列出当前目录及其所有子目录中的所有 Dart 文件:

javascript 复制代码
import 'package:glob/glob.dart';
import 'package:glob/list_local_fs.dart';

final dartFile = Glob("**.dart");

// 递归列出当前目录中的所有 Dart 文件。
void main(List<String> arguments) {
  for (var entity in dartFile.listSync()) {
    print(entity.path);
  }
}

在这个例子中,**.dart 会递归匹配当前目录及其所有子目录中的所有 Dart 文件。

如果你只想在当前目录(不包括子目录)中查找文件,你应该使用单个星号 *。例如,以下代码将只列出当前目录中的所有 Dart 文件,不会查找子目录:

javascript 复制代码
import 'package:glob/glob.dart';
import 'package:glob/list_local_fs.dart';

final dartFile = Glob("*.dart");

// 列出当前目录中的所有 Dart 文件。
void main(List<String> arguments) {
  for (var entity in dartFile.listSync()) {
    print(entity.path);
  }
}

在这个例子中,*.dart 只会匹配当前目录中的 Dart 文件,不会匹配子目录中的文件。

5. list 和 listSync函数说明

这两个方法都是 Glob 类的扩展方法,定义在 package:glob/list_local_fs.dart 中。它们都用于列出匹配 glob 模式的文件系统实体(文件、目录等)。

csharp 复制代码
/// Platform specific extensions for where `dart:io` exists, which use the
/// local file system.
extension ListLocalFileSystem on Glob {
  /// Convenience method for [Glob.listFileSystem] which uses the local file
  /// system.
  Stream<FileSystemEntity> list({String? root, bool followLinks = true}) =>
      listFileSystem(const LocalFileSystem(),
          root: root, followLinks: followLinks);

  /// Convenience method for [Glob.listFileSystemSync] which uses the local
  /// file system.
  List<FileSystemEntity> listSync({String? root, bool followLinks = true}) =>
      listFileSystemSync(const LocalFileSystem(),
          root: root, followLinks: followLinks);
}

使用时,需要做以下导入:

arduino 复制代码
import 'package:glob/list_local_fs.dart';

5.1 list 方法

list 方法是一个异步方法,返回一个 Stream 。这个 Stream 包含所有匹配 *glob 模式的文件系统实体。由于它是异步的,所以它不会阻塞主线程。

list 方法接受两个可选参数:rootfollowLinks

  • root 参数用于指定搜索的根目录,如果不指定,则默认为当前目录;
  • followLinks 参数决定是否跟随符号链接,如果设置为 true,则会跟随符号链接,否则不会。

list 方法内部调用了 listFileSystem 方法,并传入了一个 LocalFileSystem 实例,这表示它在本地文件系统上进行操作。

5.1 listSync 方法

listSync 方法是一个同步方法,返回一个 List,包含所有匹配 glob 模式的文件系统实体。由于它是同步的,所以它会立即返回所有匹配的文件系统实体。

listSync 方法也接受 rootfollowLinks 两个可选参数,含义与 list 方法中的相同。

listSync 方法内部调用了 listFileSystemSync 方法,并传入了一个 LocalFileSystem 实例,这表示它在本地文件系统上进行操作。

F. 附录

F.1 Glob 类

dart 复制代码
/// 用于引用 globs 的正则表达式。
final _quoteRegExp = RegExp(r'[*{[?\}],-()]');

/// 用于匹配和列出文件和目录的 glob。
///
/// glob 作为路径匹配整个字符串。虽然 glob 模式使用 POSIX 语法,但它可以匹配 POSIX、Windows 或 URL 路径。它期望路径使用的格式基于 [Glob.new] 的 `context` 参数;默认为当前系统的语法。
///
/// 在与 glob 匹配之前,路径会被规范化,所以例如 glob `foo/bar` 匹配路径 `foo/./bar`。相对 glob 可以匹配绝对路径,反之亦然;globs 和路径都被解释为相对于 `context.current`,默认为当前工作目录。
///
/// 当用作 [Pattern] 时,glob 将根据整个字符串是否匹配 glob 返回一个或零个匹配。这些匹配目前没有捕获组,尽管这可能在未来会改变。
class Glob implements Pattern {
  /// 用于创建此 glob 的模式。
  final String pattern;

  /// 根据此 glob 解释路径的上下文。
  final p.Context context;

  /// 如果为 true,如果路径匹配 glob 本身或递归地包含在匹配的目录中,则路径匹配。
  final bool recursive;

  /// glob 是否区分大小写匹配路径。
  bool get caseSensitive => _ast.caseSensitive;

  /// glob 的解析 AST。
  final AstNode _ast;

  /// 用于实现 [list] 和 [listSync] 的底层对象。
  ///
  /// 这不应在 [_listTreeForFileSystem] 之外直接读取。
  ListTree? _listTree;

  /// 跟踪之前使用的文件系统。如果这改变了,那么
  /// [_listTree] 必须被废弃。
  ///
  /// 这在 [_listTreeForFileSystem] 中处理。
  FileSystem? _previousFileSystem;

  /// [context] 的当前目录是否是绝对的。
  bool get _contextIsAbsolute =>
      _contextIsAbsoluteCache ??= context.isAbsolute(context.current);

  bool? _contextIsAbsoluteCache;

  /// [pattern] 是否可以匹配绝对路径。
  bool get _patternCanMatchAbsolute =>
      _patternCanMatchAbsoluteCache ??= _ast.canMatchAbsolute;

  bool? _patternCanMatchAbsoluteCache;

  /// [pattern] 是否可以匹配相对路径。
  bool get _patternCanMatchRelative =>
      _patternCanMatchRelativeCache ??= _ast.canMatchRelative;

  bool? _patternCanMatchRelativeCache;

  /// 返回 [contents],其中包含在 globs 中有意义的字符,这些字符被反斜杠转义。
  static String quote(String contents) =>
      contents.replaceAllMapped(_quoteRegExp, (match) => '\${match[0]}');

  /// 使用 [pattern] 创建一个新的 glob。
  ///
  /// 根据 [context] 解释与 glob 匹配的路径。默认为系统上下文。
  ///
  /// 如果 [recursive] 为 true,此 glob 不仅匹配和列出它明确匹配的文件和目录,而且还匹配那些下面的任何内容。
  ///
  /// 如果 [caseSensitive] 为 true,此 glob 只匹配和列出那些大小写与 glob 中的字符匹配的文件。否则,它无论大小写都匹配。当 [context] 为 Windows 时,默认为 `false`,否则为 `true`。
  factory Glob(String pattern,
      {p.Context? context, bool recursive = false, bool? caseSensitive}) {
    context ??= p.context;
    caseSensitive ??= context.style == p.Style.windows ? false : true;
    if (recursive) pattern += '{,/**}';

    var parser = Parser(pattern, context, caseSensitive: caseSensitive);
    return Glob._(pattern, context, parser.parse(), recursive);
  }

  Glob._(this.pattern, this.context, this._ast, this.recursive);

  /// 列出在提供的 [fileSystem] 中与 glob 匹配的 [root] 下的所有 [FileSystemEntity]。
  ///
  /// 这与 [Directory.list] 工作方式类似,但它只列出可能包含匹配 glob 的实体的目录。它不保证返回实体的顺序,尽管它确保只返回给定路径的一个实体。
  ///
  /// [root] 默认为当前工作目录。
  ///
  /// [followLinks] 与 [Directory.list] 的工作方式相同。
  Stream<FileSystemEntity> listFileSystem(FileSystem fileSystem,
      {String? root, bool followLinks = true}) {
    if (context.style != p.style) {
      throw StateError("Can't list glob "$this"; it matches "
          '${context.style} paths, but this platform uses ${p.style} paths.');
    }

    return _listTreeForFileSystem(fileSystem)
        .list(root: root, followLinks: followLinks);
  }

  /// 在提供的 [fileSystem] 中,同步列出在 [root] 下的所有与 glob 匹配的 [FileSystemEntity]。
  ///
  /// 这与 [Directory.listSync] 的工作方式类似,但它只列出可能包含匹配 glob 的实体的目录。它不保证返回实体的顺序,尽管它确保只返回给定路径的一个实体。
  ///
  /// [root] 默认为当前工作目录。
  ///
  /// [followLinks] 与 [Directory.list] 的工作方式相同。
  List<FileSystemEntity> listFileSystemSync(FileSystem fileSystem,
      {String? root, bool followLinks = true}) {
    if (context.style != p.style) {
      throw StateError("Can't list glob "$this"; it matches "
          '${context.style} paths, but this platform uses ${p.style} paths.');
    }

    return _listTreeForFileSystem(fileSystem)
        .listSync(root: root, followLinks: followLinks);
  }

  /// 返回此 glob 是否匹配 [path]。
  bool matches(String path) => matchAsPrefix(path) != null;

  @override
  Match? matchAsPrefix(String path, [int start = 0]) {
    // Globs 就像锚定的 RegExps,只匹配整个路径,所以如果匹配从第一个字符之后的任何地方开始,它就不能成功。
    if (start != 0) return null;

    if (_patternCanMatchAbsolute &&
        (_contextIsAbsolute || context.isAbsolute(path))) {
      var absolutePath = context.normalize(context.absolute(path));
      if (_ast.matches(toPosixPath(context, absolutePath))) {
        return GlobMatch(path, this);
      }
    }

    if (_patternCanMatchRelative) {
      var relativePath = context.relative(path);
      if (_ast.matches(toPosixPath(context, relativePath))) {
        return GlobMatch(path, this);
      }
    }

    return null;
  }

  @override
  Iterable<Match> allMatches(String path, [int start = 0]) {
    var match = matchAsPrefix(path, start);
    return match == null ? [] : [match];
  }

  @override
  String toString() => pattern;

  /// 处理获取可能缓存的 [ListTree] 为 [fileSystem]。
  ListTree _listTreeForFileSystem(FileSystem fileSystem) {
    // 不要为内存文件系统使用缓存的树,以避免内存泄漏。
    if (fileSystem is MemoryFileSystem) return ListTree(_ast, fileSystem);

    // 如果文件系统不同,丢弃我们缓存的 `_listTree`。
    if (fileSystem != _previousFileSystem) {
      _listTree = null;
      _previousFileSystem = fileSystem;
    }

    return _listTree ??= ListTree(_ast, fileSystem);
  }
}
相关推荐
kirk_wang16 小时前
Flutter适配HarmonyOS实践
flutter·华为·harmonyos
Jalor17 小时前
HarmonyOS NEXT | 一文搞懂 华为账号登录(获取UnionID/OpenID)
spring boot·flutter·harmonyos
你听得到111 天前
《Flutter性能优化全攻略:从首屏渲染到性能监测,附案例代码详解》
android·flutter
aakzhangliangming1 天前
乐乐音乐Flutter版
flutter·音乐播放器·桌面开发·动感歌词·乐乐音乐·动感歌词转换器
江上清风山间明月1 天前
Flutter DragTarget拖拽控件详解
android·flutter·ios·拖拽·dragtarget
江上清风山间明月2 天前
flutter bottomSheet 控件详解
android·flutter·底部导航·bottomsheet
yuanlaile3 天前
纯Dart Flutter库适配HarmonyOS
flutter·华为·harmonyos·flutter开发鸿蒙·harmonyos教程
yuanlaile3 天前
Flutter开发HarmonyOS 鸿蒙App的好处、能力以及把Flutter项目打包成鸿蒙应用
flutter·华为·harmonyos·flutter开发鸿蒙
zacksleo3 天前
鸿蒙原生开发手记:04-一个完整元服务案例
flutter
jcLee954 天前
Flutter/Dart:使用日志模块Logger Easier
flutter·log4j·dart·logger