C# Tips | ファイル・ディレクトリ操作:バックアップ作成

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

はじめに 「バックアップ作成」は“事故前提”でコードを書く技術

業務システムでファイルを扱うなら、「バックアップをどう残すか」は避けて通れません。
人が操作をミスることもあるし、プログラムがバグることもあるし、外部システムから変なデータが来ることもあります。

だからこそ、

壊す前に必ずコピーを取っておく
上書きする前に元ファイルを退避しておく

という「バックアップ作成」を、ユーティリティとしてサクッと呼べるようにしておくと、コード全体の安心感が一気に上がります。

ここでは、プログラミング初心者向けに、

単一ファイルのバックアップ
フォルダ丸ごとのバックアップ
日付・時刻付きバックアップ名の付け方
ファイル名重複回避との組み合わせ
実務での設計ポイント

を、例題付きでかみ砕いて説明していきます。


バックアップ作成の基本発想を整理する

「いつ」「どこに」「どの単位で」コピーするか

バックアップといっても、やっていることは本質的にはシンプルで、

元ファイル(またはフォルダ)を、別の場所・別の名前でコピーしておく

だけです。

ただし、実務でちゃんと使えるようにするには、次のようなことを決めておく必要があります。

どのタイミングで取るか
上書き前か、削除前か、処理開始前か。

どこに置くか
同じフォルダか、専用の backup フォルダか、日付ごとのサブフォルダか。

どの単位で取るか
単一ファイルか、フォルダ丸ごとか、ZIP に固めるか。

ここではまず「単一ファイルのバックアップ」から入り、
そこから「フォルダ」「日付付き」「重複回避」へと広げていきます。


単一ファイルのバックアップ作成

一番シンプルな「同じフォルダに .bak を作る」

まずは、元ファイルと同じフォルダに、拡張子だけ変えたバックアップを作るパターンです。

using System;
using System.IO;

public static class BackupUtil
{
    public static string CreateSimpleBackup(string originalPath)
    {
        if (originalPath is null)
        {
            throw new ArgumentNullException(nameof(originalPath));
        }

        if (!File.Exists(originalPath))
        {
            throw new FileNotFoundException("バックアップ元のファイルが存在しません。", originalPath);
        }

        string directory = Path.GetDirectoryName(originalPath)
            ?? throw new ArgumentException("ディレクトリを含まないパスです。", nameof(originalPath));

        string fileNameWithoutExt = Path.GetFileNameWithoutExtension(originalPath);
        string backupFileName = fileNameWithoutExt + ".bak";

        string backupPath = Path.Combine(directory, backupFileName);

        File.Copy(originalPath, backupPath, overwrite: true);

        return backupPath;
    }
}
C#

使い方のイメージです。

string original = @"C:\config\appsettings.json";

string backup = BackupUtil.CreateSimpleBackup(original);

Console.WriteLine($"バックアップ作成: {backup}");
C#

ここでの重要ポイントは次の通りです。

元ファイルの存在チェックを必ず行う
File.Exists で確認し、無ければ例外にしています。
「バックアップを取ったつもりが、そもそも元が無かった」という事故を防ぎます。

ディレクトリ・ファイル名・拡張子を分解して組み立て直す
Path.GetDirectoryNamePath.GetFileNameWithoutExtension を使って、
appsettings.jsonappsettings.bak のように名前を変えています。

File.Copyoverwrite: true を明示する
同名の .bak が既にある場合は上書きする、という仕様にしています。
ここを false にすると、2 回目以降で例外になります。

ただし、「過去のバックアップも全部残したい」場合は、次のように発展させます。


日付・時刻付きバックアップ名で履歴を残す

ファイル名にタイムスタンプを埋め込む

「毎回上書き」ではなく、「履歴を残したい」場合は、
バックアップファイル名に日付・時刻を埋め込むのが定番です。

using System;
using System.IO;

public static class BackupUtil
{
    public static string CreateTimestampBackup(string originalPath, string? backupDirectory = null)
    {
        if (originalPath is null)
        {
            throw new ArgumentNullException(nameof(originalPath));
        }

        if (!File.Exists(originalPath))
        {
            throw new FileNotFoundException("バックアップ元のファイルが存在しません。", originalPath);
        }

        string sourceDirectory = Path.GetDirectoryName(originalPath)
            ?? throw new ArgumentException("ディレクトリを含まないパスです。", nameof(originalPath));

        string directory = backupDirectory ?? sourceDirectory;

        if (!Directory.Exists(directory))
        {
            Directory.CreateDirectory(directory);
        }

        string fileNameWithoutExt = Path.GetFileNameWithoutExtension(originalPath);
        string extension = Path.GetExtension(originalPath);

        string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");

        string backupFileName = $"{fileNameWithoutExt}_{timestamp}{extension}.bak";

        string backupPath = Path.Combine(directory, backupFileName);

        File.Copy(originalPath, backupPath, overwrite: false);

        return backupPath;
    }
}
C#

使い方の例です。

string original = @"C:\config\appsettings.json";

string backup = BackupUtil.CreateTimestampBackup(
    original,
    backupDirectory: @"C:\backup\config");

Console.WriteLine($"バックアップ作成: {backup}");
C#

ここでの重要ポイントを深掘りします。

タイムスタンプのフォーマットを固定する
"yyyyMMdd_HHmmss" のように、ソートしたときに時系列順になる形式を使うのが定番です。
例: appsettings_20250128_203015.json.bak

バックアップ専用ディレクトリを指定できるようにする
backupDirectory を省略したら元ファイルと同じ場所、
指定したらそこにまとめて保存、という柔らかい仕様にしています。

File.Copyoverwrite: false にしている
タイムスタンプ付きなので基本的に重複しませんが、
万が一同じ名前が存在した場合は例外にして気づけるようにしています。


