1. Giới thiệu
Hãy tưởng tượng một kho ảnh trong vài năm — hàng nghìn file trong cùng một thư mục (tình huống thường gặp với người dùng Windows), và giờ bạn có nhiệm vụ: sao chép chỉ các file .jpg được tạo trong năm nay vào một thư mục riêng để xử lý. Hoặc bạn cần đổi tên tất cả báo cáo với tiền tố "old_" để phân biệt phiên bản cũ và mới. Ngay cả khi bạn không làm việc với kho ảnh, thao tác hàng loạt cần thiết trong hầu hết dự án xử lý dữ liệu, log, backup hoặc tự động hóa. Chúng còn xuất hiện trong phỏng vấn nữa!
Thao tác hàng loạt là dịp tốt để làm quen:
- Vòng lặp và LINQ để duyệt nội dung thư mục
- Thực hành làm việc với đường dẫn (Path)
- Những cơ bản về lọc, tìm kiếm, đổi tên theo mẫu
- Các vấn đề quan trọng: bảo mật, lỗi, ghi đè
Bắt đầu nào — chúc bạn có sức mạnh để dẹp bãi hỗn độn file!
2. Sao chép hàng loạt file
Cách hoạt động: nguyên tắc chung
Với thao tác hàng loạt thường cần:
- Lấy danh sách file cần thiết (ví dụ, tất cả .txt trong thư mục)
- Với mỗi file thực hiện hành động cần thiết (sao chép, xoá, v.v.)
Điều này có thể thực hiện bằng:
- Directory.GetFiles() — lấy danh sách file,
- Vòng lặp foreach — duyệt từng file và thực hiện thao tác.
Ví dụ: sao chép tất cả file .txt từ một thư mục sang thư mục khác
string sourceDir = @"C:\Source";
string destDir = @"C:\Target";
// Lấy danh sách tất cả file .txt trong thư mục nguồn
string[] txtFiles = Directory.GetFiles(sourceDir, "*.txt");
foreach (string srcPath in txtFiles)
{
// Lấy chỉ tên file từ đường dẫn đầy đủ
string fileName = Path.GetFileName(srcPath);
// Tạo đường dẫn đầy đủ cho file ở thư mục đích
string destPath = Path.Combine(destDir, fileName);
// Sao chép file
File.Copy(srcPath, destPath, overwrite: true); // overwrite - nếu file đã tồn tại, ghi đè
Console.WriteLine($"Copied: {fileName}");
}
Console.WriteLine("Tất cả các file .txt đã được sao chép thành công!");
Lưu ý: trong ví dụ này ta dùng filter "*.txt" — đó là pattern tìm tên file giống như trong Windows.
Minh họa (sơ đồ)
+----------------+ [*.txt] +----------------+
| C:\Source | ---filter-----> | C:\Target |
| a.txt | foreach + | |
| b.txt | --copy--------> | a.txt (copied) |
| c.jpg | | b.txt (copied) |
+----------------+ +----------------+
(.jpg files bị bỏ qua, chỉ sao chép .txt)
3. Xóa hàng loạt file
Xóa hàng loạt file là thao tác rất phổ biến. Ví dụ, thường cần dọn file tạm, xóa log cũ hoặc các ảnh jpg không còn cần.
Ví dụ: xóa tất cả file cũ hơn 30 ngày
string dir = @"C:\MyLogs";
int daysOld = 30;
DirectoryInfo di = new DirectoryInfo(dir);
// Lấy tất cả file trong thư mục
foreach (FileInfo file in di.GetFiles())
{
// Kiểm tra ngày sửa cuối
if (file.LastWriteTime < DateTime.Now.AddDays(-daysOld))
{
file.Delete();
Console.WriteLine($"Đã xóa: {file.Name}");
}
}
Mẹo: FileInfo.LastWriteTime — rất tiện cho điều kiện "cũ hơn N ngày".
4. Đổi tên hàng loạt file
Đôi khi cần đổi tên nhiều file theo mẫu. Ví dụ, thêm tiền tố chung, đổi đuôi, hoặc đánh số liên tiếp. Trong .NET cũng làm tương tự — lấy danh sách file, rồi đổi tên bằng File.Move().
Ví dụ: thêm tiền tố "old_" cho tất cả file .docx
string dir = @"C:\Reports";
string[] docxFiles = Directory.GetFiles(dir, "*.docx");
foreach (string oldPath in docxFiles)
{
string dirPath = Path.GetDirectoryName(oldPath)!;
string fileName = Path.GetFileName(oldPath);
string newPath = Path.Combine(dirPath, "old_" + fileName);
// Đổi tên (về thực chất là di chuyển "vào cùng thư mục với tên mới")
File.Move(oldPath, newPath);
Console.WriteLine($"Đã đổi tên: {fileName} -> old_{fileName}");
}
Điểm quan trọng: nếu trong thư mục đã có file với tên mới, sẽ ném exception. Có thể xử lý bằng try-catch nếu cần.
5. Sao chép cả thư mục cùng toàn bộ nội dung
Với thao tác đơn giản trên một thư mục không có hàm "sao chép toàn bộ thư mục" một dòng (Directory.Copy không có — cái này là bẫy!). Cần tự làm:
- Tạo thư mục đích (nếu chưa có)
- Sao chép tất cả file (xem ví dụ quen thuộc)
- Sao chép đệ quy tất cả thư mục con (mỗi thư mục con xử lý như một nhiệm vụ sao chép mới)
Hàm tổng quát: sao chép thư mục đệ quy
using System;
using System.IO;
class Program
{
static void CopyDirectory(string sourceDir, string destDir, bool overwrite = true)
{
// Tạo thư mục nếu chưa có
Directory.CreateDirectory(destDir);
// Sao chép tất cả file
foreach (string filePath in Directory.GetFiles(sourceDir))
{
string fileName = Path.GetFileName(filePath);
string destFile = Path.Combine(destDir, fileName);
File.Copy(filePath, destFile, overwrite);
}
// Sao chép tất cả thư mục con (đệ quy)
foreach (string subDir in Directory.GetDirectories(sourceDir))
{
string dirName = Path.GetFileName(subDir);
string destSubDir = Path.Combine(destDir, dirName);
CopyDirectory(subDir, destSubDir, overwrite);
}
}
static void Main()
{
string source = @"C:\Archive2023";
string target = @"D:\Backup2023";
CopyDirectory(source, target);
Console.WriteLine("Thư mục đã được sao chép thành công!");
}
}
Sơ đồ khối
CopyDirectory(A, B)
/ \
copy files foreach subDir -> CopyDirectory(subDir, destSubDir)
6. Lọc, tìm kiếm và xử lý hàng loạt file
Giả sử bạn muốn không chỉ duyệt thư mục mà chọn theo nhiều tiêu chí: ví dụ, chỉ ảnh có kích thước lớn hơn 5 MB, và được tạo trong năm 2024! Thường kết hợp LINQ với các lớp hệ thống file sẽ tiện.
Ví dụ: in ra tên các ảnh lớn và mới
string dir = @"C:\Pictures";
var filtered = new DirectoryInfo(dir)
.GetFiles("*.jpg")
.Where(f => f.Length > 5_000_000 && f.CreationTime.Year == 2024);
foreach (var file in filtered)
{
Console.WriteLine($"{file.Name} ({file.Length / 1024 / 1024} MB)");
}
Trong ví dụ này ta dùng LINQ để "xâu" các bộ lọc — và viết code thực tế hay gặp trong công việc.
7. Duyệt thư mục lồng nhau: đệ quy và duyệt
Thường cần xử lý không chỉ một thư mục mà tất cả thư mục con (ví dụ, xóa tất cả file tạm trong toàn bộ cấu trúc). Các method lấy file (Directory.GetFiles và DirectoryInfo.GetFiles) có biến thể với tham số SearchOption.AllDirectories, giúp làm việc đó cho bạn!
Ví dụ: tìm và xóa tất cả file .tmp trong mọi thư mục con
string root = @"D:\BigFolder";
string[] tmpFiles = Directory.GetFiles(root, "*.tmp", SearchOption.AllDirectories);
foreach (string file in tmpFiles)
{
File.Delete(file);
Console.WriteLine($"Đã xóa: {file}");
}
Console.WriteLine("Tất cả file tạm đã được xóa.");
Lưu ý: Cẩn thận với flag này — nó có thể tìm file trong những cấu trúc lồng nhau rất sâu!
8. Tạo hàng loạt file và thư mục
Đôi khi nhiệm vụ ngược lại — tự động tạo cấu trúc thư mục cần thiết hoặc sinh nhiều file.
Ví dụ: tạo 10 thư mục và 10 file trong mỗi thư mục
string root = @"C:\GeneratedFolders";
for (int i = 1; i <= 10; i++)
{
string subDir = Path.Combine(root, $"Folder_{i}");
Directory.CreateDirectory(subDir);
for (int j = 1; j <= 10; j++)
{
string filePath = Path.Combine(subDir, $"File_{j}.txt");
File.WriteAllText(filePath, $"Đây là file số {j} trong thư mục {i}");
}
}
Console.WriteLine("Các thư mục và file đã được tạo!");
Mẹo: Có thể nhanh chóng dựng hạ tầng test, sinh "dummy" cho test, học tập, v.v.
9. Di chuyển hàng loạt file
Tương tự sao chép, chỉ dùng File.Move thay vì File.Copy. Phù hợp để sắp xếp file theo thư mục.
Ví dụ: sắp xếp file theo phần mở rộng
Giả sử trong thư mục có nhiều file loại khác nhau, và bạn muốn phân loại chúng vào thư mục .jpg, .pdf, .docx, v.v.
string source = @"C:\Downloads";
string[] files = Directory.GetFiles(source);
foreach (string path in files)
{
string ext = Path.GetExtension(path).TrimStart('.').ToUpper(); // "JPG", "PDF", "DOCX"
if (string.IsNullOrEmpty(ext)) ext = "OTHER";
string destDir = Path.Combine(source, ext);
Directory.CreateDirectory(destDir); // không sao nếu đã tồn tại
string fileName = Path.GetFileName(path);
string destPath = Path.Combine(destDir, fileName);
if (!File.Exists(destPath))
{
File.Move(path, destPath);
Console.WriteLine($"Đã di chuyển: {fileName} -> {destDir}");
}
else
{
Console.WriteLine($"File đã tồn tại ở {destDir}, bỏ qua: {fileName}");
}
}
File sẽ "đi vào từng hộp", và bạn sẽ cảm thấy như Marie Kondo phiên bản số hóa.
10. Lỗi thường gặp và những lưu ý khi thao tác hàng loạt
Khi làm việc với không chỉ một file mà hàng trăm, sẽ lộ ra nhiều chi tiết thú vị.
Ví dụ, một số file có thể đang được mở bởi chương trình khác — cố gắng xóa hoặc sao chép sẽ gây lỗi. Cũng hay gặp tình huống trong thư mục đích đã có file cùng tên. Nếu code không dự liệu ghi đè, sẽ ném exception. Đôi khi file hoặc thư mục không có quyền truy cập, hoặc bạn vô tình xóa nhầm thứ quan trọng do đệ quy.
Lỗi kinh điển — thực hiện thao tác mà không xử lý lỗi: nếu giữa chừng có sự cố (ví dụ, thiếu quyền với một file), vòng lặp sẽ dừng và các thao tác sau đó không được thực hiện. Vì vậy để thao tác hàng loạt đáng tin cậy hầu như luôn dùng xử lý ngoại lệ qua try-catch bên trong vòng lặp, để có thể bỏ qua file gặp sự cố và tiếp tục với phần còn lại.
Và đừng quên các tham số ghi đè: nơi nào hợp lý thì dùng overwrite: true trong File.Copy, còn khi đổi tên/di chuyển hãy kiểm tra trước sự tồn tại của file ở đường dẫn đích hoặc áp dụng chiến lược xử lý xung đột (đổi tên kèm hậu tố, bỏ qua, ghi log).
GO TO FULL VERSION