在前几节课中,我们学习了库与包管理,掌握了代码组织和依赖管理的核心技能。今天我们将探索 Dart 在文件操作和命令行工具开发方面的能力。这些技能在数据处理、自动化脚本、工具开发等场景中非常实用,能极大提升你的开发效率。
一、文件操作基础:使用 dart:io 库
Dart 标准库中的 dart:io 提供了丰富的文件和目录操作 API,支持读取、写入、删除文件以及目录管理等功能。
1. 引入 dart:io 库
使用文件操作前,需要先导入 dart:io 库:
import 'dart:io';
2. 读取文件内容
读取文件有多种方式,根据文件大小和需求选择合适的方法:
(1)一次性读取小文件
在项目根目录下新建example.txt, 写入任意内容。对于小型文本文件,可以用 readAsString() 一次性读取全部内容:
import 'dart:io';
void main() async {
// 定义文件路径
final file = File('example.txt');
try {
// 读取文件内容(异步操作)
String content = await file.readAsString();
print('文件内容:');
print(content);
} catch (e) {
print('读取文件失败:$e');
}
}
(2)按行读取文件
在项目根目录下新建large_file.txt, 写入任意内容。对于大型文件,按行读取更高效,避免占用过多内存:
import 'dart:convert';
import 'dart:io';
void main() async {
final file = File('large_file.txt');
try {
// 按行读取(返回 Stream<String>)
Stream<String> lines = file
.openRead()
.transform(utf8.decoder)
.transform(LineSplitter());
await for (String line in lines) {
print('行内容:$line');
// 可以在这里添加处理逻辑,如查找特定内容
}
} catch (e) {
print('读取文件失败:$e');
}
}
需要额外导入编码和行分割器:
import 'dart:convert'; // 提供 utf8 编码
3. 写入文件内容
写入文件同样有多种方式,支持覆盖写入或追加内容:
(1)覆盖写入文件
import 'dart:io';
void main() async {
final file = File('output.txt');
try {
// 写入字符串(覆盖原有内容)
await file.writeAsString('Hello, Dart 文件操作!\n这是第二行内容');
print('文件写入成功');
} catch (e) {
print('文件写入失败:$e');
}
}
(2)追加内容到文件
import 'dart:io';
void main() async {
final file = File('output.txt');
try {
// 追加内容(mode: FileMode.append)
await file.writeAsString('\n这是追加的内容', mode: FileMode.append);
print('内容追加成功');
} catch (e) {
print('追加失败:$e');
}
}
(3)写入字节数据
根目录下放入一张original_image.png图片,对于二进制文件(如图片、音频),使用 writeAsBytes():
import 'dart:io';
void main() async {
final imageFile = File('image_copy.png');
final sourceFile = File('original_image.png');
try {
// 读取字节数据
List<int> bytes = await sourceFile.readAsBytes();
// 写入字节数据
await imageFile.writeAsBytes(bytes);
print('二进制文件复制成功');
} catch (e) {
print('文件复制失败:$e');
}
}
4. 目录操作
除了文件,dart:io 也支持目录的创建、删除和遍历:
import 'dart:io';
void main() async {
// 定义目录路径
final dir = Directory('my_files');
// 检查目录是否存在
if (!await dir.exists()) {
// 创建目录(recursive: true 表示创建多级目录)
await dir.create(recursive: true);
print('目录创建成功');
}
// 遍历目录中的文件和子目录
await for (FileSystemEntity entity in dir.list()) {
if (entity is File) {
print('文件:${entity.path}');
} else if (entity is Directory) {
print('目录:${entity.path}');
}
}
// 删除目录(recursive: true 表示删除目录及其中所有内容)
// await dir.delete(recursive: true);
// print('目录删除成功');
}
二、命令行参数解析:使用 args 包
开发命令行工具时,通常需要解析用户输入的参数(如 --help、-o output.txt 等)。args 包是 Dart 生态中处理命令行参数的首选工具。
1. 添加 args 依赖
在 pubspec.yaml 中添加依赖:
dependencies:
args: ^2.7.0
执行 dart pub get 安装依赖。
2. 基本参数解析
import 'package:args/args.dart';
void main(List<String> arguments) {
// 创建参数解析器
final parser = ArgParser()
..addFlag('help', abbr: 'h', help: '显示帮助信息', negatable: false)
..addOption('output', abbr: 'o', help: '指定输出文件路径')
..addOption(
'mode',
help: '运行模式',
allowed: ['debug', 'release'],
defaultsTo: 'debug',
);
try {
// 解析参数
final results = parser.parse(arguments);
// 处理 --help 参数
if (results['help'] as bool) {
print('使用方法:dart script.dart [选项]');
print(parser.usage);
return;
}
// 获取其他参数值
print('输出文件:${results['output'] ?? '未指定'}');
print('运行模式:${results['mode']}');
print('位置参数:${results.rest}'); // 未解析的位置参数
} on FormatException catch (e) {
print('参数错误:$e');
print('使用 --help 查看帮助');
}
}
参数类型说明:
addFlag:布尔值参数(如--help),abbr表示短选项(如-h)addOption:带值的选项(如--output file.txt)results.rest:获取未解析的位置参数
运行示例:
dart script.dart -o result.txt --mode release input1.txt input2.txt
输出:
输出文件:result.txt
运行模式:release
位置参数:[input1.txt, input2.txt]
三、实战:批量重命名文件脚本
结合文件操作和命令行参数解析,我们来开发一个实用的批量重命名工具。该工具支持:
- 指定目标目录
- 添加前缀 / 后缀
- 按序号重命名
- 预览重命名效果(不实际修改)
完整代码实现
import 'dart:io';
import 'package:args/args.dart';
import 'package:path/path.dart' as path;
void main(List<String> arguments) async {
// 创建参数解析器
final parser = ArgParser()
..addFlag('help', abbr: 'h', help: '显示帮助信息', negatable: false)
..addFlag('preview', abbr: 'p', help: '预览重命名效果,不实际修改', negatable: false)
..addOption('dir', abbr: 'd', help: '目标目录路径', defaultsTo: '.')
..addOption('prefix', abbr: 'x', help: '文件名前缀')
..addOption('suffix', abbr: 's', help: '文件名后缀')
..addFlag('number', abbr: 'n', help: '添加序号', negatable: false)
..addOption('start', abbr: 't', help: '序号起始值', defaultsTo: '1');
try {
final results = parser.parse(arguments);
// 显示帮助信息
if (results['help'] as bool) {
print('批量重命名工具');
print('用法:dart rename.dart [选项]');
print(parser.usage);
return;
}
// 解析参数
final previewMode = results['preview'] as bool;
final targetDir = Directory(results['dir'] as String);
final prefix = results['prefix'] as String? ?? '';
final suffix = results['suffix'] as String? ?? '';
final addNumber = results['number'] as bool;
final startNumber = int.tryParse(results['start'] as String) ?? 1;
// 检查目录是否存在
if (!await targetDir.exists()) {
print('错误:目录不存在 - ${targetDir.path}');
return;
}
// 获取目录中的文件(排除子目录)
final files = await targetDir
.list()
.where((entity) => entity is File)
.toList();
files.sort((a, b) => a.path.compareTo(b.path)); // 按路径排序
if (files.isEmpty) {
print('目录中没有文件:${targetDir.path}');
return;
}
// 批量重命名
print('${previewMode ? '预览' : '执行'}重命名(共 ${files.length} 个文件):');
int currentNumber = startNumber;
for (final file in files.cast<File>()) {
// 获取文件名和扩展名
final fileName = path.basename(file.path);
final extension = fileName.contains('.')
? '.${fileName.split('.').last}'
: '';
final baseName = extension.isNotEmpty
? fileName.substring(0, fileName.length - extension.length)
: fileName;
// 构建新文件名
String newName = '$prefix$baseName$suffix';
if (addNumber) {
newName = '${newName}_$currentNumber';
currentNumber++;
}
newName += extension;
// 构建新路径
final newPath = '${targetDir.path}${Platform.pathSeparator}$newName';
// 显示或执行重命名
print('${file.path} → $newPath');
if (!previewMode && file.path != newPath) {
await file.rename(newPath);
}
}
print('${previewMode ? '预览' : '重命名'}完成');
} on FormatException catch (e) {
print('参数错误:$e');
print('使用 --help 查看帮助');
} catch (e) {
print('操作失败:$e');
}
}
代码解析
- 参数定义:通过
args包定义了多种参数,满足不同重命名需求。 - 目录检查:验证目标目录是否存在,避免错误。
- 文件处理:
- 过滤出目录中的文件(排除子目录)
- 按路径排序,确保重命名顺序一致
- 文件名构建:
- 分离文件名和扩展名
- 根据前缀、后缀、序号等参数生成新文件名
- 执行重命名:支持预览模式(仅显示效果)和实际执行模式。
使用示例
- 预览当前目录文件添加前缀 "photo_" 并加序号的效果:
dart rename.dart -p -x photo_ -n
- 对
images目录的文件添加后缀 "_edited":
dart rename.dart -d images -s _edited
四、其他实用文件操作技巧
1. 获取文件信息
import 'dart:io';
void printFileInfo(File file) async {
final stat = await file.stat();
print('路径:${file.path}');
print('大小:${stat.size} 字节');
print('修改时间:${stat.modified}');
print('是否为文件:${stat.type == FileSystemEntityType.file}');
}
2. 复制文件
import 'dart:io';
Future<void> copyFile(String sourcePath, String targetPath) async {
final source = File(sourcePath);
final target = File(targetPath);
if (await source.exists()) {
await source.copy(targetPath);
print('复制成功:$targetPath');
} else {
print('源文件不存在:$sourcePath');
}
}
3. 递归遍历目录
import 'dart:io';
import 'package:path/path.dart' as path;
Future<void> listAllFiles(Directory dir, {int indent = 0}) async {
final spaces = ' ' * indent;
await for (final entity in dir.list()) {
if (entity is Directory) {
print('${spaces}目录:${entity.path}');
// 递归遍历子目录
await listAllFiles(entity, indent: indent + 1);
} else if (entity is File) {
print('${spaces}文件:${path.basename(entity.path)}');
}
}
}