【シェルコマンド基礎講座】rsyncコマンドの使い方を細かく検証しながらinclude/excludeのコツを覚える


※ 当ページには【広告/PR】を含む場合があります。
2023/09/19
rsyncコマンドで『some files/attrs were not transferred』でたまにファイルがコピーされないエラーに対処する
『rsync』コマンド は2つのストレージ間のファイルをスマートに同期してくれる非常に利便性の高いコマンドの一つです。
この記事ではrsyncコマンドを使いこなす上で重要な以下のポイントを中心に、具体的な利用例をもって説明していこうと思います。

            + rsync独自の簡易正規表現を良く理解しよう
+ 基本的にはexcludeでフィルタリングしていこう
+ フィルタを付ける順序には気をつけよう

        

合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】シェルスクリプトをこれから学びたい人のためのオススメ書籍&教材特集

rsyncコマンドを使いこなすメリットとは?

「rsync」 コマンドは、パソコン内に存在するフォルダ同士の同期にとどまらず、ネットワーク越しに別のストレージに存在するフォルダ・ファイルでも使うことのできる非常に強力なツールです。
個人的なユースケースをあげると、

            1. SSH経由でネットワーク上にある別のローカルマシーン上のリソースを同期
2. AWS EC2などのクラウド上のLinuxインスタンスにあるリソースを同期
3. Docker等でホストOSとコンテナ間のリソースを同期

        

などなど、色々応用が考えられます。
Linuxの標準コマンドではありませんが、オンラインで何かしらのプロジェクトを開発する際には不可欠とも言えます。
仮にrsyncを使わないとなると、2つのフォルダ間で同期するスクリプトを自作するのは大変ですが、おおよそ
find コマンド・ grep コマンド・ read コマンドを素朴に組み合わせて、下のようなテクニックを骨格として組み上げることになるでしょう。

            SRC_DIR=./src
DST_DIR=./dst

find ${SRC_DIR} -maxdepth 1 | grep -E "<...特定のファイル名でフィルタ>" | while read -r fname; do
    cp "$fname" "${DST_DIR}/"
done

        

これだとコピーさせたいファイルを見つけて、単純にファイルを保存先にコピーするだけですので、rsyncコマンドと比べるべくもなく、おおよそ"同期"とは呼べない代物ものですが、ここから頑張って果てしないカスタマイズしていけば...いつかはrsyncに辿り着けるかもしれません。
そんなスクリプトの実装に苦労をするよりも、「rsync」コマンドの使い方をしっかり覚えておけば、さまざまな開発シーンで役に立つので、ここで頑張って勉強しておきましょう。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】シェルスクリプトをこれから学びたい人のためのオススメ書籍&教材特集

rsyncコマンドを具体例から小出しに考えてみる



Debian系・Ubuntu系などaptが使える場合には、以下のコマンドで一発導入することができます。

            $ sudo apt install rsync
$ rsync --version
rsync  version 3.2.3  protocol version 31
Copyright (C) 1996-2020 by Andrew Tridgell, Wayne Davison, and others.
Web site: https://rsync.samba.org/
#...

        

他のLinuxディストリビューションの殆どのパッケージマネージャからでも同様の操作で導入することができると思います。

rsyncコマンドの基本的な使い方



同期元のフォルダを
src 、同期先のフォルダを dst とすると、 src にあるファイルのすべてを丸々同期させたい場合、

            $ rsync -av src/ dst/
#もしくは
$ rsync -avP src/ dst/

        

コマンドオプションに付いている
-av/-avP は一種のイディオムのようなもので rm -rf みたいなものです。
オプションに関して気になることがあれば、
「rsync --help」 を確認してください。
ここで実際に例をみながらいくつか実験してみましょう。
とりあえず実験用に同期元の
src フォルダに、以下ようないくつかのファイルと下位のフォルダ( fugamoga )を準備しておきます。

            $ tree -a src
