tarとsshで複数ファイルやディレクトリを送る/取ってくる

「サーバーにディレクトリ丸っと送りたいんだけど、途中に踏み台サーバーが居て…」よくあります。
「え、ProxyCommand 塞がれてるの??」とセキュリティ堅い環境でよく聞きます。

tar+ssh で大抵回避できるのでコピペで使えるようにまとめました。

tl;dr

ssh -l $STEP_USER $STEP ssh -l $REMOTE_USER $REMOTE tar c $FILES | ssh -l $STEP_USER $STEP ssh -l $REMOTE_USER $REMOTE tar x が分かれば状況に応じて削るだけ。
quote は回避しつつ避けられない場合はがんばりましょう。

direct

to remote:

tar c *.txt | ssh remote.example.com tar x

from remote:

ssh remote.example.com tar c '*.txt' | tar x

over the step

to remote:

tar c *.txt | ssh step.example.com ssh remote.example.com tar x

from remote:

ssh step.example.com ssh remote.example.com tar c '*.txt' | tar x

Prepare

送ったり取ってくるためのサンプルファイルを作ります。
ファイルはなんでもいいのですが「ファイルサイズが小さくて内容の確認が容易で重複しない」要件を満たすために UUID を書き込んだテキストファイルを 2 つ作りました。

❯ uuidgen | tee -a uuid.1.txt
16293D4F-419C-4925-8C88-6C0136F69F14

❯ uuidgen | tee -a uuid.2.txt
17B42188-9445-4DB6-B22F-BA14DC4B5D8D

念のため中身を確認しておきます。
find コマンドは SSH 越しに実行するとオプションのクオートやエスケープに手間がかかり良いサンプルなので、今回は次のように確認しました。
あえて find を使う必要はありませんので状況に応じた適した方法で確認してください。

❯ find . -type f -name 'uuid.*.txt' -print -exec cat {} \;
./uuid.2.txt
17B42188-9445-4DB6-B22F-BA14DC4B5D8D
./uuid.1.txt
16293D4F-419C-4925-8C88-6C0136F69F14

Servers

今回はこの uuid.1.txtuuid.2.txt を次の 3 つのホストの間でやりとりします。

  • (localhost)
    • 特に表記しませんが手元で直接 Terminal を開いている macOS や Linux です
  • step.example.com
    • 制約やルールにより経由する必要がある踏み台サーバーです
    • これが登場すると急にややこしくなりますね
  • remote.example.com
    • 本来ファイルをやりとりしたい Linux などが動いているサーバーです

それぞれ読み替えてください。
tar を使うのでディレクトリをやりとりする場合でもだいたい同じです。

to remote

まずは基本、SSH 越しに送ります。

❯ tar c uuid.*.txt | ssh remote.example.com tar x

処理の流れは次の通りです。

  1. ローカルで tar c を実行して作った tar アーカイブをパイプで標準出力に渡します
  2. ssh $REMOTE で SSH ログインし、
  3. リモートで tar x が実行され tar アーカイブが標準入力から読まれて展開されます

もし step.example.com に SSH ログインするユーザー user を指定する場合は、
user@host の形式で次のように指定するか、

❯ tar c uuid.*.txt | ssh user@remote.example.com tar x

または -l user オプションを使って

❯ tar c uuid.*.txt | ssh -l user remote.example.com tar x

のように指定します。
これは ssh コマンドのオプションなので以降すべて共通です。

送ったファイルを確認してみます。
* がローカルで展開されないようにクオートで囲む必要があります。

❯ ssh remote.example.com ls -1 'uuid.*.txt'
uuid.1.txt
uuid.2.txt

ファイルの中身を確認してみましょう。
あえて find を使っていますが、 *, {, }, \ などは見るからにややこしそうですね。

❯ tar c uuid.*.txt | ssh remote.example.com "find . -type f -name 'uuid.*.txt' -print -exec cat {} \;"
./uuid.1.txt
16293D4F-419C-4925-8C88-6C0136F69F14
./uuid.2.txt
17B42188-9445-4DB6-B22F-BA14DC4B5D8D

""find コマンドを囲み、 find に渡すファイルパターン uuid.*.txt'' で囲むことで SSH 越しに実行できます。

