【その他】ごみスクリプト置き場 
 某所にいたときに不毛な作業を少しでも楽にしようと足掻いた時に作ったゴミを置いておきます。たまにこれをベースに何かを作ったりするので主に自分のためのメモですが。
 内容としては、Expectで複数サーバに対して同一コマンドをsuして実行するだけのゴミと複数のサーバから一か所にファイルをSCPで集めるか送るだけのゴミです。



スクリプト背景


 背景として、何かの作業をする時にまず踏み台サーバにログインし、そのあとでさらにSSHを張って二重SSHで作業する必要がある環境でした。その踏み台についても、環境ごとに踏み台先がいくつかあり簡単な作業をしようとしてもログインやらなんやらが結構めんどくさかったのです。全台同一設定作業であれば何とかして楽したいというのがこの背景です。

 先進的なところであればこの手の仕組みはすでにあるはずなのですが、某所にはない&何かエージェントを入れるといったことはできないため、どうにか手元だけでなんとかする必要がありました。
 最初はTeratermマクロで頑張ろうとしましたが、どうもマクロの書き方が好きになれなかったのと自分の知識不足により一部うまく処理できない内容があり、その結果Cygwin+Expect(Tcl)がよさげという判断になりました。そもそも論でその判断すら残念ですが。

実行環境


 Cygwin+Expectを入れてください。たぶん動きます。LinuxのExpectでも動いたので手元にLinux環境があるならyum install expectなどで入れてください。


SSHするゴミ



#!/usr/bin/expect
#使い方: ./gomi1.sh $IPが書かれたファイル $コマンドが書かれたファイル

#IPのリストファイル読み込み
set file [open [lindex $argv 0 ]]
set list [split [read $file] "\n"]
close $file

#コマンドリストファイル読み込み
set file2 [open [lindex $argv 1 ]]
set cmd [split [read $file2] "\n"]
close $file2

#ネットワーク環境が悪い場合を考え10文字送ったら0.1秒待つ
set send_slow {10 0.1}

#ループカウンタその他
set prv ""
set cnt 1

#カンマ区切りのサーバリストファイルを読み込む
foreach l $list {
#カンマをばらす
set line [ split $l "," ]
#第一カラムから踏み台先のIPを得る
set humidai [lindex $line 0 ]
#踏み台にログインするためのユーザー
set h_usr [lindex $line 1 ]
#踏み台にログインするためのパスワード
set h_pw [lindex $line 2 ]
#踏み台のプロンプト(PS1,[aaa@~ ]$ など)
set h_ps [lindex $line 3 ]
#踏み台からSSH接続するIP
set r_ip [lindex $line 4 ]
#踏み台からSSHを張る時に使うユーザー
set r_usr [lindex $line 5 ]
#踏み台からSSHを張る時に使うパスワード
set r_pw [lindex $line 6 ]
#リモートの通常ユーザのプロンプト
set r_ps [lindex $line 7 ]
#リモートでsu -するときに使うパスワード
set r_supw [lindex $line 8 ]
#リモートのrootのプロンプト
set r_root_ps [lindex $line 9 ]
#最終行に来たら終了する
if {$humidai == ""} {
exit
}
#踏み台が直前と違ったらSSHを張りなおすために今のセッションを切る
if { $prv != $humidai } {
if { $cnt != 1 } {
   puts " Humidai changed "
   close
}
#踏み台にSSHを張る
spawn ssh $h_usr@$humidai
expect "password: " {
send -s "$h_pw\r"
#初回SSH接続時のアレをyする
} "/no)? " {
send -s "yes\r"
expect "password: "
send -s "$h_pw\r"
}
}

#--踏み台にSSHを張った後の処理--
#プロンプトを待つ
expect "$h_ps"
#踏み台のヒストリがsshだらけになってしまうので無視させる
send -s "export HISTIGNORE=* \r"
expect "$h_ps"
#踏み台から対象サーバにSSHを張る
send -s "ssh $r_usr@$r_ip \r"
expect "password: " {
send -s "$r_pw\r"
} "/no)? " {
send -s "yes\r"
expect "password: "
send -s "$r_pw\r"
}

#--リモートにSSHを張った後の処理--
expect "$r_ps"
#suする
send -s "su - \r"
#password:を待つ
expect ":"
send -s "$r_supw\r"
expect "$r_root_ps"
#ワンライナーで長いコマンドを書いてしまうとヒストリがカオスなことになってしまうので無視させる
send -s "export HISTIGNORE=* \r"

#--コマンドが書かれたファイルを読み込みrootでコマンドを実行していく--
foreach c $cmd {
expect "$r_root_ps"
send -s "$c \r"
}

#--コマンド実行終了--
#後処理、何度も実行するとヒストリがHISTIGNOREだらけになってしまうのでそれを消す
send -s "for a in `history|grep HISTIGNORE|awk '{ print \$1 }' |srot -nr `; do histroy -d \$a;done \r"
expect "$r_root_ps"
send "exit\r"
expect "$r_ps"
send -s "exit \r"
#踏み台のヒストリからHISTIGNOREを消す
expect "$h_ps"
send -s "for a in `history|grep HISTIGNORE|awk '{ print \$1 }' |srot -nr `; do histroy -d \$a;done \r"

set prv "$humidai"
set cnt [expr $cnt + 1 ]
}