src
├── .hoge
├── a.hoge.txt
├── b-hoge.txt
├── c_hoge.txt
├── fuga
│   ├── a.fuga.txt
│   ├── b-fuga.txt
│   ├── c_fuga.txt
│   ├── fuga.log
│   ├── fuga.txt
│   ├── fugafuga.txt
│   └── piyo.txt
├── hoge.log
├── hoge.txt
├── hogehoge.txt
├── moga
│   ├── a.moga.txt
│   ├── b-moga.txt
│   ├── c_moga.txt
│   ├── moga.log
│   ├── moga.txt
│   ├── mogamoga.txt
│   └── piyo.txt
└── piyo.txt

        

ここで、ファイル・フォルダのフィルタリングは一旦考えず、
srcdst へ同期させてみます。

            $ rsync -av src/ dst/
$ tree -a dst
dst
├── .hoge
├── a.hoge.txt
├── b-hoge.txt
├── c_hoge.txt
├── fuga
│   ├── a.fuga.txt
│   ├── b-fuga.txt
│   ├── c_fuga.txt
│   ├── fuga.log
│   ├── fuga.txt
│   ├── fugafuga.txt
│   └── piyo.txt
├── hoge.log
├── hoge.txt
├── hogehoge.txt
├── moga
│   ├── a.moga.txt
│   ├── b-moga.txt
│   ├── c_moga.txt
│   ├── moga.log
│   ├── moga.txt
│   ├── mogamoga.txt
│   └── piyo.txt
└── piyo.txt

        

当然ながら、丸ごとリソースがもれなくコピーされています。

include/excludeオプションで同期対象をフィルタリングする



rsyncコマンドの真骨頂はなんといっても
「include/excludeオプション」 をつけて、同期させる・させないを細かく制御できることに尽きます。
rsyncコマンドの
include/exculde の考え方を征することで、より効果的にファイルの同期が行うことができるのですが、慣れないうちはincludeとexcludeの使い方がなんとも掴みどころがなく感じてしまいます。
まずincludeから理解してみましょう。
さきほど最初に紹介していた一見なんのフィルタも持たないコマンド例ですが、実際には
すべてのフォルダ・ファイルを含む というincludeフィルタが潜んでいます。

            $ rsync -av src/ dst/
#👇明示な書き換え
$ rsync -av --include="*" src/ dst/

        

理解しておきたいのが、
「*」(アスタリスク文字) で、詳しくは後述しますが、これは すべてのファイル名にマッチ することを意味します。
なので、
--include="*" を解釈すると、「すべてのファイル名にマッチ」したものは同期に含める、ということになります。
これを理解していると、初心者でやりがちなたとえばここでの例でいうと「fuga」フォルダの中身だけ同期させたい、と意図して以下のようにやってしまっても、

            $ rsync -av --include="fuga/" src/ dst/
$ tree -a dst
dst
├── .hoge
├── a.hoge.txt
├── b-hoge.txt
├── c_hoge.txt
├── fuga
│   ├── a.fuga.txt
│   ├── b-fuga.txt
│   ├── c_fuga.txt
│   ├── fuga.log
│   ├── fuga.txt
│   ├── fugafuga.txt
│   └── piyo.txt
├── hoge.log
├── hoge.txt
├── hogehoge.txt
├── moga
│   ├── a.moga.txt
│   ├── b-moga.txt
│   ├── c_moga.txt
│   ├── moga.log
│   ├── moga.txt
│   ├── mogamoga.txt
│   └── piyo.txt
└── piyo.txt

        

結果は
fuga フォルダの中身だけでなく、 src フォルダすべてがやはり同期されてしまいます。
これは、よくよく考えれば当たり前で、

            $ rsync -av --include="fuga/" src/ dst/
#👇明示に書き換え
$ rsync -av --include="*" --include="fuga/" src/ dst/

        

であり、もともとすべてファイルを含める(
「"*"」 )ので、そこに fuga フォルダも含めると指定しても結果は全く同じになります。

rsyncのフィルタの基本は除外(exclude)していく



