screenを使ってファイルを転送する。


「いま、sshで見えてるこのファイルが手元にほしい」という時に、scpは手間がかかりすぎる。

  • 接続するホスト = sshで接続しているこのホスト
  • 欲しいファイル = いまlsで見えてるこのファイル

なのに、なぜscpにコマンドラインオプションを渡さなければならないのか。そもそも踏み台サーバを経由している場合など、単純にscpできないこともある。

目の前のssh接続を利用して、ファイルを手軽に転送したい。できることならリモートのコマンドラインで

$ get file.tar.gz
$ put file.tar.gz

としたい。が、実現する方法が思いつかないので、screenを使う方法を考えた。

以下、Ctrl-Aはscreenの設定にあわせて読みかえてください。

原始的な方法。


uuencodeされたファイルをログに書く。

Ctrl-A H (ログ開始)
$ uuencode filename filename
Ctrl-A H (ログ終了)

でローカルに screenlog.1 みたいなログファイルができる。これを

$ uudecode screenlog.1

でデコードする。ログの前後に不要な行が含まれていても問題ない。uuencodeで指定した二番目のファイル名で保存される。

応用編: uuencodeがサーバに無い場合。


ログイン中のサーバで uuencode が使えない時は、base64エンコードする。この場合、前後にゴミの行が入ってはいけないので、最初と最後にsleepするとよい。

$ sleep 5; base64 ファイル名; sleep 20;
Ctrl-A H (ログ開始)
:
:
Ctrl-A H (出力が終わったらログ停止)

sleepしている隙にログ開始・終了すると、base64エンコードされたファイルだけがログに書き込まれる。エンコード完了時のsleepは油断していると終わってしまうので長めにとってある。

冒頭にも末尾にもゴミの行がついていると、tailとheadを組み合わせなければならず面倒なので、sleepがオススメ。末尾だけなら手元でheadで削ってもよい。

# 行数を確認
wc -l screenlog.1

head -(行数-削る行数) screenlog.1 | base64 -d > filename

応用編: uuencodeのbase64オプションを利用する。


リモートにuuencodeが入っておらず、ローカルに-mオプションが使えるuuencodeが入っている場合は、base64エンコードしたファイルを uudecode できる。手動でヘッダとフッタを出力すればよい。

Ctrl-A H (ログ開始)
echo 'begin-base64 644 filename.out'; base64 filename; echo '====';
Ctrl-A H (ログ終了)

filename.outはデコード時の出力ファイル名。644はデコード時のパーミッション

デコードは uudecode するだけ。簡単。

uudecode screenlog.1

base64エンコードコマンド


base64エンコードは、サーバに入っているアプリケーションで大抵なんとかできる。

# base64
sleep 5; base64 filename; sleep 20;

# macports GNU CoreUtils
sleep 5; gbase64 filename; sleep 20;

# OpenSSL
sleep 5; openssl enc -base64 -in filename; sleep 20;

# Perl
sleep 5; perl -MMIME::Base64 -0777 -ne 'print encode_base64($_)' < filename; sleep 20;

# Python
sleep 5; python -c "import base64,sys;base64.encode(sys.stdin,sys.stdout)" < filename; sleep 20;

# Ruby
sleep 5; ruby -rbase64 -e 'puts Base64.encode64(STDIN.read)' < filename; sleep 20;

FreeBSDはb64encodeが使えるかも。手元にないので未確認。

もっとあれば教えてください。

テキストファイルならエンコードしなくてもいい。

sleep 5; cat filename; sleep 20;

基本的なツールをローカルに入れておく。


ローカルには、base64 や uuencode が入っていてほしい。

Macportsでは port install sharutils で uuencode/uudecode をインストールできる。元から入っている /usr/bin/uuencode だと、base64オプション(-m)が使えなかった。

base64コマンドは、Macportsだと gbase64 という名前で使える。

速度と信頼性


base64で約115Mのファイルを転送してみた。エンコード後は158M。Amazon EC2から手元のMacに落ちるまで15分程度かかった。結構遅い。もうちょっと速い方法があるといいが、、。

はてブのコメントでも指摘されているが、不安なので毎回md5sumをチェックしている。今のところ転送失敗は一度も起きてない。

zmodemを使う。


コメントで教えていただきました!ありがとうございます!

準備。

  • sz/rzコマンドを入れる。ローカル・リモートの両側に必要。
    • Macportsなら、 port install lrzsz 。

ダウンロードの手順。

  • Ctrl-A :zmodem catch でzmodemのキャッチモードに入る。
  • sz filename
  • 受信用の rz コマンドが表示されるので、enterキーを押す。
  • ローカルのカレントディレクトリにファイルが転送される。
  • Ctrl-A :zmodem off でzmodemのキャッチモードを抜ける。

アップロードの手順。

  • Ctrl-A :zmodem catch でzmodemのキャッチモードに入る。
  • リモートで rz -vv -b -E を実行。
  • szのコマンドラインが表示されるので、アップロードしたいローカルのファイルパスを入力し、enterを押す。
  • リモートのカレントディレクトリにファイルが転送される。
  • Ctrl-A :zmodem off でzmodemのキャッチモードを抜ける。

手元の環境ではrzコマンドのフルパスを指定しないと動かなかった。 *1

rz/szオプションを変更するには、.screenrcに以下を追加する。

zmodem recvcmd '!!! /opt/local/bin/rz -vv -b -E '
zmodem sendcmd '!!! /opt/local/bin/sz -vv -b '

無事ファイルは転送できるのだが、転送後にターミナルの出力が壊れたりクラッシュすることがあるようだった。コマンドラインにファイルの内容が流し込まれる怖い現象も何度か経験した。まだ実用にはちょっと厳しいかもしれない……。

screenのバージョンは 4.00.03 。最新のscreenならもっと安定してるかも。

参考

*1:Ctrl-A :exec echo $PATH を実行すると /usr/bin:/bin:/usr/sbin:/sbin なのが原因だろうが、、PATH以外の環境変数は引き継がれているようなのがよくわからない。未解決。