前書き
ShellScript (bash & expect) で機器から情報収集をするってよくやりますよね。
RANCID などのツールを使ってちゃんと管理するのが理想的ではありますが、ちょっとした作業なんかで私は時々スクリプトを書いています。
で、作るたびにゼロから作っている気がしたのでメモがてら投稿。
スクリプト
get-config.sh
#!/usr/bin/env bash
###############################################
# 2017-10-03 とりあえず作成
# 2017-10-18 説明を少し修正・追記
# 2017-12-29 col コマンドによる改行コード削除を追加
#
###############################################
# memo
# ・連想配列使ってるので bash 4 系必須
# ・ホスト名の大文字小文字の違いは無視
#
function usage_exit() {
cat <<_EOT_
Usage:
$0 [-h] [-d] [-n] [-l]
Description:
ネットワーク機器へログインして情報を収集する
Options:
-d debug mode on (未使用)
-c no confirm before connecting to devices
-l no check line number of output log files
-h help
_EOT_
exit 1
}
# If there are 0 arguments --> error
# if [ $# = 0 ]; then
# usage_exit
# exit 1
# fi
# フラグのデフォルト値設定
FLAG_DEBUG=off
FLAG_CONFIRM=on
FLAG_LOG_CHECK=on
# 引数処理
if [ "$OPTIND" = 1 ]; then
while getopts cdhl OPT
do
case $OPT in
c)
FLAG_CONFIRM="off"
echo ">>> confirmation OFF <<<"
;;
d)
FLAG_DEBUG="on"
echo ">>> debug mode ON <<<"
;;
l)
FLAG_LOG_CHECK="off"
echo ">>> log check OFF <<<"
;;
h)
usage_exit
;;
\?)
usage_exit
;;
esac
done
else
echo "No installed getopts-command." 1>&2
exit 1
fi
shift $((OPTIND - 1))
####################################################################
# check for $FLAG_CONFIRM
function check_flag_confirm(){
echo ""
echo ">>> ----------------------------------------------------------------"
echo ">>> Target --> host: ${1} ( IP: ${2} )"
echo ">>> Continue?"
echo ">>> YES --> just push return key"
echo ">>> NO --> exit & return key"
echo -n ">>> Please enter : "
read line
echo ""
case ${line} in
[eE][xX][iI][tT])
exit
;;
'')
echo ">>> go to next..."
;;
* )
echo ">>> input error..."
exit 1
;;
esac
}
####################################################################
# Type C1 : cisco, telnet, no login username
function get_from_type_c1(){
declare -A hosts
hosts["xxxx-sw01"]="192.168.1.250"
hosts["xxxx-sw02"]="192.168.1.251"
login_pass="xxxx"
enable_pass="xxxx"
for host_name in ${!hosts[@]}; do
host_ip=${hosts[${host_name}]}
if [ "$FLAG_CONFIRM" = "on" ]; then
check_flag_confirm ${host_name} ${host_ip}
fi
expect -c "
set timeout 5
spawn telnet ${host_ip}
expect -nocase \"Password:\" {
send \"${login_pass}\n\"
expect -nocase \"${host_name}>\" {
send \"ter len 0\n\"
expect -nocase \"${host_name}>\"
send \"enable\n\"
expect -nocase \"Password:\"
send \"${enable_pass}\n\"
expect -nocase \"${host_name}#\"
send \"show inventory\n\"
expect -nocase \"${host_name}#\"
}
send \"exit\n\"
}
expect eof
" | tee ${log_dir}/${current_time}/${host_name}.tmp
# エスケープ文字列等々を削除したファイル(.log)作成
col -bx < "${log_dir}/${current_time}/${host_name}.tmp" > "${log_dir}/${current_time}/${host_name}.log"
# 一時ファイル(.tmp)削除
rm "${log_dir}/${current_time}/${host_name}.tmp"
done
}
####################################################################
# Type V
# --> vyos, ssh,
function get_from_type_v1){
declare -A hosts
hosts["vyos117-01"]="192.168.83.254"
hosts["vyos117-02"]="192.168.83.253"
login_user="vyos"
login_pass="vyos"
for host_name in ${!hosts[@]}; do
host_ip=${hosts[${host_name}]}
if [ "$FLAG_CONFIRM" = "on" ]; then
check_flag_confirm ${host_name} ${host_ip}
fi
expect -c "
set timeout 5
spawn ssh -l ${login_user} ${host_ip}
expect -nocase \"Are you sure you want to continue connecting (yes/no)?\" {
send \"yes\n\"
expect -nocase \"password:\"
send \"${login_pass}\n\"
} -nocase \"password:\" {
send \"${login_pass}\n\"
}
expect -nocase \"${host_name}:\" {
send \"show configuration | no-more \n\"
expect -nocase \"${host_name}:\"
}
send \"exit\n\"
expect eof
" | tee ${log_dir}/${current_time}/${host_name}.tmp
# エスケープ文字列等々を削除したファイル(.log)作成
col -bx < "${log_dir}/${current_time}/${host_name}.tmp" > "${log_dir}/${current_time}/${host_name}.log"
# 一時ファイル(.tmp)削除
rm "${log_dir}/${current_time}/${host_name}.tmp"
done
}
####################################################################
# ログの行数確認をする際の閾値
min_line_num="10"
# ログを保存するディレクトリパスを指定
log_dir="/tmp/config"
current_time=`date +%Y%m%d_%H%M%S`
# ログを保存するディレクトリを作成
if [ ! -d ${log_dir}/${current_time} ]; then
mkdir -p ${log_dir}/${current_time}
chmod 775 ${log_dir}/${current_time}
fi
# 実際に機器へログインする関数呼び出し
get_from_type_c1
get_from_type_v1
# $FLAG_LOG_CHECK が ON の場合は、取得したログの行数を確認する
# $min_line_num 以下の場合はエラーとして、ファイル名を表示する
if [ "$FLAG_LOG_CHECK" = "on" ]; then
echo ""
echo "----------------------------------------------------------------"
echo ">>> Check the line number of output files."
echo ">>> Looking for files with ${min_line_num} lines or less"
for file in `ls -1 ${log_dir}/${current_time}/`; do
line_num=`wc -l ${log_dir}/${current_time}/${file} | awk '{print $1}'`
if [ ${line_num} -lt ${min_line_num} ]; then
echo "${file}"
fi
done
fi
説明
多分作るときに迷う部分って、expect の条件分岐っぽいところだと思うんですよね。
なのでそこの説明を多少してみる。
get_from_type_c1
の部分
expect -c "
set timeout 5
spawn telnet ${host_ip}
expect -nocase \"Password:\" {
send \"${login_pass}\n\" <-- ログインパスワードを入力して
expect -nocase \"${host_name}>\" { <-- 意図したプロンプト(ホスト名>) が表示されなければ
send \"ter len 0\n\"
expect -nocase \"${host_name}>\"
send \"enable\n\"
expect -nocase \"Password:\"
send \"${enable_pass}\n\"
expect -nocase \"${host_name}#\"
send \"show inventory\n\"
expect -nocase \"${host_name}#\"
}
send \"exit\n\" <-- exit する
}
expect eof
" | tee ${log_dir}/${current_time}/${host_name}.log <-- tee を使って、スクリプトを実行した画面に表示しつつファイルへ出力を保存している
上記説明の、間の部分は特に問題ないですよね。
enable して show コマンド叩いてるだけだし。
get_from_type_v1
の部分
expect -c "
set timeout 5
spawn ssh -l ${login_user} ${host_ip}
expect -nocase \"Are you sure you want to continue connecting (yes/no)?\" { <-- ssh 初回ログインのアレが
send \"yes\n\" <-- でたら、yes 入力して
expect -nocase \"password:\"
send \"${login_pass}\n\" <-- パスワードを入力
} expect -nocase \"password:\" { <-- アレがでなければ、パスワードを入力
send \"${login_pass}\n\"
}
expect -nocase \"${host_name}:\" { <-- 意図したプロンプト(ホスト名:)が表示されなければ
send \"show configuration | no-more \n\"
expect -nocase \"${host_name}:\"
}
send \"exit\n\" <-- exit する
expect eof
" | tee ${log_dir}/${current_time}/${host_name}.log
上記説明の、間の部分は show コマンド叩いてるだけ