Ruby でファイル転送のコマンドを作る

ローカルネットワーク内に何台か PC があるので、Rubyファイル転送を行うことを考えました。SSH でやってもよいのですが、一台の PC に Linux をマルチブートしているので、面倒なので Ruby でやります。セキュリティは何も考えていないので、当り前ですが絶対にインターネットに出てはいけません。というか、ローカルでも可能な限り SSH(scp コマンド)でやった方がいいと思います(参考)。


使い方はこんな感じ。受け手側(もちろんこちらを先に実行して下さい)。

$ file_transfer trans
IPアドレス: 192.168.11.6
[192.168.11.2:34098] からの接続を了承しました
spline_curve.png を受信中です...
spline_curve.png の受信が完了しました

送り手側。

$ file_transfer trans 192.168.11.6 spline_curve.png
spline_curve.png を送信中です...
spline_curve.png の送信が完了しました

 
コードはこんな感じです。基本的にここのコードを流用させて頂きました(ありがとうございます!)。
file_transfer

#!/usr/bin/env ruby
require 'socket'
require 'thor'

class FileTransfer < Thor
  desc 'trans', "Transfer a file."
  def trans(*args)
    send_file = lambda do |host, name|
      puts "#{name} を送信中です..."
      open(name, "rb") do |file|
        size = File.size(name)
        sock = TCPSocket.open(host, 7413)
        sock.puts "PUT_FILE #{name} #{size}"
        sock.write(file.read)
        sock.close
      end
      puts "#{name} の送信が完了しました"
    end
    
    receive_file = lambda do
      print "IPアドレス: "
      ip_ad = Socket.getifaddrs.select {|x| x.addr.ipv4?}
      puts ip_ad.select {|x| x.addr.ip_address.include?("192.168")}[0].addr.ip_address
      
      s0 = TCPServer.open(7413)
      loop do
        Thread.start(s0.accept) do |sock|
          puts "[#{sock.peeraddr[3]}:#{sock.peeraddr[1]}] からの接続を了承しました"
          while (m = /^PUT_FILE\s+(\S+)\s+(\d+)$/.match(sock.gets.chop))
            name, size = File.basename(m[1]), m[2]
            puts "#{name} を受信中です..."
            open("#{name}", "wb") do |file|
              file.write(sock.read(size.to_i))
            end
            puts "#{name} の受信が完了しました"
          end
          puts "[#{sock.peeraddr[3]}:#{sock.peeraddr[1]}] からの接続が拒否されました"
          sock.close
        end
      end
    end
    
    
    host, file_name = args
    
    if file_name
      send_file.call(host, file_name)
    else
      receive_file.call
    end
  end
end

FileTransfer.start

コマンド化は Gem 'thor' で行っているので、これをインストールする必要があります。そして、ファイルに実行権を与えて、/usr/local/bin などにコピーして下さい。なお、ポート番号など細かいところは適宜各自の環境に合わせて下さい。

なお、rbenv を利用して Ruby をインストールされている方は、シェバング行を

#!/home/***/.rbenv/versions/2.3.3/bin/ruby

のように変更して下さい。もちろん「***」や Ruby のバージョンの部分は適宜自分の環境に合わせて下さい。


※参考
お手軽ファイル転送 - Usipedia
Ruby 2.1.0 では Socket.getifaddrs で簡単にインターフェイスのアドレスを取れる
Ruby の Thor でコマンドを作る - Marginalia
 

※追記
このままだとバイナリファイルや(ネストした)ディレクトリが送れなかったりするので、改良版を作りました。
obelisk.hatenablog.com