7
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

expect TIPS

Last updated at Posted at 2020-05-01

expect TIPS

unix系OSでコマンドライン対話を自動化するツール expect に関するTIPSです。
expect 特有の話と、Tcl の話が混ざってます。
なんとなく、ルーター、L2スイッチ、L3スイッチに対する対話を想定しています。

expect は Tcl である

expect のスクリプトは Tcl(ティクル) 言語をベースにしています。
もっと言うと、expect は Tcl 言語の拡張機能です。

Expect is an extension to the TCL scripting language written by Don Libes.

基本

変数への代入


set MY_VAR "yes"

コマンド展開: []

コマンドを [] で囲うと実行時に展開されます。

if での判定や、set での代入にコマンドの実行結果を使いたいときは [] で文を囲います。

# 環境変数 MY_VAR が存在したら何かする
if { [ info exists ::env(MY_VAR) ] } {
    # do something
}
# 最初のコマンドライン引数を、MY_VARに代入する
set MY_VAR [lindex $argv 0]

文字列リテラル

$VAR または ${VAR} で変数を参照し、展開できます。

文字列リテラルの中でも、[] はコマンド展開されます。展開されたくないときは \[\] のようにバックスラッシュでエスケープします。

set PROMPT "\[a-zA-Z0-9\._-\]+#"

フロー制御

条件分岐

if { 式1 } {
    # 何かする
} elseif { 式2 } {
    # 何かする
} else {
    # 何かする
}

ループ

for { 初期化文 } { 判定式 } { 繰り返し時に実行する文 } {
    # ループで何かする
}

for { set i 0 } { i <= 10 } { incr i } {
    # i=0からi=10まで繰り返される
}

ログファイル

通信ログをファイルに記録する

log_file ファイル名

デフォルトだと追記モードです。追記したくない場合は -noappend を使います。

log_file -noappend ファイル名

ログファイルへの記録を止める

引数無しでコマンド実行すると、ログファイルへの記録が終了します。

log_file

画面出力

ログと標準出力に出力する: send_user

send_user で標準出力に文字列を出力できます。
log_file でファイル出力している場合、ログファイルにも記録されます。
改行は自動付与されないので、明示的に書く必要があります。

# 標準出力とログファイルの両方に出力される
send_user "hello\n"

標準出力に出力する: puts

puts でも標準出力に文字列を出力できます。
log_file でファイル出力している場合、ログファイルには記録されません。
改行が自動付与されます。

# 標準出力だけに出力される
puts "hello"

実践編

telnet、sshで改行を送信するときは \r で送る

send "somedata\r"

\n ではなく \r です。

コマンドライン引数を取得する

コマンドライン引数は $argv 配列に入っています。
配列は lindex 関数でアクセスします。
https://www.tcl.tk/man/tcl8.5/TclCmd/lindex.htm

$ expect -f myscript.expect arg1 arg2 arg3
if { [ lindex $argv 0 ] == "arg1" } {
    send_user "first arg is \"arg1\""
}

spawn のコマンドラインを表示させない

-noecho を使います。

spawn ssh 10.0.0.1
# expect の実行時に "spawn ssh 10.0.0.1" と表示される
spawn -noecho ssh 10.0.0.1
# expect の実行時に "spawn ssh 10.0.0.1" が表示されない

環境変数が存在するかチェックする

組み込み関数 info exists を使ってチェックします。
https://www.tcl.tk/man/tcl8.5/TclCmd/info.htm#M11

if { [ info exists ::env(MY_VAR) ] } {
    # do something
}

デバッグする

exp_internal を使うと expect の内部状態の遷移が標準出力に出力されます。

exp_internal 1

expectコマンドで複数のパターンを待つ

expect {
    "login:" {
        send "$USERNAME\r"
    }
    "password:" {
        send "$PASSWORD\r"
    }
}

"login:" が来たら USERNAME変数の値を送信します。
"password:" が来たら PASSWORD変数の値を送信します。

どっちもこなかったら、タイムアウトまで待ってエラーになります。

expectコマンドで正規表現で待つ

パターンの前に -re を指定します。
[] はコマンド展開にならないようにエスケープが必要です。

expect {
    -re "(username|login):" {
        send "$USERNAME\r"
    }
    -re "\[Pp\]assword:" {
        send "$PASSWORD\r"
    }
}

正規表現は tcl の方言

正規表現の . は改行にはマッチしない

"partial newline-sensitive matching" になっているっぽい。

partial newline-sensitive matching

  • . は改行文字 (\r, \n) にはマッチしない
  • ^ は入力文字列の先頭にのみマッチする。途中の各行の先頭にはマッチしない。
  • $ は入力文字列の末尾にのみマッチする。途中の各行の末尾にはマッチしない。

正規表現のエスケープは二重エスケープにする

tcl文字列として評価された後に、正規表現としての評価になるので、二重にエスケープが必要です。

expect {
    -re "\\S+\[\\r\\n\]" {
        # 何かする
    }
}

\\S+\[\\r\\n\] は、tcl文字列として評価すると \S+[\r\n] になります。これが正規表現として評価されます。

ただし、\r\n については、tcl文字列の評価で 0x0d、0x0a に変換されるので単一エスケープでも動作します。
\\S+\[\r\n\] は、tcl文字列として評価すると \S+[ 0x0d 0x0a ] になります。
つまり、正規表現エンジンに対して、\r\nという文字列が与えられるのではなく、0x0d 0x0a という制御文字コードがそのまま与えられることになります。

一方で、\S\s は、tcl文字列としては有効なエスケープシーケンスではないので、単に \ が外されて、Sという文字列になり、Sが正規表現エンジンに与えられます。 アルファベットのSという文字にしかマッチしません。

行頭にマッチする、の書き方

^(文字列の先頭) または [\r\n](改行文字) を |(OR条件) で並べて (?:) (非キャプチャグルーピング) で囲う。

expect {
    -re "(?:^|\[\\r\\n\])Password:" {
        # 何かする
    }
}

または、(?n) で newline-sensitive matching を指定して、^ にマッチさせる。
接続先のOS依存だが、\r\nで改行した後に、\r で行頭にカーソル移動させて表示させていることがあるので、\r? も入れておくと無難です。

expect {
    # 改行後に `\r` が入っている場合でもマッチさせる
    -re "(?n)^\\r?Password:" {
        # 何かする
    }
}

expectコマンドでマッチした部分を取り出す

$expect_out(0,string) でマッチした全体を取り出します。
$expect_out(1,string) で 一つ目の () の中身を取り出します。

expect {
    -re "(username|login):" {
        send "$USERNAME\r"
        send_user "prompt is $expect_out(0,string)\n"
        # ==> username: (または login:)
        send_user "prompt-keyword is $expect_out(1,string)\n"
        # ==> username (または login)
    }
}
7
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?