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)