C# Tips | ファイル・ディレクトリ操作:ファイル名一括変更

C sharp C#
スポンサーリンク

はじめに なぜ「ファイル名一括変更」が業務で効くのか

業務システムや日々の運用では、「毎日出力されるファイルに連番を振りたい」「人が付けたバラバラな名前を、システムで扱いやすい規則的な名前に揃えたい」「外部から受け取ったファイル名を、自社ルールの名前に変えたい」といったニーズが頻繁に出てきます。
こういうときに力を発揮するのが「ファイル名一括変更」のユーティリティです。

C# では、「ファイル名を変える」こと自体は File.Move で行います。
そこに Directory.GetFilesPath クラスを組み合わせることで、「フォルダ内のファイルをまとめてリネームする」処理を柔軟に作れます。
ここでは、プログラミング初心者向けに、基本から実務で使えるユーティリティ化まで、順を追って解説していきます。


基本の考え方 ファイル名変更=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.jpgimg_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.GetExtensionPath.Combine を使うことで、「拡張子を壊さずに名前だけ変える」ことができます。

さらに、「変更後の名前が既に存在していたら例外を投げて止める」という安全側の設計にしている点も重要です。
上書きしてよいかどうかは業務ルール次第なので、ここをどうするかは要件に合わせて調整します。


元の名前を活かした一括リネーム プレフィックス・サフィックス追加

プレフィックスを付けるユーティリティ

次に、「元のファイル名は残しつつ、先頭にプレフィックスを付ける」パターンを考えてみます。
たとえば、report.csv2025_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.csvreport_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.GetFileNamePath.GetFileNameWithoutExtensionPath.GetExtensionPath.Combine を使って、「名前の部品」を安全に扱うこと。
連番、プレフィックス、サフィックスなど、よく使うパターンをユーティリティとして切り出し、業務ルールに合わせて組み合わせられるようにすること。
名前衝突の扱い(上書きするか、エラーにするか、スキップするか)を明確に決め、必要に応じてプレビューやバックアップを用意すること。
例外やエラーの情報をログに残し、「どこで何が起きたか」を後から追えるようにしておくこと。

ここまで押さえておけば、「毎回人が手でリネームしていた運用」や「名前のバラつきでシステムが扱いづらい状態」を、かなりスッキリさせられます。

タイトルとURLをコピーしました