SSD Nodes Learn 🎉 VPS $5.50/月〜
ガイド Matt Connor著者 Matt Connor

rm -rfで削除したファイルを復旧する方法

誤ったパスに rm -rf を実行した直後は、ディスクへの書き込みを止めてアンマウントします。ext4で実際に可能な復旧手順を順番に解説します。

最初の 60 秒で行うこと

rm -rfで削除したファイルを復旧できるかどうかは 2 つの対応で決まり、どちらも検索エンジンを開く前に行います。そのファイルシステムへの書き込みを停止します。次に、アンマウントするか read-only で再マウントして、使用を停止します。

rmは何も消去しません。ディレクトリエントリを削除し、inode とファイルのデータブロックを空きとしてマークします。バイト列はまだデバイス上に残っています。ブロックアロケーターがそれらのブロックを別の用途に割り当て、その用途でデータが上書きされるまで残ります。ファイルシステムがマウントされたまま使用されている間は、1 秒ごとにデーモンがログを書き込んだり、データベースがページをフラッシュしたりします。どちらの書き込みも、復旧したいブロックに行われる可能性があります。

そのため、最初に実行するコマンドはファイルを復旧するものではなく、書き込みを停止するものです。

sudo systemctl stop nginx postgresql
sudo umount /mnt/data

umountの結果がumount: /mnt/data: target is busy.の場合は、ファイルシステムを開いたままにしているプロセスを調べます。

sudo fuser -vm /mnt/data
sudo lsof +D /mnt/data

解放できない場合は、代わりに read-only で再マウントします。read-only マウントにすると新しい割り当てが停止するため、必要な対策の大部分を実施できます。

sudo mount -o remount,ro /mnt/data

削除したパスが root ファイルシステム上にある場合は、さらに難しくなります。実行中のプロセスがファイルを書き込み用に開いたままにしており、kernel はそれらを強制的に閉じないため、通常はsudo mount -o remount,ro /mount: /: cannot remount /dev/vda1 read-only.で失敗します。VPS では、プロバイダーの rescue mode または recovery mode を使用するのが現実的です。このモードでは、ディスクを接続した状態で、マウントしていない別の live system を起動します。以降のコマンドはすべて、どのプロセスも書き込んでいないデバイスに対して実行されます。

このガイド全体に適用されるルールが 1 つあります。復旧元のファイルシステムに、復旧したファイル、ディスクイメージ、新しくインストールしたツールを書き込まないでください。2 台目のボリュームを接続するか、SSH 経由で別のマシンに出力を送信します。

ext4 での rm -rf 後の復旧がほぼ不可能な理由

何かをインストールする前に、期待値を調整してください。使用しているファイルシステムを確認します。

lsblk -f

ほぼすべての VPS イメージでデフォルトになっている ext4 では、ファイルデータの場所が inode 内の extent tree に保存されます。extent は、ファイルの論理ブロック N が物理ブロック M から始まり、L ブロック分続くことを示すレコードです。小さなファイルでは、最大 4 個のレコードを inode 自体に保持します。大きなファイルでは、残りの tree を保持する追加ブロックを参照します。

ファイルへの最後のリンクがなくなると、ext4 はその tree をたどり、すべての extent をブロックアロケーターに返して、inode から tree を消去します。その後、inode は空きとしてマークされ、削除時刻が記録されます。データ自体は変更されません。ただし、そのデータの場所を示す唯一の記録が消去されます。

これは、削除された inode に ext3grep のようなツールがたどるための情報が十分に残っていた ext3 との違いです。ext4 でも削除済み inode の一覧は取得できます。

sudo debugfs -R lsdel /dev/vdb1

debugfs-w を指定しない限りデバイスを読み取り専用で開くため、アンマウントしたデバイスに対して安全に実行でき、試してもコストはかかりません。inode は一覧表示されます。しかし、1 つをダンプする段階で行き詰まります。その inode が保持していたブロックマップは消去されているため、dump がたどる対象がないからです。

