シェルスクリプト (bash) で定期的に ping モニタリング

# 前書き
何か作業するときに、監視システム以外から ping で確認したりしますよね。
Windows だと ExPing 使ったり。
そんな感じのことをシェルスクリプトでやりたいな、と思ったので書いてみた。



# スクリプト
```bash:alive-monitor.sh
#!/usr/bin/env bash
###############################################
# 2017-10-08 とりあえず作成（MAC用）
# 2017-10-22 出力ログファイルへ実行スクリプト名（$0）を追加
#              --> 異なる ping 対象を記載したスクリプトファイルを複数配置しておいても対応可能に
#            OS 自動判別追加（動作確認済み：MAC, CentOS6）
# 2017-10-23 cygwin で動作するように修正（動作確認済み：MAC, CentOS6 & 7, cygwin）
# 2017-10-29 Ctrl + c での終了時に、ログディレクトリを表示
#
###############################################
# memo
# ・連想配列使ってるので bash 4 系必須
# ・動作概要
#   - 2秒でタイムアウトする ping を打って、tmp_xx.xx.xx.xx.txt ファイルへ記載する（毎回上書き）
#   - ping 実行する関数は & を付けて実行することにより、連想配列の ping 対象に一気に実行をかける
#   - sleep で、ping の実行間隔を調整しつつ、ping の結果が txt へ記載されるのを待つ
#   - sleep 後、ping の実行結果（tmp_xx.xx.xx.xx.txt）を全て cat で閲覧
#   - というわけで、ping の -W と sleep の時間に注意が必要
#   - Ctrl + c で終了させる

function usage_exit() {
cat <<_EOT_
Usage:
  $0 [-h] [-d] [-l]

Description:
  定期定期に対象へ ping を打ってモニタリングする

Options:
  -d  debug mode ON (未使用)
  -l  log OFF
  -h  help

_EOT_
exit 1
}

# If there are 0 arguments -->  error
# if [ $# = 0 ]; then
#   usage_exit
#   exit 1
# fi

# フラグの初期値設定
FLAG_DEBUG=off
FLAG_LOG=on

# 引数処理
if [ "$OPTIND" = 1 ]; then
    while getopts dhl OPT
    do
        case $OPT in
            d)
                FLAG_DEBUG="on"
                echo ">>> debug mode ON <<<"
                ;;
            l)
                FLAG_LOG="off"
                echo ">>> log OFF <<<"
                ;;
            h)
                usage_exit
                ;;
            \?)
                usage_exit
                ;;
        esac
    done
else
    echo "No installed getopts-command." 1>&2
    exit 1
fi
shift $((OPTIND - 1))

######################################
# 文字コード変換関数 for cygwin
function wincmd() {
    CMD=$1
    shift
    $CMD $* 2>&1 | iconv -f cp932 -t utf-8
}

######################################
function exec_ping(){
    ip=${1}
    name=${2}
    OS_type=${3}
    script_name=${4}
    outut_file_name=tmp_${script_name}_${ip}.log

    # OS の種類で ping のオプション、awk で引っ掛ける＆表示する文字列を調整
    if [ "$OS_type" = "mac" ]; then
        # MAC の場合 -w の単位は msec
        result=`ping -c 1 -W 2000 ${ip} | awk '/ttl/{print $6 " " $7}'`
    elif [ "$OS_type" = "cygwin" ]; then
        result=`${PING} -n 1 -w 2000 ${ip} | awk '/TTL/{print $5 $6 " " $7}'`
    elif [ "$OS_type" = "linux" ]; then
        result=`ping -c 1 -w 2 ${ip} | awk '/ttl/{print $6 " " $7}'`
    else
        echo ">>> Error : OS が判別できないため ping が実行できませんでした"
        exit
    fi

    if [ "${result}" != "" ]; then
        echo `date +%Y-%m-%d_%H:%M:%S` ": ${ip} ${name} ${result}" > ${OUTPUT_DIR}/${outut_file_name}
    else
        echo `date +%Y-%m-%d_%H:%M:%S` ": ${ip} ${name} NG!!" > ${OUTPUT_DIR}/${outut_file_name}
    fi
}

######################################

# OS 判定
# 本当は、OS の種別ではなく ping の種類の判別が一番正しいんだろうけど。。。
#OS_type=`uname | egrep -i "darwin|cygwin|linux"`
#echo ">>> OS type (uname) : ${OS_type}"
#if [ ${OS_type} = "" ]; then
#    echo ">>> Your platform (`uname`) is not supported!! :p"
#    exit
#fi

if [ `uname | grep -i cygwin` ]; then
    OS_type='cygwin'
elif [ `uname | grep -i darwin` ]; then
    OS_type='mac'
elif [ `uname | grep -i linux` ]; then
    OS_type='linux'
else
    echo ">>> Your platform (`uname`) is not supported!! :p"
    exit 1
fi
echo ">>> OS type : ${OS_type}"

# 実行スクリプト名取得（出力ログファイルで利用）
# script_name=`echo $0 | sed -e "s/^\.\///g" -e "s/\.sh$//g"`
# cygwin は $0 が full path になっていたので修正（メモとして上記を残す）
script_name=`echo $0 | awk -F'/' '{print $NF}' | sed -e "s/\.sh$//g"`

# 出力ディレクトリ
OUTPUT_DIR="/tmp/alive-monnitor"
EXEC_TIME=`date +%Y%m%d_%H%M%S`

# cygwin 用 実行 ping コマンド
PING='wincmd /cygdrive/c/Windows/System32/PING.exe'

# ping 実行間隔（秒数）
INTERVAL=5

# モニタリング対象の連想配列
declare -A ips
ips["8.8.8.8"]="GoogleDNS01"
ips["8.8.4.4"]="GoogleDNS02"

# ログ出力ディレクトリ作成
if [ ! -d ${OUTPUT_DIR} ]; then
    mkdir -p ${OUTPUT_DIR}
else
    # すでにディレクトリが存在していた場合は、不要ファイル（tmp_${script_name}_*）を削除
    rm ${OUTPUT_DIR}/tmp_${script_name}_* > /dev/null 2>&1
fi

echo ">>> log directory : ${OUTPUT_DIR}/"
echo " ----- "

trap '
    echo ""
    echo ""
    echo ">>> log directory : ${OUTPUT_DIR}/"
    exit 0
' 2

# Ctrl + c で終了させられるまで無限ループ
while : ; do
    # 連想配列でループ処理
    for ip in  ${!ips[@]}; do
        name=${ips[${ip}]}
        exec_ping ${ip} ${name} ${OS_type} ${script_name} &
    done

    # ping 実行間隔調整用 sleep
    sleep ${INTERVAL}

    # $FLAG_LOG が ON の場合は、tee で標準出力とファイル両方へ出力。OFF の場合は標準出力のみ
    if [ "$FLAG_LOG" = "on" ]; then
        cat ${OUTPUT_DIR}/tmp_${script_name}_* | column -t | tee -a ${OUTPUT_DIR}/result_${script_name}_${EXEC_TIME}.log
        echo " ----- " | tee -a ${OUTPUT_DIR}/result_${script_name}_${EXEC_TIME}.log
    else
        cat ${OUTPUT_DIR}/tmp_${script_name}_* | column -t
        echo " ----- "
    fi
    chmod 666 ${OUTPUT_DIR}/*
done
```

# 実行例
```
$ ./alive-monitor.sh 
>>> OS type : mac
>>> log directory : /tmp/alive-monnitor/
 ----- 
2017-10-29_19:50:06  :  8.8.4.4  GoogleDNS02  ttl=53  time=50.349
2017-10-29_19:50:06  :  8.8.8.8  GoogleDNS01  ttl=53  time=61.549
 ----- 
2017-10-29_19:50:11  :  8.8.4.4  GoogleDNS02  ttl=53  time=52.303
2017-10-29_19:50:11  :  8.8.8.8  GoogleDNS01  ttl=53  time=42.530
 ----- 
^C

>>> log directory : /tmp/alive-monnitor/
$
$ ls -1 /tmp/alive-monnitor/
result_alive-monitor_20171029_194604.log
result_alive-monitor_20171029_194743.log
result_alive-monitor_20171029_194858.log
result_alive-monitor_20171029_195006.log
tmp_alive-monitor_8.8.4.4.log
tmp_alive-monitor_8.8.8.8.log
$ 
```