では同期させたいフォルダ・ファイルをどのようにフィルタリングするとうまくいくのかを考えてみましょう。
ここで出てくるのが、もう一つの
exclude オプションです。
このオプションで指定したファイルやフォルダは同期から除外し、含めなくすることができます。
たとえば、特定のファイルをexclude指定したとしましょう。

            $ rsync -av --exclude="piyo.txt" src/ dst/
$ tree -a dst
dst
├── .hoge
├── a.hoge.txt
├── b-hoge.txt
├── c_hoge.txt
├── fuga
│   ├── a.fuga.txt
│   ├── b-fuga.txt
│   ├── c_fuga.txt
│   ├── fuga.log
│   ├── fuga.txt
│   └── fugafuga.txt
├── hoge.log
├── hoge.txt
├── hogehoge.txt
└── moga
    ├── a.moga.txt
    ├── b-moga.txt
    ├── c_moga.txt
    ├── moga.log
    ├── moga.txt
    └── mogamoga.txt

        

ここでは、
piyo.txt のファイル名にマッチしたものがすべて同期から除外されています。
注目すべきはルートディレクトリだけでなく、
fugamoge のすべての階層の piyo.txt がもれなく除外されています。
同じ結果になるという意味では、

            $ rsync -av --exclude="piyo.txt" src/ dst/
#👇これでも同じ結果になる
$ rsync -av --exclude="*piyo.txt" src/ dst/

        

ともできます。

「*piyo.txt」 は、 ルート / fuga / moga に存在する piyo.txt ファイルにマッチしているからと解釈できます。
rsyncコマンドでは、独自の
「簡略式正規表現」 が幾つかあり、 「*」 はその代表格になります。
この「簡略式正規表現」に関して、次の節から詳しく説明していきましょう。

ファイル名に「*」を付ける・付けないの違い



さきほどはさらりと説明を流しましたが、rsyncのフィルタ規則で最も目にする
「*」 の働きを良くみていきます。
まずは、
「*」 なしで特定のexcludeした場合、

            $ rsync -av --exclude="hoge.txt" src/ dst/
$tree -a dst
dst
├── .hoge
├── a.hoge.txt
├── b-hoge.txt
├── c_hoge.txt
├── fuga
│   ├── a.fuga.txt
│   ├── b-fuga.txt
│   ├── c_fuga.txt
│   ├── fuga.log
│   ├── fuga.txt
│   ├── fugafuga.txt
│   └── piyo.txt
├── hoge.log
├── hogehoge.txt
├── moga
│   ├── a.moga.txt
│   ├── b-moga.txt
│   ├── c_moga.txt
│   ├── moga.log
│   ├── moga.txt
│   ├── mogamoga.txt
│   └── piyo.txt
└── piyo.txt

        

となって、
src フォルダ中の全ての hoge.txt が同期対象から除外されました。
次にこのフィルタを、
「*hoge.txt」 に変えてみます。

            $ rsync -av --exclude="*hoge.txt" src/ dst/
$ tree -a dst
dst
├── .hoge
├── fuga
│   ├── a.fuga.txt
│   ├── b-fuga.txt
│   ├── c_fuga.txt
│   ├── fuga.log
│   ├── fuga.txt
│   ├── fugafuga.txt
│   └── piyo.txt
├── hoge.log
├── moga
│   ├── a.moga.txt
│   ├── b-moga.txt
│   ├── c_moga.txt
│   ├── moga.log
│   ├── moga.txt
│   ├── mogamoga.txt
│   └── piyo.txt
└── piyo.txt

        

すると、今度は同期結果が異なって、
hoge.txt だけでなく、以下のファイルも除外されました。

            hoge.txt
a.hoge.txt
b-hoge.txt
c_hoge.txt
hogehoge.txt

        

ということで、rsyncでのフィルタ中のマッチ文字
「*」 は、簡易的な正規表現になっていて、その意味は 「/(スラッシュ)文字以外の1文字以上」 にマッチしています。
これは一般の正規表現で表現した場合、
『*』-->『[^/]+』 とみなすことができます。
つまりは正規表現で書き換えると、
『*hoge.txt』-->『[^/]+hoge\.txt$』 というファイル名のものを全てマッチするという意味になっています。