この問題を回避しようとして、ジャーナルを読み取るツールが 2 つあります。ジャーナルは、クラッシュ後もメタデータの整合性を保つために ext4 が使用する固定サイズのリングです。削除前の inode の古いコピーが残っている場合もあります。extundeleteext4magic はどちらもジャーナルを検索します。使用しているサイズを確認してください。

sudo dumpe2fs -h /dev/vdb1 | grep -i journal

ジャーナルにはメタデータだけが保存され、サイズも小さいため、通常の書き込みですぐに一巡します。稼働中のサーバーでは、削除前の inode が残っている時間は数分程度です。どちらのツールも現在は積極的に保守されておらず、すべてのディストリビューションでパッケージ化されているわけでもありません。どちらも可能性の低い手段と考えてください。アンマウントしたデバイス、またはディスクイメージに対して実行し、何も返らなくても驚かないでください。

lsblk -fxfs を報告する場合も状況は改善しません。XFS にもサポートされた undelete の手段がないためです。以下のオプションの順序を変更しても結果は変わりません。

実行中のプロセスがファイルを開いたままですか?

このページで成功する可能性が高い復旧方法はこれだけです。そのため、ファイルを使用していたサービスを再起動してはいけません。

ファイルが完全に消えるのは、2 つのカウントが 0 になったときだけです。1 つは、その inode を指すディレクトリエントリの数です。もう 1 つは、開いているファイルディスクリプターの数です。rm は最初のカウントを 0 にします。プロセスがファイルを開いたままなら、2 つ目のカウントは 0 ではありません。そのため、inode とそのブロックは割り当てられたままで、データも読み取れます。

リンク数が 0 になった開いているファイルを探します。

sudo lsof +L1

+L1 は、リンク数が 1 未満の開いているファイルを一覧表示します。各項目には、プロセス、ファイルディスクリプター番号、NLINK0、そして (deleted) で終わるパスが表示されます。PID とディスクリプター番号を使って /proc します。

sudo ls -l /proc/1234/fd

項目は 3 -> /var/log/app/events.log (deleted) のように表示されます。このリンクから、まだデータにアクセスできます。別のファイルシステムへコピーします。

sudo cp /proc/1234/fd/3 /mnt/rescue/events.log

mv ではなく cp を使用してください。/proc/1234/fd/3 を開くと、同じ inode に対してオフセット 0 から新しいハンドルが作成されます。そのため、書き込みプロセスの現在位置以降ではなく、ファイル全体を取得できます。

知っておくべき制限が 2 つあります。削除されたディレクトリツリーは、この方法では復元できません。プロセスが開いたまま保持しているのは、個々のファイルだけだからです。また、データベースエンジンが書き込み中にコピーしたデータベースファイルは、クラッシュ整合性のあるコピーです。クリーンなファイルとして扱わず、エンジン独自の復旧を実行する計画を立ててください。lsof がディスクリプター番号の代わりに mem を表示する項目は、メモリーマップされています。そのような項目には、コピー元にできる /proc/<pid>/fd エントリがありません。

btrfs、ZFS、または LVM にスナップショットはありますか?

ファイルシステムがスナップショットを作成する構成であれば、削除したファイルは変更されないまま、すでにスナップショット内に残っています。これは、削除前にスナップショットが存在していた場合にだけ役立ちます。今から作成するスナップショットで、過去の状態に戻ることはできません。

btrfs はスナップショットをサブボリュームとして保持します。

sudo btrfs subvolume list /

スナップショットを参照し、必要なパスを cp -a でコピーして取り出します。サブボリューム全体をロールバックするより、個別のパスをコピーする方法を優先してください。ロールバックすると、スナップショット作成後に書き込まれたすべての内容も破棄されるためです。

ZFS は各スナップショットを読み取り専用ディレクトリとして公開します。

zfs list -t snapshot
ls /tank/data/.zfs/snapshot/

.zfs ディレクトリは非表示で、データセットのルートに対する通常の ls には表示されません。ただし、名前を指定すれば移動できます。そこからファイルをコピーして取り出します。zfs rollback はデータセット全体を指定したスナップショットの状態に戻し、それより新しいすべてのスナップショットを破棄するため、最後の手段にしてください。

