In previous lessons, we learned about library and package management, mastering core skills for code organization and dependency management. Today we'll explore Dart's capabilities in file operations and command-line tool development. These skills are extremely useful for data processing, automation scripts, and tool development, significantly boosting your development efficiency.
I. File Operations Basics: Using the dart:io Library
Dart's standard dart:io library provides comprehensive APIs for file and directory operations, supporting reading, writing, deleting files, and directory management.
1. Importing the dart:io Library
Before working with files, import the dart:io library:
import 'dart:io';
2. Reading File Content
There are multiple ways to read files, choose the appropriate method based on file size and requirements:
(1) Reading Small Files at Once
Create a new example.txt
in the project root directory, For small text files, you can read the entire content at once with readAsString():
import 'dart:io';
void main() async {
// Define file path
final file = File('example.txt');
try {
// Read file content (asynchronous operation)
String content = await file.readAsString();
print('File content:');
print(content);
} catch (e) {
print('Failed to read file: $e');
}
}
(2) Reading Files Line by Line
Create a new large_file.txt
in the project root directory, For large files, reading line by line is more efficient and avoids excessive memory usage:
import 'dart:io';
import 'dart:convert'; // Provides utf8 encoding
void main() async {
final file = File('large_file.txt');
try {
// Read line by line (returns Stream<String>)
Stream<String> lines = file
.openRead()
.transform(utf8.decoder)
.transform(LineSplitter());
await for (String line in lines) {
print('Line content: $line');
// Add processing logic here, like searching for specific content
}
} catch (e) {
print('Failed to read file: $e');
}
}
3. Writing File Content
There are also multiple ways to write files, supporting overwriting or appending content:
(1) Overwriting a File
import 'dart:io';
void main() async {
final file = File('output.txt');
try {
// Write string (overwrites existing content)
await file.writeAsString('Hello, Dart file operations!\nThis is the second line');
print('File written successfully');
} catch (e) {
print('Failed to write file: $e');
}
}
(2) Appending to a File
import 'dart:io';
void main() async {
final file = File('output.txt');
try {
// Append content (mode: FileMode.append)
await file.writeAsString(
'\nThis is appended content',
mode: FileMode.append,
);
print('Content appended successfully');
} catch (e) {
print('Failed to append: $e');
}
}
(3) Writing Byte Data
Put a original_image.png
picture in the root directory,For binary files (like images, audio), use writeAsBytes():
import 'dart:io';
void main() async {
final imageFile = File('image_copy.png');
final sourceFile = File('original_image.png');
try {
// Read byte data
List<int> bytes = await sourceFile.readAsBytes();
// Write byte data
await imageFile.writeAsBytes(bytes);
print('Binary file copied successfully');
} catch (e) {
print('File copy failed: $e');
}
}
4. Directory Operations
In addition to files, dart:io supports directory creation, deletion, and traversal:
import 'dart:io';
void main() async {
// Define directory path
final dir = Directory('my_files');
// Check if directory exists
if (!await dir.exists()) {
// Create directory (recursive: true creates multi-level directories)
await dir.create(recursive: true);
print('Directory created successfully');
}
// Traverse files and subdirectories in the directory
await for (FileSystemEntity entity in dir.list()) {
if (entity is File) {
print('File: ${entity.path}');
} else if (entity is Directory) {
print('Directory: ${entity.path}');
}
}
// Delete directory (recursive: true deletes directory and all contents)
// await dir.delete(recursive: true);
// print('Directory deleted successfully');
}
II. Command-Line Argument Parsing: Using the args Package
When developing command-line tools, you typically need to parse user input arguments (like --help, -o output.txt). The args package is the preferred tool for handling command-line arguments in the Dart ecosystem.
1. Adding the args Dependency
Add the dependency to pubspec.yaml:
dependencies:
args: ^2.7.0
Run dart pub get to install the dependency.
2. Basic Argument Parsing
import 'package:args/args.dart';
void main(List<String> arguments) {
// Create argument parser
final parser = ArgParser()
..addFlag(
'help',
abbr: 'h',
help: 'Show help information',
negatable: false,
)
..addOption('output', abbr: 'o', help: 'Specify output file path')
..addOption(
'mode',
help: 'Run mode',
allowed: ['debug', 'release'],
defaultsTo: 'debug',
);
try {
// Parse arguments
final results = parser.parse(arguments);
// Handle --help argument
if (results['help'] as bool) {
print('Usage: dart script.dart [options]');
print(parser.usage);
return;
}
// Get other argument values
print('Output file: ${results['output'] ?? 'not specified'}');
print('Run mode: ${results['mode']}');
print(
'Positional arguments: ${results.rest}',
); // Unparsed positional arguments
} on FormatException catch (e) {
print('Argument error: $e');
print('Use --help for assistance');
}
}
Argument type explanations:
- addFlag: Boolean arguments (like --help), abbr specifies short option (like -h)
- addOption: Options with values (like --output file.txt)
- results.rest: Gets unparsed positional arguments
Example usage:
dart script.dart -o result.txt --mode release input1.txt input2.txt
Output:
Output file: result.txt
Run mode: release
Positional arguments: [input1.txt, input2.txt]
III. Practical Project: Batch File Renaming Script
Combining file operations and command-line argument parsing, we'll develop a useful batch renaming tool. This tool supports:
- Specifying target directory
- Adding prefixes/suffixes
- Renaming with sequence numbers
- Previewing renaming effect (without actual modification)
Complete Code Implementation
import 'dart:io';
import 'package:args/args.dart';
import 'package:path/path.dart' as path;
void main(List<String> arguments) async {
// Create argument parser
final parser = ArgParser()
..addFlag(
'help',
abbr: 'h',
help: 'Show help information',
negatable: false,
)
..addFlag(
'preview',
abbr: 'p',
help: 'Preview changes without modifying',
negatable: false,
)
..addOption(
'dir',
abbr: 'd',
help: 'Target directory path',
defaultsTo: '.',
)
..addOption('prefix', abbr: 'x', help: 'Filename prefix')
..addOption('suffix', abbr: 's', help: 'Filename suffix')
..addFlag(
'number',
abbr: 'n',
help: 'Add sequence numbers',
negatable: false,
)
..addOption(
'start',
abbr: 't',
help: 'Starting number for sequence',
defaultsTo: '1',
);
try {
final results = parser.parse(arguments);
// Show help information
if (results['help'] as bool) {
print('Batch Rename Tool');
print('Usage: dart rename.dart [options]');
print(parser.usage);
return;
}
// Parse arguments
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;
// Check if directory exists
if (!await targetDir.exists()) {
print('Error: Directory does not exist - ${targetDir.path}');
return;
}
// Get files in directory (exclude subdirectories)
final files = await targetDir
.list()
.where((entity) => entity is File)
.toList();
files.sort((a, b) => a.path.compareTo(b.path)); // Sort by path
if (files.isEmpty) {
print('No files in directory: ${targetDir.path}');
return;
}
// Batch rename process
print(
'${previewMode ? 'Previewing' : 'Performing'} rename (${files.length} files total):',
);
int currentNumber = startNumber;
for (final file in files.cast<File>()) {
// Get filename and extension
final fileName = path.basename(file.path);
final extension = path.extension(fileName);
final baseName = path.basenameWithoutExtension(fileName);
// Build new filename
String newName = '$prefix$baseName$suffix';
if (addNumber) {
newName = '${newName}_$currentNumber';
currentNumber++;
}
newName += extension;
// Build new path
final newPath = path.join(targetDir.path, newName);
// Show or perform rename
print('${file.path} → $newPath');
if (!previewMode && file.path != newPath) {
await file.rename(newPath);
}
}
print('${previewMode ? 'Preview' : 'Rename'} completed');
} on FormatException catch (e) {
print('Argument error: $e');
print('Use --help for assistance');
} catch (e) {
print('Operation failed: $e');
}
}
Code Explanation
- Argument Definition: Various parameters are defined through the args package to meet different renaming needs.
- Directory Check: Verifies if the target directory exists to avoid errors.
- File Processing:
- Filters files in the directory (excludes subdirectories)
- Sorts by path to ensure consistent renaming order
- Filename Construction:
- Separates filename and extension
- Generates new filenames based on prefix, suffix, sequence number and other parameters
- Rename Execution: Supports preview mode (shows effect only) and actual execution mode.
Usage Examples
- Preview adding prefix "photo_" and sequence numbers to files in current directory:
dart rename.dart -p -x photo_ -n
2 . Add suffix "_edited" to files in the images directory:
dart rename.dart -d images -s _edited
IV. Other Useful File Operation Techniques
1. Getting File Information
import 'dart:io';
void printFileInfo(File file) async {
final stat = await file.stat();
print('Path: ${file.path}');
print('Size: ${stat.size} bytes');
print('Modified: ${stat.modified}');
print('Is file: ${stat.type == FileSystemEntityType.file}');
}
2. Copying Files
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('Copied successfully: $targetPath');
} else {
print('Source file does not exist: $sourcePath');
}
}
3. Recursively Traversing Directories
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}Directory: ${entity.path}');
// Recursively traverse subdirectories
await listAllFiles(entity, indent: indent + 1);
} else if (entity is File) {
print('${spaces}File: ${path.basename(entity.path)}');
}
}
}
Top comments (0)