rsyncで使えるフィルタの簡易正規表現 〜 『*』・『**』・『?』



先ほど
『*』 文字を使ったファイルパスの正規表現的マッチングルールについて説明しましたが、rsyncには他にもいくつか利用可能な正規表現規約があります。
`『
』`は、 「任意の1文字以上」 の文字にマッチという規約になり、等価な正規表現では`『 「任意の1文字以上」 『*』 との違いは、「/」文字をマッチに含めるか含めないかの違いだけですので、先ほどの例を hoge.txt と変えてあげても結果に違いはありません。 使い所としてはフォルダ構造が深くネストしてくると、 』`がより効果的に使えて、特定のフォルダの中にある特定のファイル名をマッチさせたい時、

            fuga/a/hoge.txt
fuga/b/c.hoge.txt
fuga/d/e/f.hoge.txt
fuga/d/g/h.i.hoge.txt
...

        

などのように、下位のフォルダ構造のファイルを根こそぎマッチさせることが出来る利点があります。
また
『?』 文字もrsyncでは特殊文字として認識されます。
意味合いで言うと、
「/(スラッシュ)文字以外の1文字」 にマッチし、等価な正規表現では 『?』-->『[^/]』 と解釈されます。
あまり使いどころはないかもしれませんが、例えば、

            x.hoge.txt
x-hoge.txt
x_hoge.txt
xxhoge.txt
...

        

と言うファイル名にマッチさせることができます。

注意が必要なrsyncのフィルタで「/(スラッシュ)」のルール



rsyncコマンドのフィルタ表現において「/(スラッシュ)」文字を扱う上で、覚えておかないと困惑するであろうルールがあるので、ここで少し説明しておきます。
まず、
「/」から始めるフィルタ がある場合、 ファイルパスの先頭 から厳密マッチさせる、と言う意味になります。
これは一般の正規表現でいう、文字列の先頭を示す
『^』 の意味になっています。
例えば、

            hoge.txt(ルートフォルダ内のみ)

        

つまり、等価な正規表現で言うと
『/hoge.txt』-->『^hoge\.txt$』 と解釈されていることに注意です。
ファイルパスの先頭に厳密マッチするので、たとえば下位のフォルダに存在する
hoge.txt では、 fuga/hoge.txt のようになってしまうのでマッチしなくなります。
また、
末尾が「/」で終わるフィルタ の表現も特別な意味合いがあり、これはマッチさせたい対象が ファイルではなくフォルダ であると解釈されます。

            moga(フォルダ)
a/moga(フォルダ)
a/b/moga(フォルダ)
...

        

この例では、
「moga」と言うフォルダ を全てマッチする一方、 「moga」と言うファイル にはマッチしません。

特定のフォルダを除外する



ここからまたいくつかの使用例の紹介に戻りましょう。
先ほどの内容でも紹介したように、特定のフォルダを除外したい場合は、以下のように書けます。

            $ rsync -av --exclude="fuga/" src/ dst/
$ tree -a dst
dst
├── .hoge
├── a.hoge.txt
├── b-hoge.txt
├── c_hoge.txt
├── hoge.log
├── hoge.txt
├── hogehoge.txt
├── moga
│   ├── a.moga.txt
│   ├── b-moga.txt
│   ├── c_moga.txt
│   ├── moga.log
│   ├── moga.txt
│   ├── mogamoga.txt
│   └── piyo.txt
└── piyo.txt

        

パターンに
<フォルダ名>/ のように 「/」(スラッシュ) が付けばフォルダとして認識されています。

特定の階層の特定のファイルだけ除外する



まずは、ピンポイントに除外したいファイルパスを指定するやり方をやってみます。

            $ rsync -av --exclude="fuga/piyo.txt" src/ dst/
