はじめに なぜ「ファイル名一括変更」が業務で効くのか
業務システムや日々の運用では、「毎日出力されるファイルに連番を振りたい」「人が付けたバラバラな名前を、システムで扱いやすい規則的な名前に揃えたい」「外部から受け取ったファイル名を、自社ルールの名前に変えたい」といったニーズが頻繁に出てきます。
こういうときに力を発揮するのが「ファイル名一括変更」のユーティリティです。
C# では、「ファイル名を変える」こと自体は File.Move で行います。
そこに Directory.GetFiles や Path クラスを組み合わせることで、「フォルダ内のファイルをまとめてリネームする」処理を柔軟に作れます。
ここでは、プログラミング初心者向けに、基本から実務で使えるユーティリティ化まで、順を追って解説していきます。
基本の考え方 ファイル名変更=File.Move
File.Move で「名前を変える」イメージをつかむ
まずは、「ファイル名を変える」ときに何を使うのかを押さえます。
C# では、ファイルの移動と名前変更はどちらも File.Move で行います。
「同じフォルダ内で別の名前にする」場合も、「別フォルダに移動する」場合も、同じメソッドです。
using System;
using System.IO;
class Program
{
static void Main()
{
string oldPath = @"C:\data\report_old.csv";
string newPath = @"C:\data\report_202501.csv";
File.Move(oldPath, newPath);
Console.WriteLine("ファイル名を変更しました。");
}
}
C#この例では、フォルダは同じで、ファイル名だけが変わっています。
つまり、「ファイル名変更=同じフォルダ内での Move」として扱える、ということです。
Path クラスで「名前の部品」を扱う
ファイル名一括変更では、「元のファイル名の一部を使う」「拡張子はそのまま残す」「連番だけ付け替える」といった操作がよく出てきます。
そのときに、文字列を手作業で切り貼りするのではなく、Path クラスを使って「部品」として扱うのが安全です。
using System;
using System.IO;
class Program
{
static void Main()
{
string path = @"C:\data\report_old.csv";
string fileName = Path.GetFileName(path); // "report_old.csv"
string nameWithoutExt = Path.GetFileNameWithoutExtension(path); // "report_old"
string ext = Path.GetExtension(path); // ".csv"
Console.WriteLine(fileName);
Console.WriteLine(nameWithoutExt);
Console.WriteLine(ext);
}
}
C#この三つを覚えておくと、「元の名前の一部を残しつつ、新しい名前を組み立てる」という処理がとても書きやすくなります。
単純な一括リネーム 連番を付ける基本ユーティリティ
フォルダ内のファイルに連番を振る
まずは、「指定フォルダ内のファイルに、同じプレフィックス+連番+元の拡張子」という形で名前を付け直すユーティリティを作ってみます。
たとえば、C:\images にあるファイルを img_001.jpg、img_002.jpg のように揃えるイメージです。
using System;
using System.IO;
public static class FileBatchRenameUtil
{
public static int RenameWithSequence(
string directoryPath,
string prefix,
int startNumber = 1,
int digits = 3)
{
if (!Directory.Exists(directoryPath))
{
throw new DirectoryNotFoundException($"対象ディレクトリが見つかりません: {directoryPath}");
}
string[] files = Directory.GetFiles(directoryPath);
Array.Sort(files, StringComparer.OrdinalIgnoreCase);
int count = 0;
int number = startNumber;
foreach (string filePath in files)
{
string ext = Path.GetExtension(filePath);
string newName = $"{prefix}_{number.ToString(new string('0', digits))}{ext}";
string newPath = Path.Combine(directoryPath, newName);
if (string.Equals(filePath, newPath, StringComparison.OrdinalIgnoreCase))
{
number++;
continue;
}
if (File.Exists(newPath))
{
throw new IOException($"変更後のファイル名が既に存在します: {newPath}");
}
File.Move(filePath, newPath);
count++;
number++;
}
return count;
}
}
C#使い方の例は次の通りです。
class Program
{
static void Main()
{
string dir = @"C:\images";
int renamed = FileBatchRenameUtil.RenameWithSequence(
dir,
prefix: "img",
startNumber: 1,
digits: 4
);
Console.WriteLine($"リネームしたファイル数: {renamed}");
}
}
C#ここで深掘りしたいポイントがいくつかあります。
まず、「Directory.GetFiles でファイル一覧を取得し、Array.Sort で並び順を固定している」ことです。
これにより、「どのファイルが 001 で、どれが 002 か」が毎回同じになるので、運用上の混乱を防げます。
次に、「元の拡張子はそのまま使い、名前の本体だけを prefix_連番 にしている」ことです。Path.GetExtension と Path.Combine を使うことで、「拡張子を壊さずに名前だけ変える」ことができます。
さらに、「変更後の名前が既に存在していたら例外を投げて止める」という安全側の設計にしている点も重要です。
上書きしてよいかどうかは業務ルール次第なので、ここをどうするかは要件に合わせて調整します。
元の名前を活かした一括リネーム プレフィックス・サフィックス追加
プレフィックスを付けるユーティリティ
次に、「元のファイル名は残しつつ、先頭にプレフィックスを付ける」パターンを考えてみます。
たとえば、report.csv を 2025_01_report.csv のように変えるケースです。
using System;
using System.IO;
public static class FileBatchRenameUtil
{
public static int AddPrefixToFiles(string directoryPath, string prefix)
{
if (!Directory.Exists(directoryPath))
{
throw new DirectoryNotFoundException($"対象ディレクトリが見つかりません: {directoryPath}");
}
string[] files = Directory.GetFiles(directoryPath);
Array.Sort(files, StringComparer.OrdinalIgnoreCase);
int count = 0;
foreach (string filePath in files)
{
string fileName = Path.GetFileName(filePath);
string newName = prefix + fileName;
string newPath = Path.Combine(directoryPath, newName);
if (string.Equals(filePath, newPath, StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (File.Exists(newPath))
{
throw new IOException($"変更後のファイル名が既に存在します: {newPath}");
}
File.Move(filePath, newPath);
count++;
}
return count;
}
}
C#使い方の例は次の通りです。
class Program
{
static void Main()
{
string dir = @"C:\data\reports";
int renamed = FileBatchRenameUtil.AddPrefixToFiles(
dir,
prefix: "2025_01_"
);
Console.WriteLine($"リネームしたファイル数: {renamed}");
}
}
C#このパターンは、「年月や案件番号をファイル名の先頭に付けたい」といった業務でよく使われます。
サフィックス(末尾)を付けるユーティリティ
同じように、「拡張子の前にサフィックスを付ける」パターンもよくあります。
たとえば、report.csv を report_backup.csv にするケースです。
using System;
using System.IO;
public static class FileBatchRenameUtil
{
public static int AddSuffixBeforeExtension(string directoryPath, string suffix)
{
if (!Directory.Exists(directoryPath))
{
throw new DirectoryNotFoundException($"対象ディレクトリが見つかりません: {directoryPath}");
}
string[] files = Directory.GetFiles(directoryPath);
Array.Sort(files, StringComparer.OrdinalIgnoreCase);
int count = 0;
foreach (string filePath in files)
{
string nameWithoutExt = Path.GetFileNameWithoutExtension(filePath);
string ext = Path.GetExtension(filePath);
string newName = nameWithoutExt + suffix + ext;
string newPath = Path.Combine(directoryPath, newName);
if (string.Equals(filePath, newPath, StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (File.Exists(newPath))
{
throw new IOException($"変更後のファイル名が既に存在します: {newPath}");
}
File.Move(filePath, newPath);
count++;
}
return count;
}
}
C#このように、「プレフィックス」「サフィックス」「連番」などのパターンをユーティリティとして用意しておくと、業務のファイル名ルールに合わせて組み合わせやすくなります。
実務での注意点 名前衝突・プレビュー・ロールバック
名前衝突(同名ファイル)の扱いを決める
一括リネームで必ず意識すべきなのが、「変更後の名前が既に存在していたらどうするか」です。
上書きしてよいのか、スキップするのか、エラーで止めるのかは、業務ルールによって変わります。
安全側に倒すなら、「既に存在していたら例外を投げて止める」が基本です。
一方、「バックアップフォルダで、同名があっても上書きで構わない」といったケースでは、File.Delete で先に消してから File.Move する、という設計もありえます。
重要なのは、「どうするかをユーティリティの引数や名前で明示する」ことです。
たとえば、RenameWithSequenceAllowOverwrite のように、メソッド名でポリシーを表現してしまうのも一つの手です。
実務では「いきなり本番」ではなくプレビューが欲しくなる
現場でよく言われるのが、「いきなり名前を変えられると怖いので、まずは『どう変わるか』を一覧で見たい」という要望です。
その場合、実際に File.Move する前に、「元のパス」と「新しいパス」のペアをリストとして返すメソッドを用意しておくと便利です。
イメージとしては、「プレビュー用メソッド」と「実行用メソッド」を分ける形です。
初心者のうちは、まずは実行用だけで構いませんが、「本番運用を意識したらプレビューも欲しくなる」という感覚を持っておくと、設計の視野が広がります。
ロールバック(元に戻す)をどう考えるか
一括リネームは、「途中で失敗したら中途半端な状態になる」リスクがあります。
本格的にロールバックまで考えると、「変更前と変更後の対応表をどこかに保存しておき、失敗時に逆順で戻す」といった仕組みが必要になります。
最初からそこまで作り込む必要はありませんが、「一括リネームは破壊的な操作であり、簡単には元に戻せない」という意識は持っておくべきです。
だからこそ、テスト環境で十分に試すこと、必要ならプレビューやバックアップを用意することが大切になります。
例外とエラー処理を意識した一括リネーム
どんな例外が起こり得るか
ファイル名一括変更では、次のような理由で例外が発生する可能性があります。
対象ディレクトリが存在しない、ファイルが存在しない、変更後の名前のファイルが既に存在している、権限がなくてリネームできない、別プロセスがファイルをロックしている、パスが長すぎる、などです。
呼び出し側での例外処理の例を見てみます。
using System;
using System.IO;
class Program
{
static void Main()
{
string dir = @"C:\images";
try
{
int renamed = FileBatchRenameUtil.RenameWithSequence(
dir,
prefix: "img",
startNumber: 1,
digits: 3
);
Console.WriteLine($"リネームに成功しました。件数: {renamed}");
}
catch (DirectoryNotFoundException ex)
{
Console.WriteLine("ディレクトリが見つかりません: " + ex.Message);
}
catch (IOException ex)
{
Console.WriteLine("入出力エラーが発生しました: " + ex.Message);
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("権限エラーが発生しました: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("想定外のエラーが発生しました: " + ex.Message);
}
}
}
C#実務では、これらのメッセージをログに残しておくことで、「どのフォルダで、どんな理由で一括リネームに失敗したか」を後から追跡できます。
まとめ 実務で使えるファイル名一括変更ユーティリティの考え方
ファイル名一括変更は、「人間が手作業でやるとミスしやすく、時間もかかる」作業を、自動化してしまうための強力な武器です。
だからこそ、「とりあえず動けばいい」ではなく、「安全に・意図通りに・運用しやすく」動くように設計することが大切です。
File.Move を使って「名前変更=同じフォルダ内での移動」として扱うこと。Path.GetFileName、Path.GetFileNameWithoutExtension、Path.GetExtension、Path.Combine を使って、「名前の部品」を安全に扱うこと。
連番、プレフィックス、サフィックスなど、よく使うパターンをユーティリティとして切り出し、業務ルールに合わせて組み合わせられるようにすること。
名前衝突の扱い(上書きするか、エラーにするか、スキップするか)を明確に決め、必要に応じてプレビューやバックアップを用意すること。
例外やエラーの情報をログに残し、「どこで何が起きたか」を後から追えるようにしておくこと。
ここまで押さえておけば、「毎回人が手でリネームしていた運用」や「名前のバラつきでシステムが扱いづらい状態」を、かなりスッキリさせられます。