なお、ゴミなので入力エラーなどなにもチェックしていません。
また、ファイルへコピーするときはUTF-8(BOMなし)とLFの改行コードで作成しないと怒られます。

使い方は、
./このファイル名.sh IPりすとファイル コマンドが書かれたファイル
として使います。

IPリストファイルの書き方は、以下のように書きます。Excelなどでcsvを作り、dos2unixなどで改行コードをlfにすると楽です。#などは別にコメントアウト扱いにならないため注意してください。

#踏み台IP,踏み台ログインユーザ,踏み台ユーザパスワード,踏み台ユーザプロンプト,接続先IP,リモートユーザ,リモートユーザパスワード,リモートユーザプロンプト,リモートrootパスワード,リモートrootプロンプト

192.168.1.100,usr1,usrpass,]$ ,192.168.2.99,usr1,usrpass,]$ ,rootpass,]# ,HOSTNAME
192.168.1.100,usr1,usrpass,]$ ,192.168.2.98,usr1,usrpass,]$ ,rootpass,]# ,HOSTNAME
192.168.1.100,usr1,usrpass,]$ ,192.168.2.97,usr1,usrpass,]$ ,rootpass,]# ,HOSTNAME
192.168.1.101,usr1,usrpass,]$ ,192.168.3.99,usr1,usrpass,]$ ,rootpass,]# ,HOSTNAME
192.168.1.101,usr1,usrpass,]$ ,192.168.3.98,usr1,usrpass,]$ ,rootpass,]# ,HOSTNAME
....