$ tree -a dst
dst
├── .hoge
├── a.hoge.txt
├── b-hoge.txt
├── c_hoge.txt
├── fuga
│   ├── a.fuga.txt
│   ├── b-fuga.txt
│   ├── c_fuga.txt
│   ├── fuga.log
│   ├── fuga.txt
│   └── fugafuga.txt
├── hoge.log
├── hoge.txt
├── hogehoge.txt
├── moga
│   ├── a.moga.txt
│   ├── b-moga.txt
│   ├── c_moga.txt
│   ├── moga.log
│   ├── moga.txt
│   ├── mogamoga.txt
│   └── piyo.txt
└── piyo.txt

        

狙ったファイルだけ弾かれています。
もしくは逆パターンで、指定したファイル以外は残すようなやり方も試してみましょう。


            $ rsync -av --include="fuga/piyo.txt" --exclude="fuga/*" src/ dst/
#👇でも同じ
#rsync -av --include="piyo.txt" --exclude="fuga/*" src/ dst/
#rsync -av --include="*piyo.txt" --exclude="fuga/*" src/ dst/

$ tree -a dst
dst
├── .hoge
├── a.hoge.txt
├── b-hoge.txt
├── c_hoge.txt
├── fuga
│   └── piyo.txt
├── hoge.log
├── hoge.txt
├── hogehoge.txt
├── moga
│   ├── a.moga.txt
│   ├── b-moga.txt
│   ├── c_moga.txt
│   ├── moga.log
│   ├── moga.txt
│   ├── mogamoga.txt
│   └── piyo.txt
└── piyo.txt

        

ただし、
「*」 なしに --exclude="fuga/" としてしまうと、ファイルではなく、フォルダの除外とみなされて結果が異なることに注意しましょう。


            $ rsync -av --include="piyo.txt" --exclude="fuga/" src/ dst/
$ tree -a dst
dst
├── .hoge
├── a.hoge.txt
├── b-hoge.txt
├── c_hoge.txt
├── hoge.log
├── hoge.txt
├── hogehoge.txt
├── moga
│   ├── a.moga.txt
│   ├── b-moga.txt
│   ├── c_moga.txt
│   ├── moga.log
│   ├── moga.txt
│   ├── mogamoga.txt
│   └── piyo.txt
└── piyo.txt

        

同一ファイル名の指定があった場合には先に指定されたほうが優先



あまり意味が無い比較かもしれませんが、なんらかの手違いで同一のファイルでinclude/exclude指定が重複していたパターンを考えます。
まずは
exclude を先にして、 include を後に指定した場合です。

            $ rsync -av --exclude="piyo.txt" --include="piyo.txt" src/ dst/
$ tree -a dst/
dst/
├── .hoge
├── a.hoge.txt
├── b-hoge.txt
├── c_hoge.txt
├── fuga
│   ├── a.fuga.txt
│   ├── b-fuga.txt
│   ├── c_fuga.txt
│   ├── fuga.log
│   ├── fuga.txt
│   └── fugafuga.txt
├── hoge.log
├── hoge.txt
├── hogehoge.txt
└── moga
    ├── a.moga.txt
    ├── b-moga.txt
    ├── c_moga.txt
    ├── moga.log
    ├── moga.txt
    └── mogamoga.txt

        

こちらは除外が優先され、
piyo.txt が消えています。
これを逆にするとどうでしょう。

            $ rsync -av --include="piyo.txt" --exclude="piyo.txt" src/ dst/
$ tree -a dst/
dst/
├── .hoge
├── a.hoge.txt
├── b-hoge.txt
├── c_hoge.txt
├── fuga
│   ├── a.fuga.txt
│   ├── b-fuga.txt
│   ├── c_fuga.txt
│   ├── fuga.log
│   ├── fuga.txt
│   ├── fugafuga.txt
│   └── piyo.txt
├── hoge.log
├── hoge.txt
├── hogehoge.txt
├── moga
│   ├── a.moga.txt
│   ├── b-moga.txt
│   ├── c_moga.txt
│   ├── moga.log
│   ├── moga.txt
│   ├── mogamoga.txt
│   └── piyo.txt
└── piyo.txt

        

