2009年05月11日(月) [長年日記]
- 連休が明けたばかりだからか夜眠れない。朝になってやっと眠くなる。
- 夏のように暑い。
■ [ruby] Net::SSH でコマンドの終了コードを得る
(追記:Net::SSHの最新のドキュメントはgithub上のものみたい。)
Net::SSHは Net::SSH::Connection::Session#exec()で簡単にリモートでのコマンド実行ができる。が、このメソッドではコマンドの終了コードを得られず困る。
Net::SSH::Connection::Channel#on_request()を使うことで終了コードを取得できたので、サンプルスクリプトと実行結果をメモ。
require "rubygems" require "net/ssh" include Net::SSH::Prompt def parse_args if ARGV.size < 2 || ARGV.size > 3 $stderr.puts "Usage: #{$0} HOST USER [PASSWORD]" exit 2 end host = ARGV[0] user = ARGV[1] password = ARGV[2] || prompt("Enter password for #{user}@#{host}:", false) return host, user, password end # ssh session の close を保証する def ssh_start(host, user, password) begin session = Net::SSH.start(host, user, :password => password) yield session rescue => ex puts "Exception: #{ex.message}" ex.backtrace.each { |trace| puts trace } ensure session.close if session end end def ssh_exec(session, command) exit_status = nil session.open_channel { |channel| # Channel#on_request() を使うと終了コードを取得できる channel.on_request("exit-status") { |ch, data| exit_status = data.read_long } # 標準出力は Channel#on_data() で扱う channel.on_data { |ch, data| data.each_line { |line| puts "[remote:out] #{line}" } } # 標準エラーは Channel#on_extended_data() で扱う channel.on_extended_data { |ch, type, data| data.each_line { |line| puts "[remote:err] #{line}" if type == 1 } } channel.exec(command) } session.loop return exit_status end ssh_start(*parse_args) { |session| puts "Exit status: #{ssh_exec(session, "ps | grep ssh")}" puts "Exit status: #{ssh_exec(session, "sp")}" puts "Exit status: #{ssh_exec(session, "cat nantoka")}" }
実行結果は次の通り。
$ ruby sshexec.rb localhost kenichi Enter password for kenichi@localhost: [remote:out] 3668 ? 00:00:02 ssh-agent [remote:out] 10067 ? 00:00:00 sshd [remote:out] 11922 ? 00:00:00 sshd Exit status: 0 [remote:err] zsh:1: command not found: sp Exit status: 127 [remote:err] cat: [remote:err] nantoka [remote:err] : No such file or directory [remote:err] Exit status: 1
■ [shell][unix] /usr/bin/getopt を使ったサンプル
/usr/bin/getoptを使うとシェルスクリプトの引数の解析が簡単(でもないけど)にできるが、使い方を覚えられないのでメモ。
- getoptは与えられたパラメータを並び変えた結果を出力する。また、パラメータのエラーを検出してくれる。
- ロングオプションも扱える。
- getoptが並び変えた結果を自前で解析する必要がある。
- getoptが行うクォート処理を正しく扱う必要がある。ここ理解しきれていなくて、spikelet daysにある例の真似をした。
- 少なくともbashでは「set --」を使って位置パラメータを置き換えることができる。
以下サンプルスクリプトと実行結果。
#!/bin/bash # -a 引数必須 # -b 引数なし # --long 引数必須 # $@を""で囲む必要がある。 args=`getopt -o a:b -l long: -- "$@"` # getoptの終了コードで書式に間違いがあるかが分かる if [ "$?" -ne 0 ]; then echo "usage: $0 [-a VALUE] [-b] [--long VALUE] ARGS" >&2 exit 2 fi # getoptはパラメータの順序を整理した結果を出力する echo 'getopt' output: $args # 位置パラメータ($1, $2, ...)の内容を再設定する # ""で囲った結果をevalで実行する必要がある eval set -- "$args" until [ "$1" == "--" ]; do case $1 in -a) a=$2 shift ;; -b) b=true ;; --long) long=$2 shift ;; esac shift done shift # '--' を取り除く echo a=$a echo b=$b echo long=$long for arg; do echo arg=$arg; done
実行結果は次の通り。
$ type getopt getopt is /usr/bin/getopt $ getopt --version getopt (enhanced) 1.1.4 $ ./getopt.sh abc def getopt output: -- 'abc' 'def' a= b= long= arg=abc arg=def $ ./getopt.sh 'abc def' getopt output: -- 'abc def' a= b= long= arg=abc def $ ./getopt.sh abc def --oreore getopt: unrecognized option '--oreore' usage: ./getopt.sh [-a VALUE] [-b] [--long VALUE] ARGS $ ./getopt.sh abc def --long getopt: option '--long' requires an argument usage: ./getopt.sh [-a VALUE] [-b] [--long VALUE] ARGS $ ./getopt.sh abc def --long=loooong -a 's p a c e' -b getopt output: --long 'loooong' -a 's p a c e' -b -- 'abc' 'def' a=s p a c e b=true long=loooong arg=abc arg=def