フォルダ丸ごとのバックアップ作成

ディレクトリを再帰コピーする

設定ファイル一式やテンプレート一式など、「フォルダごとバックアップしたい」ケースも多いです。
その場合は、ディレクトリを再帰的にコピーするユーティリティを用意します。

using System;
using System.IO;

public static class DirectoryBackupUtil
{
    public static void CopyDirectory(string sourceDir, string destinationDir, bool overwrite = false)
    {
        if (!Directory.Exists(sourceDir))
        {
            throw new DirectoryNotFoundException($"コピー元ディレクトリが存在しません: {sourceDir}");
        }

        Directory.CreateDirectory(destinationDir);

        foreach (string filePath in Directory.GetFiles(sourceDir))
        {
            string fileName = Path.GetFileName(filePath);
            string destFilePath = Path.Combine(destinationDir, fileName);

            File.Copy(filePath, destFilePath, overwrite);
        }

        foreach (string subDir in Directory.GetDirectories(sourceDir))
        {
            string dirName = Path.GetFileName(subDir);
            string destSubDir = Path.Combine(destinationDir, dirName);

            CopyDirectory(subDir, destSubDir, overwrite);
        }
    }
}
C#

これを使って、「日付付きフォルダにバックアップを取る」例です。

string sourceDir = @"C:\app\data";
string backupRoot = @"C:\backup\data";

string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string backupDir = Path.Combine(backupRoot, timestamp);

DirectoryBackupUtil.CopyDirectory(sourceDir, backupDir);

Console.WriteLine($"フォルダバックアップ完了: {backupDir}");
C#

ここでの重要ポイントは、

Directory.CreateDirectory は「既にあっても OK」
存在しないときは作り、存在するときは何もしません。
そのため、バックアップ先フォルダの作成に安心して使えます。

ファイルとサブディレクトリを分けて処理する
Directory.GetFilesDirectory.GetDirectories を使い、
ファイルは File.Copy、サブディレクトリは再帰呼び出しでコピーしています。

バックアップ先の構造を「日付フォルダ」で整理する
backupRoot\yyyyMMdd_HHmmss のようにしておくと、
「いつのバックアップか」が一目で分かり、削除ポリシーも立てやすくなります。


ファイル名重複回避との組み合わせ

タイムスタンプ+重複回避で“ほぼ絶対に”かぶらない名前にする

タイムスタンプを付けても、理論上は「同じ秒に複数回バックアップを取る」とかぶる可能性があります。
そこまでシビアでなくていい場面も多いですが、
「絶対に上書きしたくない」場合は、ファイル名重複回避ユーティリティと組み合わせると安心です。

例えば、先に作ったような GetUniqueFilePath を使うイメージです。

public static string CreateSafeTimestampBackup(string originalPath, string backupDirectory)
{
    if (!Directory.Exists(backupDirectory))
    {
        Directory.CreateDirectory(backupDirectory);
    }

    string fileNameWithoutExt = Path.GetFileNameWithoutExtension(originalPath);
    string extension = Path.GetExtension(originalPath);
    string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");

    string desiredName = $"{fileNameWithoutExt}_{timestamp}{extension}.bak";
    string desiredPath = Path.Combine(backupDirectory, desiredName);

    string uniquePath = FileNameUtil.GetUniqueFilePath(desiredPath);

    File.Copy(originalPath, uniquePath, overwrite: false);

    return uniquePath;
}
C#

こうしておくと、

タイムスタンプでまずほぼ一意になる
それでもかぶったら (1), (2) が付いて回避される

という二重の安全策になります。


実務での設計ポイント

「どこまで残すか」を最初に決める

バックアップは「取れば取るほど安心」ですが、
ディスク容量は有限です。

実務では、次のようなポリシーを決めておくと運用が楽になります。

何世代分残すか
直近 7 日分だけ残す、直近 30 世代だけ残す、など。

どのタイミングで古いバックアップを消すか
新しいバックアップ作成後に古いものを削除する、
夜間バッチで一括削除する、など。

削除処理自体もユーティリティ化しておくと、
「バックアップフォルダが肥大化してサーバーがパンクする」といった事故を防げます。

「バックアップが失敗したらどうするか」

バックアップ作成は、ディスク容量不足や権限不足で失敗することがあります。
そのときに、

バックアップ失敗をログに残すだけで処理を続行するのか
バックアップが取れなければ本処理を中止するのか

を決めておく必要があります。

設定ファイルの上書き前バックアップなどは、
「バックアップが取れないなら上書きしない」という方針のほうが安全です。


まとめ 「バックアップ作成ユーティリティ」は“安心して壊せる環境”を作る

バックアップ作成は、「壊さない」ための技術ではなく、
「壊しても戻せる」ための技術です。

押さえておきたいポイントは次の通りです。

単一ファイルのバックアップは、File.Copy を使って別名・別フォルダにコピーするだけだが、名前の付け方と場所の設計が重要。
履歴を残したい場合は、ファイル名やフォルダ名に yyyyMMdd_HHmmss 形式のタイムスタンプを埋め込むと、時系列管理がしやすい。
フォルダ丸ごとのバックアップは、再帰コピーユーティリティを 1 本持っておくと、いろいろな場面で再利用できる。
タイムスタンプ+ファイル名重複回避を組み合わせると、「ほぼ絶対に上書きしない」バックアップ名を自動生成できる。
何世代残すか、失敗したらどうするか、といった運用ポリシーまで含めて考えると、“業務で本当に使えるバックアップユーティリティ”になる。

ここまで理解できれば、「とりあえず上書きしてしまう」コードから卒業して、
「壊す前に必ずコピーを取る」という、プロっぽい習慣を自分の C# ユーティリティに埋め込めるようになります。

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