HOSTNAMEに関してはスクリプト内では使っていませんが、とりあえず一つのファイルに全ての情報を書いておくと作業依頼が来たときに対象を絞り出す時に便利なので書いていました。リストファイルはローカルのみでの保存なのでセキュリティーは確保されている前提です(きり

作業対象のホスト名だけを書いたlstというファイルを置いて
for a in `cat lst`; do grep $a ip.lst ;done

みたいなことをするとリストから必要な情報だけをとれます。

コマンドファイルは単純に実行するコマンドを縦に書いていきます。
echo "`uname -n` at `date`"
ls /
whoami

など…

ちなみに'"'や'$'などは特にエスケープしなくてもExpectかTclがよしなにしてくれるようですが、すべてを確認したわけではないので事前に爆発しないか確認してください。

SCPするゴミ


上のSSHするゴミで実行した結果を/tmpなどに書き出し、それをまとめて踏み台に持ってくるためのゴミです。
ただし、あくまでも”踏み台サーバから(まで)"のファイル移動なので、手元に持ってくる(配布物を踏み台に持っていく)には手動でSCPをする必要があります。まあ少し書けばそれ単体のスクリプトも作れますが…。

#!/usr/bin/expect
#使い方: ./gomi2.sh -p|-g $iplistfile $filelist
set m [lindex $argv 0 ]

#SCPのオプション
set scp "scp -pr"
#持っていく、またはとってくるときにファイルを置くディレクトリ
set dir "/tmp/"

#IPのリストを開く
set file [open [lindex $argv 1 ]]
set list [split [read $file] "\n"]
close $file

#ファイルリストを開く
set file2 [open [lindex $argv 2 ]]
set flst [split [read $file2] "\n"]
close $file2

#カウンタとか
set prv ""
set cnt 1
set send_slow {10 0.1}


#モードの切り替え
if {$m == "-p" } {
set mode "put"
} elseif {$m =="-g"} {
set mode "get"
} else {
puts stderr " first option must be -p or -g "
puts stderr " -p is 'put', -g is 'get'"
exit
}
#debug
puts " mode is $mode "

#IPリストを開いてアレする
foreach l $list {
#カンマをばらす
set line [ split $l "," ]
#第一カラムから踏み台先のIPを得る
set humidai [lindex $line 0 ]
#踏み台にログインするためのユーザー
set h_usr [lindex $line 1 ]
#踏み台にログインするためのパスワード
set h_pw [lindex $line 2 ]
#踏み台のプロンプト(PS1,[aaa@~ ]$ など)
set h_ps [lindex $line 3 ]
#踏み台からSSH接続するIP
set r_ip [lindex $line 4 ]
#踏み台からSSHを張る時に使うユーザー
set r_usr [lindex $line 5 ]
#踏み台からSSHを張る時に使うパスワード
set r_pw [lindex $line 6 ]
#リモートの通常ユーザのプロンプト
set r_ps [lindex $line 7 ]
#リモートでsu -するときに使うパスワード
set r_supw [lindex $line 8 ]
#リモートのrootのプロンプト
set r_root_ps [lindex $line 9 ]

if {$humidai == ""} {
exit
}

if { $prv != $humidai } {
if { $cnt != 1 } {
close
}
puts " start scp "
spawn ssh $h_usr@$humidai
expect "password: " {
send -s "$h_pw\r"
} "/no)? " {
send -s "yes\r"
expect "password: "
send -s "$h_pw\r"
}
}
#踏み台のヒストリがSCPだらけになってしまうのを防ぐためにすべてをHISTIGNOREする
expect "$h_ps"
send -s "export HISTIGNORE=* \r"
expect "$h_ps"
send -s "for a in `history|grep HISTIGNORE|awk '{ print \$1 }' |sort -nr `; do history -d \${a};done \r"


#ファイルリストを読み込む
foreach f $flst {
expect "$h_ps"
if { $f == "" } {
#最後のファイルをSCPするために空エンターする必要がある
send -s " \r"
break
}
#送信か受信か
if {$mode == "get"} {
#--ファイル受信開始--
send -s "$scp $r_usr\@$r_ip\:$f $dir \r"
} elseif {$mode == "put"} {
#--ファイル送信開始--
send -s "$scp $f $r_usr\@$r_ip\:$dir \r"
}

#SCPしていく
expect "password: " {
send -s "$r_pw\r"
} "/no)? " {
send -s "yes\r"
expect "password: "
send -s "$r_pw\r"
}
}
set prv "$humidai"
set cnt [expr $cnt + 1 ]
}


使い方
ファイルリストに書かれた"踏み台サーバにある"ファイルを対象サーバの/tmpに配置する
./gomi2.sh -p IPリストファイル ファイルリスト

某所の環境はrootで直接ログインできず、一般ユーザでログインする必要があり、また権限の問題でファイルの受け渡しに/tmpを使う必要があったため、送信:受信ディレクトリを/tmpとしています。
IPリストファイルのフォーマットはSSHするゴミと同じです。

ファイルリストは対象ファイルをフルパスで書く必要があります。


SSHするゴミでifconfig -aの情報を>/tmpに書き出し、SCPするゴミで踏み台に持ってくる
#コマンドファイル
[root@localhost html]# cat cmd.lst
ifconfig -a >/tmp/ifconfig-`hostname`.txt
chmod 777 /tmp/ifconfig-`hostname`.txt
#IPリスト
[root@localhost html]# cat ip.lst
192.168.1.100,usr1,usrpass,]$ ,192.168.2.99,usr1,usrpass,]$ ,rootpass,]# ,HOSTNAME
192.168.1.100,usr1,usrpass,]$ ,192.168.2.98,usr1,usrpass,]$ ,rootpass,]# ,HOSTNAME
192.168.1.100,usr1,usrpass,]$ ,192.168.2.97,usr1,usrpass,]$ ,rootpass,]# ,HOSTNAME
192.168.1.101,usr1,usrpass,]$ ,192.168.3.99,usr1,usrpass,]$ ,rootpass,]# ,HOSTNAME
192.168.1.101,usr1,usrpass,]$ ,192.168.3.98,usr1,usrpass,]$ ,rootpass,]# ,HOSTNAME

#実行
[root@localhost html]# ./gomi.sh ip.lst cmd.lst
(省略)

これでとるべき情報をまず/tmp/ifconfig-`hostname`.txtとして保存したので、その後SSHするゴミで踏み台の/tmpに集めます。
#リストファイル
[root@localhost html]# cat file.lst
/tmp/ifconfig-*.txt
#実行
[root@localhost html]# gomi2.sh -g ip.lst file.lst

これで踏み台の/tmpにそれぞれの内容が集まったので、あとは手でSCPして持ってきます。
[root@localhost html]# for a in `cut -f1 -d"," ip.lst|uniq`;do scp -pr ${a}:/tmp/ifconfig-*txt ./;done

まとめ
本来であればこんなことしなくても一括配布的な何かが導入されていそうなものなのですが…
しかし、よくみんな今まで手で一つ一つ作業していたなあと逆に感心しました。

自分のためのメモですが、何かのベースにしたりするのはご自由にどうぞ。Expectの需要が今更あるのか謎ですが。

コメントを書く
必要事項とコメントを入力して下さい。









タグの挿入