Linuxにおけるディレクトリ内容の再帰的移動に対する実務上の答えは「mvはディレクトリ自体を指定すれば配下をまとめて移動でき、内容だけを移す場合の*はdotfileを含みません。対象をfindで列挙し、移動先の空き容量・重複名・mount境界を確認し、backupまたはsnapshotを取ってから少量ずつ移します。」です。ここでは同一filesystemのrenameと別filesystemへのcopy-and-removeで障害時の性質が異なる前提を明示し、初回確認、sample実行、本番判定、元へ戻す条件をそれぞれ独立させます。
移動元・移動先とfilesystem境界を固定する
ディレクトリを再帰移動する前に、移動元と移動先のファイルシステム、空き容量、所有者、シンボリックリンクの扱いを確認します。rsyncのdry-runとitemize出力で対象を固定し、コピー後の件数とハッシュが一致してから切り替えます。元ディレクトリの削除は別工程として承認後に行います。
- find source -mindepth 1 -maxdepth 1 -printでdotfileを含む直下を確認する
- findmnt -T sourceとfindmnt -T destinationでfilesystemを比較する
- df -hとdf -iで容量とinode余裕を確認する
- 移動先に同名file、symlink、権限差がないか照合する
trailing slashとdotfileで変わる対象を読む
- アスタリスクでdotfileを落とす
- 同名directoryへmergeされる動作を見落とす
- 別filesystemをatomic renameと思い込む
- 開かれたDBfileを稼働中に移す
- ACL、xattr、hard linkを保持しないcopyへ置き換える
同一filesystemのmvと段階rsyncを使い分ける
ディレクトリ自体を移す
printf '%s\n' '移動元:'
find ./project -maxdepth 2 -print
printf '%s\n' '移動先:'
ls -ld -- ./archive
mv -i -- ./project ./archive/
-iは上書き前に確認します。非対話の自動処理ではprompt待ちになるため、事前に重複を検査し、終了コードを確認する設計へ変えます。
内容だけをdotfile込みで候補表示
find ./source -mindepth 1 -maxdepth 1 -print
shellの*は通常、名前がドットで始まる項目を展開しません。findの一覧を保存し、.envや.gitなど移動してよいdotfileか個別に判断します。
別filesystemはrsyncで段階移行
rsync -aHAXni --numeric-ids -- ./source/ ./destination/
# itemized changeを保存し、対象・件数・metadata方針を承認する
rsync -aHAXi --numeric-ids -- ./source/ ./destination/
# 実copy後、itemized outputが空になることを再確認する
rsync -aHAXni --numeric-ids -- ./source/ ./destination/
-nはdry-run、-iは変更候補のitemize表示です。最初の確認から両方を併用し、ACLやxattrを使わない環境では保存要件とoption対応を確認します。実コピー後に件数とhashを確認しても、元データの削除は別の承認済み作業に分けます。
重複候補を確認
find ./source -mindepth 1 -maxdepth 1 -printf '%f\n' | sort > source.names
find ./destination -mindepth 1 -maxdepth 1 -printf '%f\n' | sort > destination.names
comm -12 source.names destination.names
同名項目が出たら機械的に上書きせず、内容と所有者を比較します。この確認ファイルは作業記録へ保存し、終了後に規程に沿って扱います。
既存名との衝突を移行前に洗い出す
mvに再帰オプションは不要で、ディレクトリをoperandにすれば全体を扱います。ただしsource/*はshell展開でありdotfileを落とします。source/.*を足す方法は.や..、shell差、重複の問題を招くため、内容だけの移行にはfindやrsyncで対象を明示します。cross-filesystemではコピー後に元を除く動作になり、途中失敗で両側に一部が残る可能性があります。
itemized dry runを承認記録にする
移動はパスを変えるため、サービス、symlink、設定、backup jobから見えなくなる重大な変更です。maintenance windowを取り、書き込みを止めるか整合性の取れるsnapshotを準備します。最初に読み取り一覧とdry-runを保存し、小さいsubsetで移動後の起動確認を行います。異常時は同一filesystemなら元の親へmvで戻し、別filesystemなら検証済みcopyを元位置へ戻して所有者、ACL、xattrを照合します。未確認の再帰削除で整理しません。
file hashとmetadataをcopy後に照合する
findの件数とdu –bytesの概算を両側で比較し、重要ファイルはsha256sum manifestを作って照合します。所有者とmodeはstat、ACLはgetfacl、xattrはgetfattrの利用可否を環境で確認します。サービスから新pathを読み、監視、backup、log rotationが追随したことを確認して初めて完了とします。
source削除へ進めるparity gateを定義する
同一filesystemのrenameはmvで短時間に完了できますが、別filesystemではrsync -aHAXのdry-run、実copy、再dry-run、切替の順に進めます。rsyncは成功0、syntax/usage 1、partial transfer 23/24などを返すため、0以外を一括で成功扱いしません。
-niで実copyと同じ候補を表示する
rsync -aHAXni --numeric-ids -- ./source/ ./destination/
printf 'dry_run_status=%s\n' "$?"
変更あり・差分なし・転送errorを分類する
- copy候補承認済み:dry-runのitemized changeを承認し、実copy後の再dry-runが空で件数とhashが一致する
- 転送差分なし:sourceが空でも意図した空directoryかpath誤りかを判定し、勝手にdestinationを空へ揃えない
- 容量・権限・metadata error:permission/ACL/xattr非対応、vanished file、容量不足はrsync statusとstderrを残して切替を止める
source末尾slashは内容、slashなしはdirectory自体という差があります。-aだけではhard link、ACL、xattrをすべて含まないため、-HAXの対応filesystemと必要権限を確認します。–deleteは移行確認の既定にしません。
hardlink・ACL・xattrを含むfixtureで試す
( cd ./source && find . -xdev -type f -print0 | sort -z | xargs -0 -r sha256sum ) > source.sha256
( cd ./destination && find . -xdev -type f -print0 | sort -z | xargs -0 -r sha256sum ) > destination.sha256
cmp -- source.sha256 destination.sha256
通常file、dotfile、symlink、hard link、ACL、xattr、空directory、更新中fileをtest treeへ置きます。SHA-256 manifestはregular fileの内容を照合するためのものです。directory構造、symlink先、hard link関係、owner・mode、ACL、xattrはfind、stat、getfacl、getfattrで別に照合し、open fileを持つserviceの切替時刻も確認します。
rsyncの最初の確認は-nだけでなく-iを併用し、実copyでも同じitemized形式を残します。copy後の再dry-run、hash、ACL・xattr照合がそろうまで、移動元の削除は独立した未実施工程として扱います。

コメント