*趣旨 [#c31deb5e] Linux系のコマンドを扱っていると対話型のコマンドが多いので expectをつかった自動化についてまとめる。 *目次 [#zf5e7d4c] #contents *expectとは [#d053b40a] expectはTclの拡張ライブラリで、対話型の入力が必要な時の自動実行ツールで有名です。Tclのプログラムがそのまま使えるらしいのです。 -拡張デバッグ・オプション -文字指向型のダイアログを記述するための便利なコマンド -面倒な文字指向型端末に対するこの上なく優秀な管理 **手順 [#t78b26fd] http://d.hatena.ne.jp/nakaya0611/20100407/1270642553 *自分なりのコツ [#n99cdd00] **スペースの取り扱いが大切というか特殊 [#gcf1dae4] スペース文字の取り方が重要です。 下記は、メソッド名ですが、Javaとかの感覚でスペースをつけたり、外したりすると動きません。 proc sum {arg1 arg2} { set x [expr {$arg1 + $arg2}]; return $x } puts " The sum of 2 + 3 is: [sum 2 3]\n\n" **文字列のエスケープ [#u554741c] -[はエスケープしておこう -"もエスケープすべし -スペースもエスケープ -シングルクォートは普通の文字だからあてにするな -$もエスケープせよ -タイムアウト無効は-1 例を下記に書いておきます。 ***元にしたスクリプトのファイル [#e54413e2] for num in ${list[@]};do table=${num} file=${table}_dump.sql mysqldump --opt -c -u root ozaka $table > $file pass03=program3 pc03=program3@192.168.1.119 send=~/deployscripts/expectscripts/send.expect $send $file $pc03 $pass03 done ***エスケープするとこうなる [#pd93806a] set command03 "" append command03 for\ num\ in\ \${list\[@]}\;do "\n" append command03 table=\${num} "\n" append command03 file=\${table}_dump.sql "\n" append command03 mysqldump\ --opt\ -c\ -u\ root\ ozaka\ \$table\ >\ \$file "\n" append command03 pass03=program3 "\n" append command03 pc03=program3@192.168.1.119 "\n" append command03 send=~/deployscripts/expectscripts/send.expect "\n" append command03 \$send\ \$file\ \$pc03\ \$pass03 "\n" append command03 done "\n" append command03 "\n" **引数に配列を使う [#vf010cf7] 例えば下記のようにシェルで配列を定義したとします。 list=( "aa" "bb") この引数をtclで取り扱うサンプルコードを作ることができました。 ***コード [#gee59f08] #!/usr/bin/expect proc quoteList {list} { set ret "" for {set i 0} {$i<[llength $list]} {incr i} { append ret " \"" [lindex $list $i] "\"" } return $ret } set m [quoteList $argv] set mm "list=(" append mm $m ")" puts $mm ***使い方例 [#x883e2bd] これをexpectのスクリプトに渡します。 ファイル名を仮にchangeTables.exceptとします。 changeTables.except $list ***結果 [#t451dc31] list=( "aa" "bb") **シェルをspawnする [#pc5ec78f] よく見かけるサンプルはsshのサンプルだったりしますが、シェルで時々パスワード聞かれて面倒な場合のスクリプトはシェルをspawnした方が良い。 通常のコマンドをやり取りするには、シェルをspawnした方がよい。 ***だいだいの雰囲気をつかむための例[#f549738b] #!/usr/bin/expect set timeout 5 spawn /bin/sh expect "#" set my "" send $my expect "Enter password:" send $PASS expect "#" send "exit\n" expect eof **実行が5秒かかるならば、exceptが失敗している [#pdc4ce4b] タイムアウトを5で指定しているので、 実行までに5秒かかる場合は、exceptが失敗している可能性が濃厚です。 **対話が必要なアプリはswanで実行する [#p24405b8] 対話が必要なアプリはspawnで実行するのがコツなようです。 #はプロンプトなので、スーパユーザならば#だろうしか一般ユーザならば$がよくつかわれる。 ただ、プロンプトは設定で変更できるので、確認は必要。 **実行結果 [#o6e9b412] シェルに成功したのか失敗したいのか返したいとき ***成功 [#m84d0ce5] exit 0 ***失敗 [#w5bdc97e] exit 1 ***受け取り側の記述例 [#m3c39b59] シェル側はこんな感じ if exceptのシェル then else fi **サーバー・クリニック: 期待以上の出来のExpect [#xea08193] http://www.ibm.com/developerworks/jp/linux/library/l-sc1/ **Tclの文法などの概要(Wiki) [#x106a64a] http://ja.wikipedia.org/wiki/Tcl/Tk *リンク [#b7eb913e] **本家(英語) [#wf646e1d] http://expect.sourceforge.net/ **Tcl [#p1426b50] http://www.tcl.tk/ ***Tcl基礎文法最速マスター [#gab3fffd] http://pyomeha.blog42.fc2.com/blog-entry-6.html ***マニュアルページ [#h743322c] http://www.tcl.tk/man/tcl8.5/ **DejaGnu [#e35b7333] DejaGnuは他のPCをテストするフレームワークです。Expectで書かれており、すなわちTclで書かれているわけです。 http://www.gnu.org/software/dejagnu/ **ExpectPy [#ha1cfd6e] TclのかわりにPythonで書かれたライブラリかも http://expectpy.sourceforge.net/ **Rubyのexpect.rbの使い方 [#c3365d9b] http://rakuto.blogspot.jp/2006/09/rubyexpectrb.html http://www.42klines.com/2010/08/14/what-to-expect-from-the-ruby-expect-library.html ***Ruby expect 条件文(if文)の書き方 [#f4a66012] http://oshiete.goo.ne.jp/qa/4864602.html ***telnet自動化のサンプル [#ae38fdd2] http://www.webhtm.net/blogger/2008/10/rubyexpect-expectrb.html ***リファレンスマニュアル [#y3b90d9e] http://www.ruby-lang.org/ja/old-man/html/expect.html **PHPをつかった例 [#t86ca016] http://php.net/manual/ja/expect.examples-usage.php ***Javaの場合:ExpectJ [#cf621a4b] http://expectj.sourceforge.net/ *心構え [#x43689fd] 運用を定型化したほうがよい。たとえば実行した結果をwiki等に反映させるとか。 定型化するときに、対話が必要ではない箇所はシェル化しておいたほうがよい。 理由はexpectのスクリプトを書くよりも、シェルを書いたほうが楽 パスは絶対パスでかいておいたほうがよい。理由は、expect内では、シェルの変数とかが使えなかったりするから。 exceptをつかうファイルは普段は暗号をかけ、アクセスできる権限を絞っておくことにしたほうがよい **ファイルの暗号化 [#he0bbb89] [Linux] ファイルを暗号化する方法あれこれ http://ameblo.jp/itboy/entry-10484615897.html *脆弱性チェック [#j11768b3] テキストファイルに暗号化をかけずにパスワードで保管しているのはよくないので自主的に脆弱性について検査しておくこと **Crack/Cracklib [#te2630f7] http://dropsafe.crypticide.com/article/1019 *逆引き [#la892440] **使用する言語の宣言をする [#xf6469d4] #!/usr/local/bin/expect -f (パスは環境によって変わります。) **10秒間応答がなければ終了させる [#tddd23ef] set timeout 10 **expectコマンド内でtelnetを実行 [#f796bbe1] spawn telnet 10.1.1.1 #expectコマンド内でtelnetを実行 **Password: 文字列が帰ってきたら [#uf1ba3c9] expect "Password: " **ログインパスワードaaaを入力 [#n12ae0de] send -- "aaa\n" **expectの終了 [#k36271f8] expect eof **引数の確認 [#c149c8b5] http://blogs.yahoo.co.jp/eguchium/50663261.html **複数の質問を想定 [#se444456] sshは1度目と2度目以降では質問がことなるので、その場合の記述例 expect \"Are you sure you want to continue connecting (yes/no)?\" { send \"yes\n\" expect \"work@srv01's password:\" send \"password\n\" } \"work@srv01's password:\" { send \"password\n\" } **ユーザの入力を促す [#pbca74f7] interact *サンプルコード [#e33e1e34] **パスワードを更新するための簡単なExpectプログラム [#o7025017] # Invoke as "change_password <user> <newpassword>". package require expect # Define a [proc] that can be re-used in many # applications. proc update_one_password {user newpassword} { spawn passwd $user expect "password: " exp_send $newpassword\n # passwd insists on verifying the change, # so repeat the password. expect "password: " exp_send $newpassword\n } eval update_one_password $argv ***Expectの自動化にGUIの外見をもたせる [#ufc45e54] package require Tk frame .account frame .password label .account.label -text Account entry .account.entry -textvariable account label .password.label -text Password # Show only '*', not the real characters of # the entered password. entry .password.entry -textvariable password -show * button .button -text "Update account" -command { update_one_password $account $password } pack .account .password .button -side top pack .account.label .account.entry -side left pack .password.label .password.entry -side left **interactをつかったサンプル [#f0fa5597] set CTRLZ \e032 interact { -reset $CTRLZ {exec kill -STOP 0} \e001 {puts "you typed a control-A\en"; send "\e001" } $ {puts "The date is [exec date]."} \e003 exit foo {puts "bar"} ~~ } **putsをつかったサンプル [#q629a41d] puts $time この辺はTclのコマンドをしらべたほうがいい ***Tclコマンド [#ofb82727] http://www.tcl.tk/man/tcl8.5/TclCmd/contents.htm **正規表現をつかったサンプル [#z556065c] regexp {([0-9][0-9]:[0-9][0-9]:[0-9][0-9])} $expect_out(buffer) _ time **if文をつかったサンプル [#z4f8a5be] if {[llength $argv] != 1} { puts stderr "Usage: daytime host" exit 1 } * いろいろな言語での実装リンクwiki [#q68030de] https://en.wikipedia.org/wiki/Expect *未調査なコマンド [#u4891759] **dislocate [#ha0f0456] プロセスをやめたり、名前付きのプロセスにリモートで再接続することができるかも **cryptdir [#i77a7155] ディレクトリ内のファイルを暗号化するのにつかえるのかも decryptdir **kibitz [#l0c4cad2] 2人で1つのプロセスを触る時につかうのかも **mkpasswd [#u1686a36] パスワード自動生成 **passmass [#d43aecde] 複数台のDBのパスワード変更をおこなうことができるかも **tknewsbiff [#d7c5e7a6] おしらせがあったらポップアップウィンドウを開くのかも **unbuffer [#d9539de8] 出力をみたくない場合につかうのか? **xkibitz [#t98138cb] 1つのプロセスを複数のターミナルで扱うときにつかうのかも *留意点 [#t76bfaaa] 後々知ったことではあるが SSHではRuby/PythonなどのSSHライブラリを使うべきで、パスワードではなく証明書ログインをするべき。 だが、やりすぎると、自分の家の中のドアに全部鍵をつけるがごとくになりかねないので、そこはバランスをとろう * goexpect [#vfff9c50] 自分用のforkした https://github.com/khayashi4337/goexpect