SSHでscpを使わずにファイルをコピーする


2011年 04月 04日

え、SSH通るんならscp使えばいいじゃん。

YES、その通り。

しかし、「大容量ファイルを転送中に接続が切れてしまった」なんてときに、コピーが完了した後から残りを引き継ぎたい、みたいな要求にはscpコマンドは答えてくれない。もちろん、sftp使えばレジューム機能もあるんだしそうすればいいじゃんというのは正論だが、sftpの方はsshが通るからといって必ずしも使えるとは限らない。

そんなときは、多少強引だとしても、一歩戻って解決を試みるのもひとつの手だ。

単純転送

単純な転送から試してみよう。まず、リモート先のhostAにあるsrcfileを、手元にdestfileとしてコピーしたいとする。

sshはリモートで実行するコマンドを受け付ける(実際のところ、scpも内部的にはsshのリモートコマンドで実装されていたはずだ)。ということは、リモートでcatコマンドを実行してファイルの中身を「表示」させ、それをローカルのファイルにリダイレクトしてしまえば、ファイルのコピーは可能だ。

% ssh hostA cat srcfile > destfile

単純だね。

では逆に、手元のsrcfileをリモート先のdestfileとしてコピーしたいとしよう。手元から標準入力にファイルデータを流し込めば、リモートコマンドでは標準入力から受け取ったデータをファイルに書き出せばいいことになる。同じくcatコマンドでもできるが、

% ssh hostA 'cat > destfile' < srcfile

リダイレクト記号がローカルではなくリモートで解釈されるようにするため、quoteが必要だ。

標準入出力を繋いでくれるツールとして確かにcatは便利だし分かりやすいが、そう言われるとそういうツールが古くから用意されていることに気付く。ddだ。読み書き両方に対応しているので、これを使えばどっち向きでもquoteは不要だ。

% ssh hostA dd if=srcfile > destfile
% ssh hostA dd of=destfile < srcfile

catのときと考え方は同じである。

レジューム転送

さて、準備はできた。シナリオはこうだ。

「巨大ファイルをscpで転送してたけど、途中で接続が切れちゃった! コピーし直したらまた3時間かかっちゃうよ、そんなに待てない!」

OK、まず、転送済のサイズを確認しよう。

% wc -c destfile
331710464 destfile

300MB程らしい。今コピーしようとしているファイルは1GB程あるので、あと700MB程度を転送したい。ddコマンドはブロック単位での読み書きを行うので、まず転送済みのブロック数を数えよう。ブロックサイズは別に何でもいいが、とりあえず512バイトとする。

% echo $((331710464 / 512))
647872

あとは、転送済みのブロック分を「スキップ」して転送すれば良い。ロカールからリモートへのコピーならこうだ。

% dd bs=512 if=srcfile skip=647872 | ssh hostA dd bs=512 of=destfile seek=647872

リモートからローカルでも、単にsshコマンドの位置をずらせばいい。

% ssh hostA dd bs=512 if=srcfile skip=647872 | dd bs=512 of=destfile seek=647872

コピーが終わったら、念のため正しくコピーできたかどうか確認しておこう。

% sha256 srcfile
SHA256 (srcfile) = 44400547769fd8d3d4ab8eed09bfb1c0b8a4f4bc403bd2f848ec58963212cb37

% sha256 destfile
SHA256 (destfile) = 44400547769fd8d3d4ab8eed09bfb1c0b8a4f4bc403bd2f848ec58963212cb37

問題ないようだ。

最初に言った通り、そもそもこんなことをしなきゃならない状況に陥らないようにすべきなのは確かだけど、役に立つ時はあるかもしれない。

多段転送

オマケ。

ローカルホストとリモートホストとの間に「踏み台」が必要なことはままある。しかし、scpを使うため接続を1段扱いにしたいのに、ポート転送は禁止され、ProxyCommandもncコマンドがなくて使えず、ncコマンドのインストールもままらなない…。そんなときは、前述のやり方を多段にすることでどうにかファイルのコピーはできる。

% ssh hostA ssh hostB ssh hostC dd if=srcfile > destfile
% ssh hostA ssh hostB ssh hostC dd of=destfile < srcfile

ただし、2段目以降にパスワード入力が必要な場合にパスワード入力ができなくて困ることになるので、その点について何かしらの解決を図る必要がある。例えば、-A オプション(あるいは .ssh/config に ForwardAgent yes を設定)でAgent転送を行い、そもそも接続時のパスワード入力を省略させる、とか。

まあこっちの話にしても、ProxyCommand使えるようにするなりなんなり、そういう方向でどうにかできるならしたほうが良いのは違いないのだけど。