1. はじめに
数年分の写真アーカイブを想像してみてください — ひとつのフォルダに数千ファイルが並んでいる(Windowsユーザーあるある)。ここで課題:今年作成された.jpgファイルだけを処理用の別フォルダにコピーする、あるいはすべてのレポートにプレフィックス"old_"を付けて古いバージョンと区別する、といったことです。写真アーカイブから離れていても、ログ、バックアップ、データ処理や自動化のプロジェクトでは一括操作が頻出します。面接でも出ますよ。
一括操作は以下を習得する良い機会です:
- ディレクトリ内容を走査するためのループとLINQ
- パス操作の実践(Path)
- フィルタリング、検索、テンプレートに基づくリネームの基礎
- 重要なポイント:セキュリティ、エラー処理、上書き
さあ始めよう — ファイルのカオスを片付ける力が君にありますように!
2. ファイルの一括コピー
仕組み:基本原則
一括処理では通常次のことを行います:
- 必要なファイルの一覧を取得する(例:フォルダ内のすべての .txt)
- 各ファイルに対して必要な操作を行う(コピー、削除など)
実装方法の例:
- Directory.GetFiles() — ファイル一覧を取得する、
- ループのforeach — 各ファイルを回って必要な操作を実行する。
例:あるフォルダから別フォルダへすべての .txt をコピー
string sourceDir = @"C:\Source";
string destDir = @"C:\Target";
// ソースフォルダ内のすべての .txt ファイルを取得する
string[] txtFiles = Directory.GetFiles(sourceDir, "*.txt");
foreach (string srcPath in txtFiles)
{
// フルパスからファイル名だけを取得
string fileName = Path.GetFileName(srcPath);
// ターゲットフォルダにおけるファイルのフルパスを作る
string destPath = Path.Combine(destDir, fileName);
// ファイルをコピーする
File.Copy(srcPath, destPath, overwrite: true); // overwrite - ファイルが既にある場合は上書きする
Console.WriteLine($"Copied: {fileName}");
}
Console.WriteLine("すべての .txt ファイルが正常にコピーされました!");
注意:この例ではフィルタ "*.txt" を使っています — これはWindowsのファイル名検索パターンと同じです。
ビジュアル(図)
+----------------+ [*.txt] +----------------+
| C:\Source | ---filter-----> | C:\Target |
| a.txt | foreach + | |
| b.txt | --copy--------> | a.txt (copied) |
| c.jpg | | b.txt (copied) |
+----------------+ +----------------+
(.jpg ファイルは無視され、.txt のみがコピーされます)
3. ファイルの一括削除
ファイルの一括削除はかなり一般的です。例えば一時ファイルの掃除、古いログの自動削除、もう不要な jpg 写真の削除などがよくあります。
例:30 日より古いファイルをすべて削除
string dir = @"C:\MyLogs";
int daysOld = 30;
DirectoryInfo di = new DirectoryInfo(dir);
// フォルダ内のすべてのファイルを取得する
foreach (FileInfo file in di.GetFiles())
{
// 最終更新日時をチェック
if (file.LastWriteTime < DateTime.Now.AddDays(-daysOld))
{
file.Delete();
Console.WriteLine($"削除済み: {file.Name}");
}
}
ポイント: FileInfo.LastWriteTime は「N日より古い」条件を作るのにとても便利です。
4. ファイルの一括リネーム
多くのファイルをテンプレートに従ってリネームするケースがあります。例えば共通のプレフィックスを付ける、拡張子を変える、連番を振る、など。.NETでは同様にファイル一覧を取得してそれぞれ File.Move() を使ってリネーム(=同フォルダ内での移動)します。
例:すべての .docx ファイルにプレフィックス "old_" を付ける
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);
// リネームする(実際には同じフォルダ内で名前を変えて移動する)
File.Move(oldPath, newPath);
Console.WriteLine($"リネーム: {fileName} -> old_{fileName}");
}
重要: もし同じフォルダに新しい名前のファイルが既にあると、例外が発生します。必要なら try-catch でハンドリングしてください。
5. ディレクトリ丸ごとのコピー
単一フォルダの簡単な操作なら良いですが、ディレクトリを「丸ごと一行でコピーする」ような便利メソッドは存在しません(Directory.Copy はない — これはジェダイへの罠!)。手作業でコピーする必要があります:
- ターゲットフォルダを作る(存在しないなら)
- すべてのファイルをコピーする(前に見た例参照)
- すべてのサブフォルダを再帰的にコピーする(各サブフォルダを新しいコピータスクとして処理)
汎用関数:ディレクトリを再帰的にコピーする
using System;
using System.IO;
class Program
{
static void CopyDirectory(string sourceDir, string destDir, bool overwrite = true)
{
// フォルダがなければ作成する
Directory.CreateDirectory(destDir);
// すべてのファイルをコピーする
foreach (string filePath in Directory.GetFiles(sourceDir))
{
string fileName = Path.GetFileName(filePath);
string destFile = Path.Combine(destDir, fileName);
File.Copy(filePath, destFile, overwrite);
}
// すべてのサブフォルダをコピーする(再帰)
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("ディレクトリを正常にコピーしました!");
}
}
ブロック図
CopyDirectory(A, B)
/ \
copy files foreach subDir -> CopyDirectory(subDir, destSubDir)
6. ファイルの一括フィルタリング、検索、処理
フォルダを単に走査するだけでなく、複数条件で選びたいことがあります。例えばサイズが5MBを超える画像で、しかも2024年に作成されたものだけ、など。LINQとファイルシステムクラスを組み合わせると便利です。
例:大きくて新しめの画像のファイル名を出力する
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)");
}
この例ではLINQで連続フィルタを作って、実務で使うような現実的なコードを書いています。
7. ネストしたフォルダの走査: 再帰と列挙
単一ディレクトリだけでなく、ネストした全ディレクトリを処理したいことがよくあります(例えば、アーカイブ全体の一時ファイルを削除する)。ファイル取得メソッド(Directory.GetFiles と DirectoryInfo.GetFiles)には SearchOption.AllDirectories というパラメータがあり、それを使うと簡単に全階層を走査できます!
例:すべてのサブフォルダ内の .tmp を見つけて削除
string root = @"D:\BigFolder";
string[] tmpFiles = Directory.GetFiles(root, "*.tmp", SearchOption.AllDirectories);
foreach (string file in tmpFiles)
{
File.Delete(file);
Console.WriteLine($"削除済み: {file}");
}
Console.WriteLine("すべての一時ファイルを削除しました。");
注意: このフラグは深い階層まで探索するので扱いに注意してください!
8. ファイルとディレクトリの一括作成
逆のケースとして、自動でフォルダ構成を作ったり大量のファイルを生成したりすることがあります。
例:10個のフォルダを作り、それぞれに10個のファイルを作成
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, $"フォルダ{i}のファイル番号{j}です");
}
}
Console.WriteLine("フォルダとファイルが作成されました!");
ポイント: テスト用のインフラをすばやく作ったり、テストデータ(ダミー)を生成したりするのに便利です。
9. ファイルの一括移動
コピーと同様ですが、File.Move を使います。ファイルをフォルダごとに振り分けるのに便利です。
例:拡張子ごとにファイルを分類して移動する
状況の例:いろんな種類のファイルが混ざったフォルダがあり、それらを .jpg, .pdf, .docx など別フォルダに分類したいとします。
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); // 既にあっても問題ない
string fileName = Path.GetFileName(path);
string destPath = Path.Combine(destDir, fileName);
if (!File.Exists(destPath))
{
File.Move(path, destPath);
Console.WriteLine($"移動: {fileName} -> {destDir}");
}
else
{
Console.WriteLine($"ファイルは既に {destDir} に存在するためスキップします: {fileName}");
}
}
ファイルがそれぞれ「箱」に振り分けられて、まるでデジタル近藤麻理恵のように整理されます。
10. 一括操作のよくあるミスと注意点
1つのファイルではなく数百を扱うと、現れる問題も色々です。
例えば、あるファイルが別のプログラムで開かれていると削除やコピーに失敗します。ターゲットフォルダにすでに同名のファイルがあると、上書きしない構成なら例外で処理が止まります。アクセス権がなく操作できないファイルやフォルダ、あるいは再帰で誤って重要なものを削除してしまうリスクもあります。
典型的なミスは、エラーハンドリング無しで一括操作を回すことです:途中で権限エラーなどが起きるとループが止まり、それ以降のファイルが処理されません。だから信頼性のある一括処理では、問題のあるファイルだけスキップできるようにループ内部で try-catch を使うのが常套手段です。
また上書きのパラメータを忘れないでください:必要なら overwrite: true を File.Copy に渡す、リネーム/移動時は事前にターゲットの存在をチェックする、あるいはコンフリクト解決戦略(サフィックス付与、スキップ、ログ出力)を用意しましょう。
GO TO FULL VERSION