LVM スナップショットは、固定サイズの copy-on-write ボリュームです。

sudo lvs
sudo mount -o ro /dev/vg0/data-snap /mnt/snap

読み取り専用でマウントし、内容をコピーして取り出します。信頼する前に lvs を確認してください。割り当てられた領域を使い切った LVM スナップショットは kernel によって無効化され、その時点で内容は失われるためです。

スナップショットはバックアップではありません。元のデータと同じディスクまたは同じプール上にあるため、元のデータと同じ障害の影響を受けます。2 分前の操作ミスを取り消す用途には非常に適しており、今回まさに必要なのはその機能です。

PhotoRec を使用してイメージを復元する。稼働中のディスクには使用しない

上記のどれにも当てはまらない場合、残る手段はカービングです。これは、既知のファイル形式の開始位置を示すバイトパターンを raw デバイスから検索し、その後に続くデータを書き出す方法です。カービングで読み取るのはファイルデータだけです。ファイル名、ディレクトリ構造、タイムスタンプ、所有者情報はすべてファイルシステムのメタデータであり、rm が破壊したのはそのメタデータです。そのため、これらは復元されません。f0384512.jpg のような名前のファイルが番号付きの出力ディレクトリに保存されるため、手作業で分類します。

この方法が機能するかどうかは、2 つの原則で決まります。

1 つ目は、ほかの操作を行う前にデバイスのイメージを作成することです。Debian と Ubuntu では、パッケージは gddrescue で、インストールされるバイナリは ddrescue です。

sudo apt update && sudo apt install -y gddrescue testdisk
sudo ddrescue -n /dev/vdb1 /mnt/rescue/vdb1.img /mnt/rescue/vdb1.map

/mnt/rescue は別のデバイス上に置き、パーティションの容量以上の空き領域を確保する必要があります。lsblk -b で正確なサイズをバイト単位で表示できます。map ファイルを使用すると、中断したコピーを最初からやり直さずに再開できます。イメージを作成しておけば、後からまったく同じバイト列に対して別のツールを試せます。最初のツールがディスクを上書きした場合は、この操作ができません。

2 つ目は、復旧ツールの対象としてイメージファイルを指定することです。

sudo photorec /d /mnt/rescue/recup /mnt/rescue/vdb1.img

photorec を実行すると、テキストメニューが開きます。パーティション、ファイルシステムの種類、検索するファイルシグネチャ、保存先ディレクトリの順に選択します。開始前に、失った可能性があるファイル形式だけにシグネチャの一覧を絞り込んでください。デフォルトの一覧ではすべてを検索するため、分類が必要な断片が数万個生成されます。

同じパッケージに含まれる testdisk には独自の undelete 機能があり、対応するのは FAT、exFAT、NTFS、ext2 だけです。ext4 では photorec となります。

断片化したファイルは、壊れた状態で復元される可能性があります。カービングはファイルのブロックが連続していることを前提とするため、アロケーターによってディスク上の複数箇所に分割されたファイルは、誤って再構成されるか、完全に見つからないことがあります。メディアファイルは強いヘッダーを持つため、比較的うまくカービングできます。一方、プレーンテキスト、設定ファイル、ソースコードはうまくカービングできません。シェルスクリプトの開始位置を示すバイトシグネチャが存在しないためです。

余分な空白: 間違ったパスが削除された理由

ほとんどすべての rm -rf 事故は、シェルの問題です。rm はパスの一覧を受け取り、各パスを順番に削除します。意図した内容を判断することはありません。

典型的な原因は、単一の空白です。

rm -rf /home/deploy/app /old
rm -rf /home/deploy/app/old

1 行目は 2 つの引数です。アプリケーションを削除し、その後で /old を削除します。/old が存在しない場合、rm は何も出力しません。-f が存在しないファイルのエラーを抑制するためです。何も表示されないことは、処理が正しいことの確認にはなりません。

