マルチキャストSSHクライアント
入力されたコマンドを複数のサーバーに送信して実行するスクリプトを試作しました。
・・・やっていることは基本的にこれだけなので、いろいろ問題があります。
- historyを使おうとして↑を押すと表示が乱れる。
- バックスペースを押すと表示が乱れる。
- タブを押すと(ry
- Ctrl+Cを押すと、スクリプト自体が終了してしまう。
まぁ、バッチ処理をさせるぶんには何とかなるかも?
使い方
設定ファイル("hosts.yaml")に接続先サーバーとユーザー/パスワードの一覧を平文で書きます。
--- - host: xxx.com user: admin pass: hogehoge - host: yyy.com user: admin pass: foovar
設定ファイルがあるディレクトリでスクリプトを実行。
$ ruby ssh-multicast.rb
しばらくすると「>」が表示されるので、すかさずコマンドを打ち込みます!
Last login: Wed Feb 13 18:37:22 2008 from aaa.co.jp Last login: Wed Feb 13 18:29:27 2008 from aaa.co.jp > ps aux | grep bash
サーバーごとの実行結果がつらつらと表示されます。
ps auxf | grep bash foo 16485 0.0 0.0 7044 1472 pts/0 Ss 18:51 0:00 \_ -bash foo 16877 0.0 0.0 6104 752 pts/0 S+ 18:55 0:00 \_ grep bash [admin@xxx ~]$ ---------------- ps auxf | grep bash foo 15105 0.0 0.0 5564 1480 pts/1 Ss+ 12:08 0:00 | \_ -bash foo 15196 0.0 0.0 5560 1480 pts/2 Ss 12:08 0:00 | \_ -bash foo 26758 0.0 0.0 5560 1488 pts/3 Ss 18:43 0:00 \_ -bash foo 27264 0.0 0.0 4956 752 pts/3 S+ 18:47 0:00 \_ grep bash [admin@yyy ~]$ >
依存モジュール
以下のモジュールを利用しています。別途インストールしてください。
$ gem install --remote net-ssh $ gem install --remote termios
実装
実装は以下。net/sshのサンプルをちょっと改造しただけだったりするのは秘密だ。
require 'rubygems' require 'net/ssh' begin require 'termios' rescue LoadError end def stdin_buffer( enable ) return unless defined?( Termios ) attr = Termios::getattr( $stdin ) if enable attr.c_lflag |= Termios::ICANON | Termios::ECHO else attr.c_lflag &= ~(Termios::ICANON|Termios::ECHO) end Termios::setattr( $stdin, Termios::TCSANOW, attr ) end def shell_loop( shells ) shells.each {|shell| $stdout.print shell.stdout while shell.stdout? } $stdout.print "\n> " $stdout.flush loop { shells.reject! {|shell| !shell.open? } break if shells.empty? data = "" if IO.select([$stdin],nil,nil,0.01) data = $stdin.sysread(1) shells.each {|shell| shell.send_data data } end next if data == nil || data.size <= 0 if ( data =~ /[\w \+\-|]+/ ) $stdout.print data else shells.each {|shell| $stdout.print"\n----------------\n" $stdout.print shell.stdout while shell.stdout? } $stdout.print "\n> " end $stdout.flush } end hosts = YAML.load_file("./hosts.yaml") sessions = [] shells = [] begin hosts.each {|h| sessions << Net::SSH.start( h['host'], h['user'], h['pass'] ) } begin stdin_buffer false sessions.each {|session| shells << session.shell.open( :pty => true ) } shell_loop shells ensure stdin_buffer true end ensure sessions.each {|session| begin session.close ensure Exception puts $! end } end