この場合には、includeが優先され、除外されない、という結果になります。
つまりファイルもしくはフォルダのマッチパターンには、
先に指定されたほうが優先され、それ以降のパターンが無視される ことがわかります。

ルート階層のファイルだけ含める・除外する



ルート階層のファイルは、そのファイルパスに
「<ファイル名>」(「/」文字を含まない) というパターンになる、ということで識別できます。
ルート階層以外の
piyo.txt を除外し、ルート階層の piyo.txt だけを含めたいなら、

            $ rsync -av --exclude="*/piyo.txt" src/ dst/
$ tree -a dst/
dst/
├── .hoge
├── a.hoge.txt
├── b-hoge.txt
├── c_hoge.txt
├── fuga
│   ├── a.fuga.txt
│   ├── b-fuga.txt
│   ├── c_fuga.txt
│   ├── fuga.log
│   ├── fuga.txt
│   └── fugafuga.txt
├── hoge.log
├── hoge.txt
├── hogehoge.txt
├── moga
│   ├── a.moga.txt
│   ├── b-moga.txt
│   ├── c_moga.txt
│   ├── moga.log
│   ├── moga.txt
│   └── mogamoga.txt
└── piyo.txt

        

とすれば良いでしょう。
この場合、
/fuga/piyo.txt/moga/piyo.txt にはマッチしても、 /piyo.txt にはマッチしなかった、と解釈できるでしょう。
逆に、ルート階層の
piyo.txt だけを除外したいなら、

            $ rsync -av --exclude="/piyo.txt" src/ dst/
$ tree -a dst/
dst/
├── .hoge
├── a.hoge.txt
├── b-hoge.txt
├── c_hoge.txt
├── fuga
│   ├── a.fuga.txt
│   ├── b-fuga.txt
│   ├── c_fuga.txt
│   ├── fuga.log
│   ├── fuga.txt
│   ├── fugafuga.txt
│   └── piyo.txt
├── hoge.log
├── hoge.txt
├── hogehoge.txt
└── moga
    ├── a.moga.txt
    ├── b-moga.txt
    ├── c_moga.txt
    ├── moga.log
    ├── moga.txt
    ├── mogamoga.txt
    └── piyo.txt

        

こちらは、ルート階層の
/piyo.txt にはマッチにだけマッチする、という結果になります。

複数のファイルパターンでマッチさせる



ここまででおそらくrsyncのinclude/excludeのおおよその使い方を理解してもらったのではなかろうかと思います。
では、最後にフィルタパターンをリレーして少々複雑なファイル同期をやってみましょう。

            $ rsync -av \
    --exclude=".*" \
    --exclude="hoge*.txt" \
    --exclude="*fuga.*" \
    --exclude="??g?.log" \
    --exclude="a.*" \
    --exclude="b-*.*" \
    --exclude="/piyo.txt" \
    --exclude="/**moga.txt" \
    src/ dst/

$ tree -a dst/
dst/
├── c_hoge.txt
├── fuga
│   └── piyo.txt
└── moga
    └── piyo.txt

        

どのパターンがどのファイルパスにマッチしているのかは、詳しい解説は省略しますが、上記までの内容を順番に追っていただくと、なんとなく読めると思います。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】シェルスクリプトをこれから学びたい人のためのオススメ書籍&教材特集

まとめ



以上、rsyncの利用法の実例からinclude/excludeのフィルタの考え方をじっくりと解説してきました。
rsyncコマンドを使いこなす上で、重要な項目を箇条書きでまとめると、

            + rsync独自の簡易正規表現を良く理解しよう
+ 基本的にはexcludeでフィルタリングしていこう
+ フィルタを付ける順序には気をつけよう

        

ということを中心に説明してみました。
では、良いLinuxライフを。 めでたしめでたし。

参考サイト

rsync の複雑怪奇な exclude と include の適用手順を理解しようrsyncのinclude, excludeについて
記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。

合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】シェルスクリプトをこれから学びたい人のためのオススメ書籍&教材特集