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' 部分
  2. find コマンドのオプション \ などが remote.example.com に渡るよう " で囲む
    • "find . -type f -name 'uuid.*.txt' -print -exec cat {} \;" 部分
  3. (2) がローカルで展開されないようSSH接続部分自体を " で囲む
    • "ssh remote.example.com から最後まで
  4. " が入れ子になるので (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 オプションを指定すると展開されるファイル名が表示されるので安心できます