[rsync] ディレクトリ間の同期(OR)
ディレクトリhogeとディレクトリhageがあって、hageさんとhogeさんがそれぞれのディレクトリの内容を変更する。この時2つのディレクトリの内容のORを取りたい。同じファイルを編集されると困るけど。
$ diff hoge/ hage/ $ rsync -avz hoge/ hage/ $ diff hoge/ hage/ $ rsync -avz hage/ hoge/ $ diff hoge/ hage/
2回rsyncして--deleteしないことがキモ。始めにこんなかんじだったのが、
+- hage/ | a.txt \ A.txt +- hoge/ | a.txt \ B.txt
1回目のrsyncで、こんな感じになり、
+- hage/ | a.txt | A.txt \ B.txt +- hoge/ | a.txt \ B.txt
2回目のrsyncで、こんな感じになる
+- hage/ | a.txt | A.txt \ B.txt +- hoge/ | a.txt | A.txt \ B.txt
ということで、同期できたことになる。今回の想定はhage/とhoge/に共通に存在するa.txtは全く同じ物とする。
[rsync] HDDを大容量に変えた後のHDD
まずはマウント。500GBのHDDは/dev/hdc1、160GBのHDDは/dev/hdd1にあるとする。
# mount /dev/hdc1 /mnt/hdc1 # mount /dev/hdd1 /mnt/hdd1
今回は160GBのHDD(/mnt/hdd1)から500GBのHDD(/mnt/hdc1)に向けてのコピー。rsyncを--dry-run(-n)してみる。ドライランのオプションはrsyncのやることを表示してくれるが、実際に仕事は行われないオプション。つまり、テストのためのオプション。
# rsync -avv --exclude "foo/" -n /mnt/hdd1 /mnt/hdc1 huga.txt hoge/hoga.txt is uptodate hoge/hogo.txt
このようにテストしてみると、完全に同期が取れているものに関しては'~ is uptodate'のようにして表示してくれる。つまり、今回削除したいファイルは'is uptodate'の付いたファイルだ。このまま-nを除いてrsyncを走らせるとis uptodateの付かなかったファイルに関して/mnt/hdc1にコピーが行われる。そのため、このままrsyncを走らせてはいけない。今回の目的は送信側のファイルを削除することにある。rsyncのmanページで送信側のファイルを削除するオプションを探す。
一通り見てみた限りでは、受信側のファイルを削除するオプションはあるが、送信側のファイルを削除するオプションは無い。送信側と受信側を入れ替えて削除オプションを効かせてみればどうかとも思ったが、どの削除系オプション(--delete)を試してみてもうまいこと篩い分け出来ない。そこで、先に述べたis uptodateの付いたファイルのみを抜き出すためだけにrsyncを使い、is uptodateの情報を元に目的のファイルを削除するシェルスクリプトを書いてみる。
まずは削除対象となるファイルの抜き出し。rsyncでis uptodateと判断されたファイルの名前をrm.txtにリダイレクト。rm.txtは削除対象のファイル名が1行おきにリストされたファイルになる。
# rsync -avv --exclude "foo/" -n hdd1/ hdc1/ | grep --regex ' is uptodate$' | sed "s/ is uptodate$//" > rm.txt
- Command Technica:はじめてrsyncを使う方が知っておきたい6つのルール (1/2) - ITmedia エンタープライズ
- Command Technica:はじめてrsyncを使う方が知っておきたい6つのルール (2/2) - ITmedia エンタープライズ
- rsync
ファイル名を元にrmしていく際にはrmに正しく引数が渡されるようにしなければならない。つまり、正しくエスケープしなければならない。そのためにいったんechoコマンドで引数が正しく渡されるかどうか確認。エスケープの指針として、シングルコーテーションで括るが、シングルコーテーションで括ってもエスケープできないファイル名自体に含まれるシングルコーテーションについてはシングルコーテーションで括る前に'"'"'で置換しておく。バッドノウハウであることは重々承知だが、これでよい。其の後にこれを走らせ、元の削除対象リストの内容と比較する。
# cat rm.txt | sed "s/'/'\"'\"'/g" | sed "s/$/\'/" | sed "s/^/echo \'/" > rm2.sh # sh rm2.sh > rm2.txt # diff rm.txt rm2.txt
- shellscripts - ReoGlobalBrain:PukiWikiAnnex
- §10. このファイル消せますか?(rm, bash, シェル) その3
- i-nodeを指定してファイルを削除したい(それでも削除できないやばいファイルができてしまった) - 針と糸
- How to: Linux / UNIX Delete or Remove Files With Inode Number
- Linux : How to delete file securely
出来たシェルスクリプト(rm2.sh)を走らせた結果(rm2.txt)と元の削除対象リスト(rm.txt)が同じならば、上の指針で行ったエスケープ処理が成功しechoコマンドに正しく引数が渡されたことになる。linuxの場合、引数を渡すコマンドが違っていても、エスケープされた引数を解析してコマンドに渡すまでの仕事は共通のシェル(ここではbash)がしてくれるので、rmに渡す引数のエスケープ処理とechoに渡すエスケープ処理は同じでよいはずだ。そこで、rm2.shを作ったコマンドラインを編集してrmするようなシェルスクリプトを生成する。
# cat rm.txt | sed "s/'/'\"'\"'/g" | sed "s/$/\'/" | sed "s/^/rm -- \'/" > rm3.sh
ここで生成されたシェルスクリプト(rm3.sh)を/mnt/hdd1の中で走らせれば目的が達成される。最後の確認としてviで内容を見ておこう。
# vi rm3.sh # cd /mnt/hdd1 # sh ~/rm3.sh
さて、このようにしてファイルを削除することは出来るが、空になったディレクトリを削除することは出来ない。安全かつ再帰的に空になったディレクトリを削除する良い方法は、削除したいディレクトリの親ディレクトリの中でfindとrmdirを使うことだ。rmdirは引数として与えられたディレクトリの中味が無い場合のみそのディレクトリを削除する。引数がファイルだったり中味のあるディレクトリだった場合には削除せずエラー終了する。そのため、findからもらった引数にファイル名や中味のあるディレクトリが含まれていてもこれらの場合は何もせずに終了してくれるので、rmdirの前に引数チェックしないで使えるため便利である。
# cd /mnt/hdd1 # find -print0 | xargs -0 rmdir -p
これで、目的が達成できたはずだ。/mnt/hdd1の中から、/mnt/hdc1と同期の取れたファイルやディレクトリは削除された。