2 つ目は、空白を含む値を保持した、引用符で囲まれていない変数です。

dir="/srv/my app"
rm -rf $dir

シェルは空白類文字で値を分割するため、rm/srv/myapp を別々のパスとして受け取ります。rm -rf "$dir" と記述すれば、1 つのパスになります。

3 つ目は、空の変数です。通常は、その変数に値を設定するはずのコマンドが失敗したことが原因です。

rm -rf "$TARGET"/*

TARGET が未設定の場合、これは rm -rf /* に展開されます。GNU rm は裸の形式を拒否します。rm -rf /rm: it is dangerous to operate recursively on '/' を出力して停止します。glob 形式にはこの保護がありません。シェルは rm が実行される前に /* を実在するトップレベルパスの一覧へ置き換えるためです。/ はその一覧に含まれないので、ガードは実行されません。

次の事故を防ぐ習慣

  • パスとして使用するすべての変数をクォートします。テストやループ内も含め、毎回 "$dir" と記述します。
  • 空の値で失敗させます。rm -rf "${TARGET:?TARGET is not set}"/* を設定すると、TARGET が未設定または空の場合、rm が開始する前にメッセージを表示してシェルを停止します。削除を行うスクリプトでは、先頭に set -euo pipefail を記述します。
  • --one-file-system を追加します。これにより rm は、指定した引数とは異なるファイルシステム上にあるディレクトリを処理しません。そのため、再帰的な削除がマウントされたバックアップボリュームや bind mount 内へ進むことを防げます。
  • root で削除しないでください。サービスアカウントは、自身が所有するファイルだけを削除できます。これは、各サービスを専用の非特権ユーザーで実行することの基本的な理由です。特定のアカウントがアクセスできる範囲が不明な場合は、ls の一覧で権限ビットを確認することで、1 つのコマンドで判断できます。
  • 操作する前に一覧を出力します。スクリプトではパスを作成し、printf '%s\n' し、その出力を確認してから、2 回目の処理で削除します。
  • ごみ箱コマンドをすぐ使える状態にしておきます。sudo apt install trash-cli により、trash-puttrash-listtrash-restoretrash-empty を利用できます。削除したファイルは ~/.local/share/Trash に移動し、trash-empty 30 で 30 日より古いものを削除できます。

rmtrash-put の別名にするのは、次に行う対策として自然に思えます。しかし、これは罠です。その別名が設定されていない次のサーバーで、その習慣が通用しなくなるためです。また、別名はスクリプト内では適用されません。重大なミスはスクリプト内で発生します。代わりに、trash-put と明示的に入力してください。

毎回機能する唯一の復旧手段

ここまでの対策は、どれも可能性にすぎません。バックアップは可能性ではありません。

バックアップを実用的なものにする要件は2つあります。自分で覚えていなくてもスケジュールどおりに実行されることと、少なくとも1回はそこから復元していることです。一度も復元したことのないリポジトリは、信念にすぎません。バックアップを役に立たなくする要因(include リストの誤ったパスや、誰も記録していないリポジトリパスワードなど)は、必要になった日に初めて明らかになるためです。

restic では、復元は2つのコマンドで実行できます。

restic -r /srv/restic-repo snapshots
restic -r /srv/restic-repo restore latest --target /mnt/rescue --include /srv/appdata

稼働中のパスに上書きするのではなく、空のディレクトリへ復元してください。移行する前に、両方を比較できます。VPS で restic バックアップを設定するでは、リポジトリの設定と、バックアップを実行する systemd timer を説明しています。

Borg の場合は次のとおりです。

borg list /srv/borg-repo
borg extract /srv/borg-repo::daily-2026-08-08 srv/appdata

Borg archive 内のパスは、先頭のスラッシュを除いて保存されます。そのため、srv/appdata は一致しますが、/srv/appdata は何も一致しません。borg extract は現在の作業ディレクトリへ書き込むため、まず cd で一時ディレクトリを指定してください。

まだどちらを選ぶか決めていない場合は、restic と Borg の比較で、重複排除と append-only リポジトリについて確認できます。これは、侵害されたサーバーが自分のバックアップ履歴を削除することを防ぐ特性です。どちらのツールでも問題ありません。誤った選択は、どちらも実行しないことです。

新しいサーバーでは、失うと困るものがまだ存在しないため、この設定を始めるのが最も簡単です。新しい VPS で最初の10分間に行うことでは、SSH とファイアウォールの設定とあわせて、この作業を扱っています。

その後、カレンダーに定期的な予定を登録してください。毎月1回、リポジトリから1つのディレクトリを /tmp へ復元し、ファイルを読みます。この習慣1つのほうが、このページにあるすべてのツールより価値があります。

FAQ

ext4 上のファイルを削除取り消しできますか?

通常はできません。ファイルへの最後のリンクがなくなると、ext4 は inode から extent tree を消去するため、データがどこにあったかを記録する情報がディスク上に残りません。extundeleteext4magic は ext4 のジャーナルを検索して、その inode の古いコピーを探します。ただし、削除から数分しか経過しておらず、その後ファイルシステムがほとんど変更されていない場合に限り有効です。どちらのプロジェクトも現在は積極的に保守されていません。いずれもアンマウントしたデバイスまたはディスクイメージに対して実行し、マウント中のファイルシステムには決して実行しないでください。最初に sudo dumpe2fs -h /dev/vdb1 | grep -i journal を使って対象を確認します。

サービスが削除済みファイルを開いたままの場合、復元できますか?

はい。この状態が最も復元しやすいケースです。プロセスがファイルを開いたままなら、inode とデータブロックは割り当てられたままなので、データを読み取れます。サービスを再起動しないでください。最後のファイルディスクリプターを閉じると、削除が完了するためです。sudo lsof +L1 を実行して、リンク数が 0 のオープンファイルを一覧表示します。PID とファイルディスクリプター番号を確認し、/procsudo cp /proc/1234/fd/3 /mnt/rescue/events.log を使ってコピーします。コピー先には別のファイルシステムを指定してください。ファイルディスクリプター番号の代わりに mem と表示されるエントリはメモリーマップされています。そのため、コピー元となる /proc/<pid>/fd パスがありません。

復元ツールをディスク上で実行せず、ディスクイメージを作成するべき理由は何ですか?

どのツールも出力先への書き込みが必要であり、復元対象のファイルシステムに書き込むと、データがまだ残っている空きブロックを上書きする可能性があるためです。最初に sudo ddrescue -n /dev/vdb1 /mnt/rescue/vdb1.img /mnt/rescue/vdb1.map を使ってパーティションを別のデバイスへコピーし、その後、photorec にイメージファイルを指定します。イメージがあれば、後からまったく同じバイト列に対して別のツールを試せます。元のディスクが上書きされた後では、これはできません。

rm -rf / を実行すると、現在でも Linux システムは破壊されますか?

そのままのコマンドでは破壊されません。GNU rm はこのコマンドを拒否し、rm: it is dangerous to operate recursively on '/' を表示します。危険なのは、別の経路で同じ形になるコマンドです。TARGET が未設定の状態で rm -rf "$TARGET"/* を実行すると、rm -rf /* に展開されます。するとシェルは実在するトップレベルディレクトリの一覧を rm に渡します。その一覧に / は含まれないため、保護機能は作動しません。代わりに "${TARGET:?TARGET is not set}" と記述すると、シェルは rm の実行前に停止します。

ファイルシステムのスナップショットはバックアップですか?

いいえ。btrfs または ZFS のスナップショットは、保護対象のデータと同じプール上に存在します。そのため、ディスク障害やプールの破壊が発生すると、両方が同時に失われます。LVM スナップショットには、固定サイズであるという別の問題もあります。容量を使い切ると、カーネルがスナップショットを無効化し、内容が失われます。スナップショットは、2 分前の削除を取り消す用途には非常に有効です。それ以外の用途では、別のハードウェア上にリポジトリを保持してください。

#linux#rm#data-recovery#backups#ext4