餡子付゛録゛

ソフトウェア開発ツールの便利な使い方を紹介。

ファイル喪失で半泣きにならないためのrsyncの練習

ネットワーク越しでもsshを通して更新ファイルだけ転送してくれるのでファイル単位のミラーリングが容易にとれるrsyncは何かと便利な一方、山のようにオプションがある上にオペレーションミスで即死するときがあるツールです。同一ホスト内で、挙動を確認してから実践しましょう。

練習環境の整備

同じ環境から試行錯誤した方が良いので、シェルスクリプトを書きます。

setup.sh

#!/bin/sh
WORKDIR=/var/tmp/rsync
SRC=${WORKDIR}/src
DST=${WORKDIR}/dst
INCLUDE=${WORKDIR}/include.lst
EXCLUDE=${WORKDIR}/exclude.lst
if [ -d ${WORKDIR} ]; then
  rm -fr ${WORKDIR}
fi
mkdir ${WORKDIR}
mkdir ${SRC}
mkdir ${SRC}/sub
mkdir ${SRC}/sub/docs
mkdir ${SRC}/docs
mkdir ${DST}
echo "a" > ${SRC}/a.txt
echo "b" > ${SRC}/sub/docs/b.txt
echo "c" > ${SRC}/docs/c.txt
echo "*/docs\n" > ${INCLUDE}
echo "docs\n" > ${EXCLUDE}

上のシェルスクリプトを実行したら、セットアップ完了です。

sh setup.sh

スクリプトが作成したファイルの存在と不存在を確認してみてください。

ls /var/tmp/rsync
ls /var/tmp/rsync/src
ls /var/tmp/rsync/src/docs
ls /var/tmp/rsync/src/sub/docs
ls /var/tmp/rsync/dst

rsyncを呼ぶシェルスクリプトを用意する

rsyncは定期的に呼ぶことが多いですし、ターミナルに直接打ち込むにはオプションが長くなりがちなので、シェルスクリプトを書いておきましょう。

#!/bin/sh
if [ "$1" != "--dry-run" ] && [ "$1" != "-n" ] && [ -n "$1" ]; then
  echo "Error: the argument must be empty, --dry-run, or -n." > /dev/stderr
  exit 1
fi
SRC=/var/tmp/rsync/src
DST=/var/tmp/rsync/dst
LOG=/var/tmp/rsync/rsync.log
INCLUDE=/var/tmp/rsync/include.lst
EXCLUDE=/var/tmp/rsync/exclude.lst
SYNTAX="rsync $1 -av --delete --include-from=${INCLUDE} --exclude-from=${EXCLUDE} --log-file=${LOG} ${SRC}/ ${DST}"
echo "CMD: $SYNTAX\n"  > /dev/stderr
$SYNTAX

-a は --recursive --links --perms --times --group --owner --devices と同等のオプションで、-v は監視モードで途中経過が出ます。

オプション 意味
--recursive 再帰的実行、つまりサブディレクトリー以下のファイルも対象にするオプションです。
--links シンボリックリンクを、リンク先のファイルではなくリンク自体としてコピーします。
--perms ファイルのパーミッションを保持します。
--group ファイルのグループIDを保持します。
--times 更新日時を保持します。
--owner ファイルのユーザーIDを保持します。
--devices バイスファイルを保持します。

--ownerと--devicesはOS管理ユーザーでないと意味がないわけですが、他は無いと予想外の挙動になると思います。

オプション 意味
--delete 転送元に無いファイルは、転送先で削除する
--include= 転送対象にするファイルパスのパターンを指定する
--include-from= 転送対象にするファイルパスのパターンを書いたファイルを指定する
--exclude= 転送対象外にするファイルパスのパターンを指定する
--exclude-from= 転送対象外にするファイルパスのパターンを書いたファイルを指定する
--log-file= ログファイルを指定する

以上のオプションも実用上、つけることが多くなると思います。

--include-form(もしくは--include)と--exclude-from(--exclude)が無ければすべてのファイルとディレクトリーが転送対象で、それで間に合うことも多いと思いますが、一時ファイルなどを除外したい場合もそこそこあります。Linux Desktopの$HOMEを外付けドライブにバックアップを取るときなどは、.local/share/Trash/や.cache/などは無駄なので除外したいはずです。

--include-formと--exclude-fromを書く順番には気をつけてください。この順番に書いておくと、include指定したものは必ず含まれます。--includeと--excludeは複数並べられますが、ファイルにまとめた方が間違いが少ないと思います。

docsを除外しsub/docsを適用するコツ

logやdocsなどのあちこちにありそうなディレクトリー名の指定は注意する必要があります。

今回、--include-form で指定するファイル include.lst には、

*/docs

--exclude-from で指定する exclude.lst には、

docs

と書きました。docsをexcludeして何もincludeしないと、転送元のトップ下だけではなく、そのサブディレクトリーにあるdocsも、転送対象外になるからです。

rsyncを試してみる

DRYRUN

ドライランから実行してみましょう。

sh rsync.sh --dry-run
CMD: rsync --dry-run -av --delete --include-from=/var/tmp/rsync/include.lst --exclude-from=/var/tmp/rsync/exclude.lst --log-file=/var/tmp/rsync/rsync.log /var/tmp/rsync/src/ /var/tmp/rsync/dst

sending incremental file list
a.txt
sub/
sub/docs/
sub/docs/b.txt

sent 170 bytes  received 30 bytes  400.00 bytes/sec
total size is 4  speedup is 0.02 (DRY RUN)

2ファイルと2ディレクトリが転送され、docs/c.txt が転送されないことが分かります。

なお、ドライランの短縮オプションは -n です。

最初の転送

実行してみましょう。