ファイルが少ないときは 1 つずつ指定した方がきっと楽です。

❯ ssh remote.example.com cat uuid.1.txt
16293D4F-419C-4925-8C88-6C0136F69F14

❯ ssh remote.example.com cat uuid.2.txt
17B42188-9445-4DB6-B22F-BA14DC4B5D8D

from remote

もう 1 つ基本パターンとして SSH 越しに取ってくる場合です。

❯ ssh remote.example.com tar c 'uuid.*.txt' | tar x

ファイルを送る場合と逆に以下を行なっています。

  1. ssh $REMOTE で SSH ログインし、
  2. リモートで tar c を実行して作った tar アーカイブをパイプで標準出力に渡し、
  3. 標準入力から tar アーカイブを読んで tar x でローカルに展開する

ファイルパターン uuid.*.txt がローカルで展開されないように、先ほど SSH 越しに lsfind を使った時と同じくクオートが必要です。
また今回の例であれば次のように *\ でエスケープしても実行できます。

❯ ssh step.example.com tar c uuid.\*.txt | tar x

to remote over the step

いよいよ踏み台サーバー登場ですが、次のように要は「踏み台サーバーでもう 1 回 ssh する」だけです。

❯ tar c uuid.*.txt | ssh step.example.com ssh remote.example.com tar x

SSH 越しに ls などを実行するときも考え方は一緒です。

❯ ssh step.example.com ssh remote.example.com ls -1 'uuid.*.txt'
uuid.1.txt
uuid.2.txt

find のようなオプションに記号が豊富なコマンドはちょっとややこしくなります。

❯ ssh step.example.com "ssh remote.example.com \"find . -type f -name 'uuid.*.txt' -print -exec cat {} \;\""
./uuid.1.txt
16293D4F-419C-4925-8C88-6C0136F69F14
./uuid.2.txt
17B42188-9445-4DB6-B22F-BA14DC4B5D8D

考え方は次の通りです。

  1. uuid.*.txt が展開されずに find コマンドに渡るよう ' で囲む
  • 'uuid.*.txt' 部分
  1. find コマンドのオプション \ などが remote.example.com に渡るよう " で囲む
  • "find . -type f -name 'uuid.*.txt' -print -exec cat {} \;" 部分
  1. (2) がローカルで展開されないよう SSH 接続部分自体を " で囲む
  • "ssh remote.example.com から最後まで
  1. " が入れ子になるので (2) 部分の "\" とエスケープする
  • "find . -type f -name 'uuid.*.txt' -print -exec cat {} \;" -> \"find . -type f -name 'uuid.*.txt' -print -exec cat {} \;\"

ややこしいですね。
ファイルを 1 つずつ指定できる場合はその方が明らかに楽です。

❯ ssh step.example.com ssh remote.example.com cat uuid.1.txt
16293D4F-419C-4925-8C88-6C0136F69F14

❯ ssh step.example.com ssh remote.example.com cat uuid.2.txt
17B42188-9445-4DB6-B22F-BA14DC4B5D8D

from remote over the step

SSH 越しに踏み台サーバーを経由して取ってくる場合は次の通りです。

❯ ssh step.example.com ssh remote.example.com tar c 'uuid.*.txt' | tar x

remote to remote over the step

ここまでできれば「踏み台サーバー A 越しにサーバー B のファイルを取ってきて、そのままパイプを使って踏み台サーバー C 越しにサーバー D に送る」こともできます。

❯ ssh step.example.com ssh remote.example.com tar c 'uuid.*.txt' | ssh step-2.example.com ssh remote-2.example.com tar xv

appendix: tar options

man tar でマニュアルが読めますが今回使ったオプションだけ紹介します。

  • tar c
    • tar -c, tar --create でも効果は同じです
    • tar czz オプションを指定して圧縮することもできます
  • tar x
    • tar -x, tar --extract でも効果は同じです
    • z オプションを作るときに z オプションを指定して圧縮した場合は tar xz のように展開時にも z オプションを指定できます
      • が、最近の tar は展開時には z オプションを指定しなくても勝手に伸張して展開してくれます
    • tar xvv オプションを指定すると展開されるファイル名が表示されるので安心できます