概要
LinuxやMacでコマンドの自動実行を行いう為のスクリプト。
コマンドによっては対話処理が必要な場合がある。 (例: SSHのPW入力, yum/apt等のinstallコマンド)
対話入力を含めて自動実行をするスクリプト。
対話処理には[expect]を使用する。
参考 https://linux.die.net/man/1/expect
install expect
yum install expect
apt-get install expect
brew install expect
Command
set [WORD] [VALUE] #変数を宣言して値を代入 set timeout [SEC] #expectのタイムアウト時間を指定 [無限: -1] spawn [CMD] #対話処理が発生するコマンド expect \"[WORD]\" #コマンド結果や処理中の中で対話処理のきっかけとなる文字を指定 exp_continue #expectにマッチしても再度最初に戻りチェック (ループ処理のcontinueと同じ) send [CMD] #対話処理の際に入力するコマンドや文字列 send_user [WORD] #標準出力に任意の文字を出力 (抑制状態でも出力される) puts [STR] #標準出力のみに指定の文字列を出力 open [PATH] #指定したパスのファイルを読み込む log_file [PATH] #ログを指定のファイルに出力 (-noappend オプションで上書きモード) log_file #ファイル指定なしでログ記録終了 log_user [NO] #標準出力へのログ(処理内容)の出力を制御 [0: 出力を抑制, 1: 抑制解除] send_log [WORD] #ログファイルに任意の文字列を書き込む interact #ユーザに制御を戻す (ログインだけを自動化する際などに使用) exit #spawnで開始したセッションを終了
Expect Pattern
正規表現の注意事項
Case1: 通常
expect -c "
spawn [CMD]
expect \"[WORD]\"
send [CMD]
"
Case2: 複数
expect -c "
spawn [CMD]
expect {
\"[WORD]\" {
send
}
\"[WORD]\" {
send
}
}
"
Case3: 正規表現
expect -c "
spawn [CMD]
expect {
-re \"[WORD]\" {
send
}
-re \"[WORD]\" {
send
}
}
"
Execute Pattern
Case1
#!/bin/bash
expect -c "
spawn [CMD]
expect \"[WORD]\"
send [CMD]
"
Case2
- expect program (test.exp)
#!/usr/bin/expect spawn [CMD] expect \"[WORD]\" send [CMD]
- execute
expect test.exp
条件分岐
if { 条件1 } {
#処理1
} elseif { 条件2 } {
#処理2
} else {
#いずれにも該当しない場合の処理
}
ループ処理
特殊処理
- break : 処理を中断しループを抜ける
- continue : 処理を中断し次のループ処理へ移行
for
for { 変数の初期化 } { 条件 } { 変数の加算 } {
#処理
}
### 例
for { set i 0 } {$i < 5 } { incr i } {
#処理
}
while
while { 条件 } {
#処理
}
比較演算子
### 論理演算子 x && y :x条件とy条件の比較結果がともにTRUE x || y :x条件とy条件の比較結果のどちらかがTRUE !x :x条件の比較結果がFALSEの時にTRUE ### 関係演算子 x < y :xよりyが大きい x > y :xよりyが小さい x == y :xとyが等しい x != y :xとyが等しくない x <= y :xとyが等しい、または小さい x >= y :xとyが等しい、または大きい。
例1 ssh接続を自動化
#!/bin/bash HOST="xx.xx.xx.xx" #:接続先IP USER="user" #:ユーザ名 PASS="password" #:パスワード WAITFLG="#" #:反応待ち文字 logFile="autolog.log" #:ログの出力先 expect -c " set timeout -1 spawn ssh ${USER}@${HOST} #:SSH接続のプロセスを開始 expect { \"password:\" { #:パスワード要求を待機 send \"${PASS}\n\" #:パスワード要求に対して送信 exp_continue } \"fingerprint\" { #:初回接続時のfingerprintを待機 send \"yes\n\" exp_continue } \"${WAITFLG}\" { #:接続完了時のプロンプトを待機 send \"\n\" } } interact #:接続状態でユーザに制御を戻す "
例2 ssh接続先での複数コマンド実行
Command file (cmd.txt)
cat /etc/os-release df -h
Program (autocmd-single.sh)
#!/bin/bash HOST="xx.xx.xx.xx" #:接続先IP USER="user" #:ユーザ名 PASS="password" #:パスワード CMDPATH="./cmd.txt" #:実行対象のコマンドリストのファイル WAITFLG="#" #:反応待ち文字 logFile="autolog.log" #:ログの出力先 expect -c " #SSH接続 set timeout -1 #:expect待機のタイムアウト時間 今回は無限 spawn ssh ${USER}@${HOST} #:SSH接続のプロセスを開始 expect { \"password:\" { #:パスワード要求を待機 send \"${PASS}\n\" #:パスワード要求に対して送信 exp_continue } \"fingerprint\" { #:初回接続時のfingerprintを待機 send \"yes\n\" exp_continue } \"${WAITFLG}\" { #:接続完了時のプロンプトを待機 send \"\n\" } } # Fileを読み込んでコマンド実行 set FILE [open ${CMDPATH} r] #:Fileパスからデータを読み込む while {! [eof \$FILE]} { #:ファイルを1行ずつ読み込む set line [gets \$FILE] #:現在読み込んでいる行のCommandを格納 #コマンドの送信 expect \"\${WAITFLG}\" { #コマンドの反応待ち send \"\${line}\n\" #:コマンドを送信 } #見やすいように改行を入れる for { set i 0 } {\$i < 3 } { incr i }{ #:3回改行を入れる expect \"${WAITFLG}\" { #:コマンドの反応待ち send \"\n\" #:コマンドを送信 } } } " | tee -a $logFile
例3 複数のssh接続先でコマンド実行
仕様
- コマンドを実行する対象のIPリストを[target.txt]で作成
- 今回はCisco機器を対象として作成
- IPリスト[target.txt]に記載したものと同じ名前で、以下3つのファイルを作成して配置
- コマンドリストのファイルを[./cmdlist]配下に作成
- ユーザリストのファイルを[./userlist]配下に作成
- パスワードリストのファイルを[./passlist]配下に作成
- 特権パスワードリストのファイルを[./enablelist]配下に作成
- ディレクトリ構成
- ./autocmd-multi.sh プログラム本体
- ./target.txt コマンドの実行先リスト
- ./cmdlist 各実行先ごとのコマンドを記載
- xx.xx.xx.xx.txt
- xx.xx.xx.xx.txt
- ./userlist 各実行先ごとのログイン用のユーザ名
- xx.xx.xx.xx.txt
- xx.xx.xx.xx.txt
- ./passlist 各実行先ごとのログイン用のパスワード
- xx.xx.xx.xx.txt
- xx.xx.xx.xx.txt
- ./enablelist 各実行先ごとの特権パスワード
- xx.xx.xx.xx.txt
- xx.xx.xx.xx.txt
- ./log ログの保存先ディレクトリ
- xx.xx.xx.xx.log
hosts file (target.txt)
xx.xx.xx.xx
Command Directory (./cmdlist)
- xx.xx.xx.xx.txt
show version show run
UserList Directory (./userlist)
- xx.xx.xx.xx.txt
username
PassList Directory (./passlist)
- xx.xx.xx.xx.txt
password
EnableList Directory (./enablelist)
- xx.xx.xx.xx.txt
enablepass
Program (autocmd-multi.sh)
more等の入力待機が入ると、待機状態で処理が終わらないので注意が必要
#!/bin/bash #Cisco機器へのコマンド実行を想定 HOSTSPATH="./target.txt" #:宛先リスト CMD="./cmdlist" #:コマンドリスト(共通) USER="./userlist" #:ユーザ名(共通) PASS="./passlist" #:パスワード(共通) ENAPASS="./enablelist" #:特権パスワード(共通) WAITFLG="#" #:反応待ち文字 logdir="./log" #:ディレクトリ指定(ファイル名ではない&/で終わる) CHALLENGE=2 #:パスワード試行回数制限 echo "########### Directory and File Check ###########" dirflg=1 if [ ! -f $HOSTSPATH ]; then echo "File not found: $HOSTSPATH";dirflg=0; fi #:ファイルの存在確認 if [ ! -d $CMD ]; then echo "Directory not found: $CMD";dirflg=0; fi #:ディレクトリの存在確認 if [ ! -d $USER ]; then echo "Directory not found: $USER";dirflg=0; fi #:ディレクトリの存在確認 if [ ! -d $PASS ]; then echo "Directory not found: $PASS";dirflg=0; fi #:ディレクトリの存在確認 if [ ! -d $LOGDIR ]; then echo "Directory not found: $LOGDIR";dirflg=0; fi #:ディレクトリの存在確認 if [ $dirflg -eq 0 ]; then echo "Execution finished";exit; fi #:どれか一つでも存在しなければ処理を終了 echo -e "Check Complete\n\n" echo "########### Start Get Command ###########" while read line ; do fileflg=1 if [ ! -f "${CMD}/${line}.txt" ]; then echo "File not found: ${CMD}/${line}";dirflg=0; fi #:ファイルの存在確認 if [ ! -f "${USER}/${line}.txt" ]; then echo "File not found: ${USER}/${line}";dirflg=0; fi #:ファイルの存在確認 if [ ! -f "${PASS}/${line}.txt" ]; then echo "File not found: ${PASS}/${line}";dirflg=0; fi #:ファイルの存在確認 if [ $dirflg -eq 0 ]; then echo "${line}: Skipped";continue; fi #:どれか一つでも存在しなければ対象をスキップ expect -c " log_user 0 #:Expectの処理内容を出力しない spawn ssh $(<${USER}/${line}.txt)@${line} set i 0 #:ログイン直後のPW試行回数カウント set j 0 #:特権ログインのPW試行回数カウント set flg 0 #:ログインが成否確認用フラグ set timeout 5 expect { \"fingerprint\" { send \"yes\n\" exp_continue } -re \"\[P|p\]assword\" { if {!(\$i < ${CHALLENGE})} { exit } incr i 1 send \"$(<${PASS}/${line}.txt)\n\" exp_continue } \">\" { send \"enable\n\" expect { \"Password\" { if {!(\$j < ${CHALLENGE})} { exit } incr j 1 send \"$(<${ENAPASS}/${line}.txt)\n\" exp_continue } \">\" { if {!(\$j < ${CHALLENGE})} { exit } send \"enable\n\" exp_continue } \"${WAITFLG}\" { set flg 1 send \"ter len 0\n\" } } } \"${WAITFLG}\" { set flg 1 send \"ter len 0\n\" } } if { \$flg == 0 } { send_user \"${line}: Connection failed\n\"; exit } #:ログイン出来なければ処理を終了 send_user \"${line}: running\n\" #:処理中のホストを表示 log_file -a ${LOGDIR}/${line}_$(date +"%y%m%d_%H%M%S").log #:ログの取得開始 set FILE [open \"${CMD}/${line}.txt\" r] while {! [eof \$FILE]} { set cmd [gets \$FILE] expect \"${WAITFLG}\" { send \"\${cmd}\n\" } for { set i 0 } {\$i < 3 } { incr i } { #:ログが見やすいよう改行を入れる expect \"${WAITFLG}\" { send \"\n\" } } } log_file #:ログの取得終了 " done < $HOSTSPATH echo -e "\n\n~~ All Finish ~~\n" #:プログラムが終了したことを表示