sh rsync.sh
CMD: rsync -av --delete --include-from=/var/tmp/rsync/include.lst --exclude-from=/var/tmp/rsync/exclude.lst --log-file=/var/tmp/rsync/rsync.log /var/tmp/rsync/src/ /var/tmp/rsync/dst

sending incremental file list
a.txt
sub/
sub/docs/
sub/docs/b.txt

sent 262 bytes  received 70 bytes  664.00 bytes/sec
total size is 4  speedup is 0.01

これでドライランの表示と同様に転送されました。

ファイルを更新して転送

作業前のファイルの中身を確認します。

cat /var/tmp/rsync/dst/a.txt
a

更新してrsyncを行いましょう。

echo "updated" > /var/tmp/rsync/src/a.txt
sh rsync.sh
CMD: rsync  -av --delete --include-from=/var/tmp/rsync/include.lst --exclude-from=/var/tmp/rsync/exclude.lst --log-file=/var/tmp/rsync/rsync.log /var/tmp/rsync/src/ /var/tmp/rsync/dst

sending incremental file list
a.txt

sent 219 bytes  received 37 bytes  512.00 bytes/sec
total size is 12  speedup is 0.05

更新した a.txt だけが転送されました。

cat /var/tmp/rsync/dst/a.txt
updated

転送先ファイルが更新されたものになっています。

ファイルを追加して転送

ほぼ同じ作業ですが、ファイルを追加すれば、追加ファイルだけが転送されます。

echo "d" > /var/tmp/rsync/src/d.txt
echo "e" > /var/tmp/rsync/src/docs/e.txt
sh rsync.sh
CMD: rsync  -av --delete --include-from=/var/tmp/rsync/include.lst --exclude-from=/var/tmp/rsync/exclude.lst --log-file=/var/tmp/rsync/rsync.log /var/tmp/rsync/src/ /var/tmp/rsync/dst

sending incremental file list
./
d.txt

sent 242 bytes  received 40 bytes  564.00 bytes/sec
total size is 14  speedup is 0.05

e.txtは除外ディレクトリーdocs以下にあるので、転送されていません。

ファイルを削除して転送

転送元の sub/docs/b.txt を削除して転送してみましょう。

rm /var/tmp/rsync/src/sub/docs/b.txt
sh rsync.sh
CMD: rsync  -av --delete --include-from=/var/tmp/rsync/include.lst --exclude-from=/var/tmp/rsync/exclude.lst --log-file=/var/tmp/rsync/rsync.log /var/tmp/rsync/src/ /var/tmp/rsync/dst

sending incremental file list
deleting sub/docs/b.txt
sub/docs/

sent 171 bytes  received 35 bytes  412.00 bytes/sec
total size is 12  speedup is 0.06

続けてディレクトリーsubも削除し、転送します。

rm -fr /var/tmp/rsync/src/sub
sh rsync.sh
CMD: rsync  -av --delete --include-from=/var/tmp/rsync/include.lst --exclude-from=/var/tmp/rsync/exclude.lst --log-file=/var/tmp/rsync/rsync.log /var/tmp/rsync/src/ /var/tmp/rsync/dst

sending incremental file list
deleting sub/docs/
deleting sub/
./

sent 104 bytes  received 36 bytes  280.00 bytes/sec
total size is 12  speedup is 0.09

追加・更新・削除の挙動を確認しました。

sshを通じた転送

ssh接続元の /var/tmp/rsync/src から、ユーザー$userとしてssh接続先$hostname(e.g. hist.example.com, 192.168.22.45)の /var/tmp/rsync/src にファイルなどを転送する場合は、

rsync -av --delete --include-from=/var/tmp/rsync/include.lst --exclude-from=/var/tmp/rsync/exclude.lst --log-file=/var/tmp/rsync/rsync.log -e "ssh -i ${path_to_the_secret_key}" /var/tmp/rsync/src/ ${user}@${hostname}:/var/tmp/rsync/dst

と言う風になります。なお、scpと微妙に異なり、//varでなくて/varで済みます。

-e "ssh -i ${path_to_the_secret_key}"

秘密鍵を指定してsshを呼び出すと言うオプションで、デフォルトの秘密鍵でよい場合は省略できます。

CRONなどで動かす場合に秘密鍵パスフレーズを無くしておきたい場合は、

ssh-keygen -p -f ${path_to_the_secret_key}

パスフレーズ再設定を行い、空欄とします。セキュリティーが緩くなるので、運用には気をつけましょう。

${user}@${hostname}

は、~/.ssh/configに、例えば

Host host_rsync
HostName host.example.com
User user
Port 22

と言うように設定されていれば、host_rsyncと書くだけで済みます。

rsync -av --delete --include-from=/var/tmp/rsync/include.lst --exclude-from=/var/tmp/rsync/exclude.lst --log-file=/var/tmp/rsync/rsync.log /var/tmp/rsync/src/ host_rsync:/var/tmp/rsync/dst

まとめと注意点

rsyncには山のようにオプションがあるので、もっと複雑な状況にも対処できますが、これぐらい把握しておくと使い始めやすいと思います。

転送元と転送先の指定を誤ってファイルを全部消したぐらいのトラブルしか経験はないのですが、

  • 転送対象外のファイル、ディレクトリーは同期で削除されません。
  • (最近のOSでは大丈夫でしょうが)sshで転送する場合は、ホストとクライアントの両方にrsyncがインストールされている必要があります。
  • 更新日時での同期が上手く機能しかないか、どうも転送先で壊れている気がするときは、--checksum オプションを使いましょう。
  • 巨大ファイルがディスクを圧迫するときは、一時ファイルを使わない --inplace オプションが有用かも知れません。