Dart笔记:glob 文件系统遍历

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

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


很久前介绍过一个 NodeJS 中的类似工具,叫做 fast-glob,可参见《NodeJS文件系统遍历工具:fast-glob》一文(地址:https://jclee95.blog.csdn.net/article/details/129892856)。这类库对于自己开发底层工具来说是比较常用的。比如在前端中自己去做一些脚手架。本文介绍的 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 部分添加以下代码:

  dependencies:
    glob: ^2.1.2

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

dart pub get

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

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

flutter pub add glob

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

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

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

import 'package:glob/glob.dart';

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

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 的文件:

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:
在这里插入图片描述
从图中可以看到,其输出结果为:

.\bin\glod_demo.dart
.\lib\glod_demo.dart
.\test\glod_demo_test.dart

4.2 如何使用 glob 库查找目录

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

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项目,其运行结果如图:
在这里插入图片描述
可以看到输出为:

.\.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 文件:

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 文件,不会查找子目录:

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 模式的文件系统实体(文件、目录等)。

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

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

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 类

/// 用于引用 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;

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/161317.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

机器学习笔记 - 隐马尔可夫模型的简述

隐马尔可夫模型是一个并不复杂的数学模型,到目前为止,它一直被认为是解决大多数自然语言处理问题最为快速、有效的方法。它成功地解决了复杂的语音识别、机器翻译等问题。看完这些复杂的问题是如何通过简单的模型得到描述和解决,我们会由衷地感叹数学模型之妙。 人类信息交流…

Pandas 将DataFrame中单元格内的列表拆分成单独的行

使用 explode 函数 import pandas as pddata {month: [1, 2],week: [[i for i in range(2)], [i for i in range(3)]]} df pd.DataFrame(data) print(df)df df.explode(week) print(df)

【数据结构】栈与队列面试题(C语言)

我们再用C语言做题时&#xff0c;是比较不方便的&#xff0c;因此我们在用到数据结构中的某些时只能手搓或者Ctrlcv 我们这里用到的栈或队列来自栈与队列的实现 有效的括号 有效的括号&#xff0c;链接奉上。 解题思路&#xff1a; 先说结论&#xff1a; 因为我们是在讲栈与…

[C国演义] 哈希的使用和开闭散列的模拟实现

哈希的使用和开闭散列的模拟实现 1. 使用1.1 unordered_map的接口1.2 unordered_set的接口 2. 哈希底层2.1 概念2.2 解决哈希冲突 3. 实现3.1 开放寻址法3.2 拉链法 1. 使用 1.1 unordered_map的接口 构造 void test1() {// 空的unordered_map对象unordered_map<int, in…

【Proteus仿真】【Arduino单片机】LM35温度计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用PCF8574、LCD1602液晶、LM35传感器等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602显示传感器检测温度。 二、软件设计 /* 作者&a…

Jmeter 吞吐量Per User作用

第一点&#xff1a;Per User仅在Total Execution时生效 第二点&#xff1a;Per User 选中后 聚合报告中将统计的的样本数将变成线程组配置的线程数*吞吐量控制器配置的执行样本数量&#xff08;前提是线程组配置执行接口的次数线程数*循环数 大于吞吐量控制器配置的执行样本数…

【脑与认知科学】【n-back游戏】

请参考课堂内容&#xff0c;设计一种测试工作记忆的实验方法&#xff0c;并选择三位同学作为被试测试工作记忆。请画出实验流程图&#xff0c;叙述实验测试目标&#xff0c;并分析实验结果。 举例&#xff1a;一般我们选择n_back来测试对数字或字母的记忆&#xff0c;选择色块实…

Python字典六种类型概述

1. 引言 看到这个标题&#xff0c;你可能会觉得奇怪&#xff0c;事实上在Python的标准库中共有6种字典类型&#xff01;在某些情形下&#xff0c;你可能会觉得标准的Python字典dict&#xff0c;并不能完全符合你的需求。在本文中&#xff0c;我们将讨论Python中其他5个鲜为人知…

【火炬之光-魔灵装备】

文章目录 装备天赋追忆石板技能魂烛刷图策略 装备 头部胸甲手套鞋子武器盾牌项链戒指腰带神格备注盾牌其余的装备要么是召唤物生命&#xff0c;要么是技能等级&#xff0c;鞋子的闪电技能等级加2不是核心&#xff0c;腰带的话主要是要冷却有冷却暗影的技能是不会断的&#xff…

快速提高编码生产力——中国用户如何使用Jetbrains内置的AI助手

文章目录 安装AI助手插件怎么在国内使用改Jetbrains账户Country特殊工具安排上系统设置代理IDE设置代理 使用案例 安装AI助手插件 此功能依赖于AI Assistant插件&#xff0c;您需要安装并启用该插件。 按打开 IDE 设置&#xff0c;然后选择插件。CtrlAltS打开Marketplace选项…

pyqt designer的版本问题

之前我的电脑Windows11 python3.12上安装好了pyqt6后&#xff0c;安装不了pyqt6-tools&#xff0c;导致不能使用designer设计师服务。经过摸索&#xff0c;然来只需要安装qt-tools就够了。qt-tools在plugin包里。比如文章顶部的资源包&#xff0c;下载下来直接使用pip安装该whl…

Linux inotify 文件监控

Linux 内核 2.6.13 以后&#xff0c;引入了 inotify 文件系统监控功能&#xff0c;通过 inotify 可以对敏感目录设置事件监听。这样的功能被也被包装成了一个文件监控神器 inotify-tools。 使用 inotify 进行文件监控的过程&#xff1a; 创建 inotify 实例&#xff0c;获取 i…

小命令,大世界

Linux是一个大系统&#xff0c;功能丰富&#xff0c;好比是一台巨型机器&#xff0c;而命令&#xff0c;就是这台机器的操作台。要想控制好这台机器&#xff0c;用好这台机器&#xff0c;就得会看仪表&#xff0c;会操作各种按钮。《Linux常用命令自学手册》就是介绍如何操作这…

Linux上编译和安装SOFA23.06

前言 你可以直接使用编译安装好的SOFA版本Installing from all-included binaries (v23.06.00)&#xff1a; 如果你想自己编译&#xff0c;可以看我下面写的内容&#xff0c;不过绝大多数是从官网来的&#xff0c;如果和官网有出入&#xff0c;建议还是以官网为准。 在Linux下…

csapp深入理解计算机系统 bomb lab(1)phase_1

实验目的&#xff1a;进一步了解机器级代码&#xff0c;提高汇编语言、调试器和逆向工程等方面原理与技能的掌握。 实验环境&#xff1a;C、linux 实验获取&#xff1a;进入csapp官网&#xff0c;点击linux/x86-64 binary bomb下载实验压缩包。 实验说明&#xff1a;一共有6…

下一代搜索引擎会什么?

现在是北京时间2023年11月18日。聊一聊搜索。 说到搜索&#xff0c;大家首先想到的肯定是谷歌&#xff0c;百度。我把这些定义成上一个时代的搜索引擎。ChatGPT已经火热了有一年的时间了&#xff0c;大家都认为Ai搜索是下一代的搜索。但是AI搜索&#xff0c;需要的是很大算力&a…

Wireshark TS | 应用传输缓慢问题

问题背景 沿用之前文章的开头说明&#xff0c;应用传输慢是一种比较常见的问题&#xff0c;慢在哪&#xff0c;为什么慢&#xff0c;有时候光从网络数据包分析方面很难回答的一清二楚&#xff0c;毕竟不同的技术方向专业性太强&#xff0c;全栈大佬只能仰望&#xff0c;而我们…

前端JS 使用input完成文件上传操作,并对文件进行类型转换

使用input实现文件上传 // 定义一个用于文件上传的按钮<input type"file" name"upload1" />// accept属性用于定义允许上传的文件类型&#xff0c; onchange用于绑定文件上传之后的相应函数<input type"file" name"upload2"…

【0基础学Java第十课】-- 认识String类

10. 认识String类 10.1 String类的重要性10.2 常用方法10.2.1 字符串构造10.2.2 String对象的比较10.2.3 字符串查找10.2.4 转化10.2.5 字符串替换10.2.6 字符串拆分10.2.7 字符串截取10.2.8 字符串的不可变性10.2.9 字符串修改 10.3 StringBuilder和StringBuffer10.3.1 String…