rsync で凝ったファイル名のパターンを指定する

デプロイするとき何を使うか。もちろん Capiなんとかとかいう高機能なデプロイツールを使うことができればいいのですが,使い方を習熟しなくちゃいけないですし,いろいろ環境を設定しなくちゃいけません。レポジトリ作ってなかったりもするので,お手軽に rsync でデプロイをすることも多いです。

それで困るのが,「このディレクトリは送りたいけどこのディレクトリは嫌なんだよなぁ」とかいう状況です。なので,rsync の様々なオプションと格闘してみました。
たとえば,

~/myapp/
         site-perl/
                    CGI/
                    Debug/
                    Minimalist/
                    MyApp/
                    Sandbox/
         doc/
              inside/
              pod/
         t/
         html/
               blog/
               memo/
               res/
               template/

みたいなディレクトリ構成がローカルにあったとして(ずいぶん恣意的な例ですが),太字のとこだけデプロイしたい,とします。

% cd ~/myapp
% rsync -avz site-perl/CGI/        remote:/var/www/app
% rsync -avz site-perl/Minimalist/ remote:/var/www/app
...

みたいに一つずつ sync します?でも毎回パスフレーズ聞かれるのめんどくさいですよね。agent 使えば楽になりますが。

じゃあ一度に指定するとして,

% cd ~/myapp
% rsync -azv site-perl/CGI/        \
             site-perl/Minimalist/ \
             doc/pod/ ...          remote:/var/www/app

こんな指定をするとうまくいかなかったりします(この例だと /var/www/app/CGI とかいうフォルダができます)。

あるいは --exclude や --include を使ってがんばります?


と,いろいろな例を出しましたが,こんな時はファイルフィルタルールを使うとちょっとすっきりします。
ルールのルールは

  • 先頭が「+」の行のパターンにマッチするものは許可(--include)
  • 先頭が「-」の行のパターンにマッチするものは拒否(--exclude)
  • 「?」はパスデリミタ以外の任意の1字
  • 「*」はパスデリミタ(「/」)を含まない1文字以上のもの
  • 「**」はパスデリミタも含む
  • 「[〜]」は正規表現のそれと似た感じ

こんな感じのものです(これ以外にもいろいろありますが)。

じゃあこれで上記の例を書き下すとどうなるか。

+ site-perl/CGI/**
+ site-perl/Minimalist/**
+ site-perl/MyApp/**
+ doc/*
+ doc/pod/**
+ html/res/**
+ html/template/**
- *

こんなルールでいけるかなと思いがちですが,これではうまくいきません。なぜかというと,ディレクトリを再帰的に下っていきながらsync 対象のファイルを追加するからです。

といってもなんのことやらなので具体例をあげます。たとえば,1行目の「+ site-perl/CGI/**」で転送したいファイルについて考えます。まずカレントディレクトリに「site-perl」というディレクトリがあります。このパス名にマッチするパターンは,上記のルールでは唯一最終行の「- *」です。ですから,rsync は site-perl 以下のすべてのディレクトリを転送対象としないのです

では正解をば。

+ site-perl/
+ site-perl/CGI/
+ site-perl/CGI/**
+ site-perl/Minimalist/
+ site-perl/Minimalist/**
+ site-perl/MyApp/
+ site-perl/MyApp/**
+ doc/
+ doc/*
- doc/inside
+ doc/pod/
+ doc/pod/**
+ html/
+ html/res/
+ html/res/**
+ html/template/
+ html/template/**
- *

無駄に長くなってしまって涙が出ます。でも許容範囲かなぁ。

使い方ですが,上記の内容をホームディレクトリの deploy.list という名前に保存したとして,

% (
    cd ~/myapp/ && \
    rsync -azv --filter=". ~/deploy.list" . remote:/var/www/app
  )

このような形で書いてあげます。「--filter」オプションはファイル名を指定するのではなく,フィルタルール自身を指定するというところがちょっとネックです(--filter オプション先頭のピリオドが「以下のファイルを読み込んでね」という意味になる)。


2010-10-28 追記
今見るといろいろアレなので追記。

実行例は

rsync -azv --filter='._~/deploy.list' ~/myapp/ remote:/var/www/app/

のほうがいいかも。

  1. 上述したような FILTER RULE の場合,SOURCE は trailing slash をつけるべき
  2. その場合,対称性から DESTINATION にも trailing slash をつけるべき
  3. FILTER RULE は「RULE 空白 パターン」の代わりに「RULE _ パターン」のようにも記述できる。このほうがシェルのコマンドラインシェルスクリプトなどで書く場合にトラブルがおきにくい。

2010-10-28 追記おわり


マニュアルを読むともっと詳しいことも書いてありますが,このフィルタルールまわりは rsync の暗黒面だなと思いました。そんな暗黒面と仲良くするには「-n」オプション(実際にファイルの書き込みはしない)が重宝します。make の -n と同じ意味と思うと覚えやすいですし。あと,-v オプションを重ねていくとどんどん詳しくなります。テストの段階では -vv ぐらいがおすすめです。


こんな凝ったことするより perl で転送ファイルを列挙して --files-from で指定するほうがよっぽど楽な気がしました。