BashのShell Scriptで、災害による配送状況を監視する

[OPENLOGI Advent Calendar 2020](https://qiita.com/advent-calendar/2020/openlogi) の12月16日の記事です。

オープンロジは、物流フルフィルメントプラットフォームサービスを提供しており、災害時には荷物が問題なく配送できるかを知る必要があるため、配送会社の状況をチェックする必要があります。
今年は、コロナをはじめ、大雨など、オープンロジでもかなりの影響があり、多くの荷物が配送出来ない状態となりました。

12月になった現在でも、残念ながら、7月の豪雨の影響で未だに配送できない地域が存在しています。

- [佐川 - 令和2年7月豪雨の影響について（2020年11月30日 9時現在）](https://www2.sagawa-exp.co.jp/information/detail/163/)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57633/ed64bbdd-3da2-1218-010d-eb66e8b08c73.png" width="300">

- [お荷物の集配および宅急便センターの営業状況について](https://www.kuronekoyamato.co.jp/ytc/chien/chien_hp.html)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57633/95b4160d-962d-3e9c-4cc8-1ca1e49c5bbd.png" width="300">



例年は、短い期間での対応だったため、あまり問題になりませんでしたが、今年は長期化したため、監視を自動化することにしました。

今回は1〜2時間もあれば実装できるBashのShell Scriptで監視の自動化を行います。

## 要件
まず要件を整理します。

- 配送会社の災害情報は、同じWebページ上で更新される場合があるため、RSSでの検知が難しい
- 更新日のみの変更がされ、実際に内容は変わっていないこともあるため、前回取得したページとの差分だけが知りたい
- 結果をSlackに通知したい
- 定期的に実行したい

シンプルではあるのですが、コンテンツの管理や差分など、結構手間のかかる処理が多くなっています。
でも、Linuxのコマンドならなんとかなりそうです:smile:

## スクリプト
要点は後ほど解説していきます。

```bash
#!/bin/bash
rootdir=/home/user_name
channel="#channel-example"

# 佐川（大雨）
sagawadir=$rootdir/ops/disaster/sagawa
mkdir -p $sagawadir
curl https://www2.sagawa-exp.co.jp/information/detail/163/ | xmllint --xpath "//section[contains(@class, 'ss_replace')]" --html - 2>/dev/null | lynx -dump -stdin > $sagawadir/$(date '+%Y%m%d%H%M%S').html
sagawadiff=$(diff -u $(ls -trd1 $sagawadir/* | tail -n 2))

message=""

declare -A map;
map=(
  ["佐川（大雨）"]="$sagawadiff"
)

for carrier in ${!map[@]};
do
  if [ -z "${map[$carrier]}" ]; then
    continue
  fi
  message="$message\n# *$carrier*\n\`\`\`\n${map[$carrier]}\n\`\`\`"
done

if [ -z "$message" ]; then
  message="配送停止地域の更新はありません！"
else
  message="<!here> 配送停止地域の更新があります！確認してください！\n$message"
fi

# 開発中は、echoでメッセージ内容を表示させる
# echo -e "$message"
curl -X POST 'https://slack.com/api/chat.postMessage' -d 'token=SLACK_BOT_TOKEN' -d "channel=$channel" --data-urlencode "text=$(echo -e "$message")"
```

## 解説
そんなに難しいコードではありませんが、要点となる箇所だけ解説したいと思います。

```bash
#!/bin/bash
```

1行目のshebangの定義です。Shellではなく、Bashを指定します。
これは、Mapが使いたかったため、Bashを使用することにしました。
実際に、このスクリプトで、複数のWebページを監視していたため、Mapで管理できるようにしています。

```bash
curl https://www2.sagawa-exp.co.jp/information/detail/163/ | xmllint --xpath "//section[contains(@class, 'ss_replace')]" --html - 2>/dev/null | lynx -dump -stdin > $sagawadir/$(date '+%Y%m%d%H%M%S').html
```

8行目の1行で下記のことを行っています。

1. `curl`でHTMLコンテンツを取得
2. `xmllint` のxpathで指定の要素を取得
3. テキストベースのウェブブラウザである`lynx`で、レイアウトを整形
4. ファイルとして保存

特徴としては、`linx`を使用して、タグを除去しつつ、HTMLのレイアウトに整形している点です。
これによって、差分を取得する際に、きれいなレイアウトで比較できるようになっています。

```bash

sagawadiff=$(diff -u $(ls -trd1 $sagawadir/* | tail -n 2))
```

9行目は、少し複雑です。
まず、`ls`で日付の昇順にソートし、横ではなく、縦のリストにします。

![2020-12-15_20-55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57633/9670e768-8ed4-9285-e453-56d61a880b0f.png)

`tail`を使用して、リストの末尾２件だけになるようにし、比較したい最新のファイルとして取得します。

![2020-12-15_20-57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57633/a49200b3-4ecc-af28-920b-8c6b8566e413.png)


取得した2件のファイルを、`diff`のunifiled形式を使用して、Gitでお馴染みのdiff形式で表示するようにしています。
Shell Scriptならこういう処理もワンライナーで処理できるので便利です。

![2020-12-15_21-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57633/021069af-e5ec-e34b-8595-689082889e13.png)

```
declare -A map;
map=(
  ["佐川（大雨）"]="$sagawadiff"
)
```

13行目の上記で、Bashにした理由のMapが出てきます。
Mapを使用することにより、複数のdiffの結果を一括で通知できるようにします。
今回は１件だけの例ですが実際には複数のWebページの監視をし、通知をまとめるようにしています。

```bash
curl -X POST 'https://slack.com/api/chat.postMessage' -d "token=SLACK_BOT_TOKEN" -d "channel=$channel" --data-urlencode "text=$(echo -e "$message")"
```

最後の行で、curlを使用してSlackに通知しています。
textのパラメータを指定する際には、`--data-urlencode`を使用しましょう。
`--data-urlencode`を使用しない場合、一部の記号がテキストとして認識されません。

Slack BotやTOKENなどの作成方法は、検索すればかなりの数がヒットするため、今回の記事では特に触れません。
Bot名は、実行されるサーバのホスト名を含んで、`hostname-cron`としておくと、どこで実行されているのかわかるのでおすすめです。
また、Botにした場合は、チャンネルへの招待を忘れずにしましょう。

## 通知時の結果

今回のスクリプトを使用すると下記のような形で、Slackに通知することができるようになります。

![2020-12-15_22-59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57633/d8258e57-cef6-6fc7-20bc-c1884af5f14b.png)

## `cron`での設定
最後は`cron`で定期実行すれば、定期的に監視・通知できる仕組みの完成です。

```bash
30 9,19 * * * /bin/bash /home/user_name/ops/disaster/disaster-monitor.sh
```

## まとめ
Bashを使用したShell Scriptでも簡単に監視の仕組みを作ることができました。

もしかしたら、JavaScriptでも簡単に出来るのかなーと思いつつも、レイアウトの整形などは難しそうな気がするので、BashやShellのスクリプトの方がメリットがある場合も多いかもしれないですね。
ただ、BashやShellは、高水準な言語ではないので、できるだけ小さいプログラムにしておきたいとは思います。