シェルスクリプト、次のステップへ

本連載ではこれまでに、シェル/シェルスクリプトの機能を一通り説明してきた。ここ数回は、実際にシェルスクリプトを組んでログデータを整理する処理のサンプルを紹介している。

シェルスクリプトの作成と言っても、最初はシステムに用意されているコマンドや、パッケージ管理システム経由でインストールしたコマンドを組み合わせるところから始めてみればよいと思う。そうして何度もシェルスクリプトを組んでいくと、特定の業務では似たような処理を繰り返し行っていることに気がつくはずだ。そこまで来たら今度は、記述がよりシンプルになるように専用のコマンドを作成する……というのが次のステップである。

専用のコマンドを作る利点はいくつかある。うまくコマンドを作ることができれば、シェルスクリプトがシンプルで読みやすく、メンテナンスしやすいものになる。また、C言語などでコマンドを組むことができれば、それなりに処理の高速化も期待できる。

今回は、専用コマンドのサンプルをいくつか紹介していく。本連載はプログラミング言語自体の習得に重きを置くものではないのでその辺りの詳細には触れないが、余力があるならぜひ取り組んでみて欲しい。いつもの作業が数秒でサクッと終わるようになるというのは、何とも気持ちのよいものである。

専用コマンド使ったシェルスクリプト

今回も先にサンプルを掲載しておく。まず、毎回生のログデータを加工するのはシェルスクリプトが煩雑になるので、ある程度整理したデータを処理の対象にしておく。ここでは、次のようなデータ(ssh_date)を用いることにする。

% head -20 ssh_date
20190708071859 170.80.226.29 51154 root
20190708071907 170.80.226.29 51158 root
20190708071925 170.80.226.29 51170 admin
20190708071933 170.80.226.29 51176 admin
20190708071946 170.80.226.29 51186 oracle
20190708234029 27.41.190.25 28590 root
20190802012547 36.106.167.197 47710 root
20190802071308 223.241.254.22 38241 root
20190802135313 113.215.223.108 42793 root
20190802150959 180.126.217.189 55921 admin
20190802171011 171.117.148.213 59854 root
20190803054442 27.184.64.119 46802 admin
20190803061317 119.165.192.72 52224 root
20190803074303 149.147.186.20 51968 root
20190803074305 149.147.186.20 51975 service
20190803074308 149.147.186.20 51980 admin
20190803074312 149.147.186.20 51987 root
20190803091634 168.90.143.166 4730 root
20190803154202 149.147.186.20 34962 admin
20190803154204 149.147.186.20 34973 support
%

上記データは、次のような構成になっている。

  • 1列目 日付(年月日時分秒)
  • 2列目 アクセス元IPアドレス
  • 3列目 ポート番号
  • 4列目 ユーザー名


このログデータを処理し、報告用のHTMLファイルを作成する次のようなシェルスクリプト(ssh-check)を用意する。

#!/bin/sh

#=====================================================================
# 利用する変数を定義
#=====================================================================
logfile=ssh_date

#=====================================================================
# 作業用ディレクトリの作成と削除トラップの設定
#=====================================================================
readonly tmpd=/tmp/_$$; mkdir $tmpd
trap "[ -d $tmpd ] && chmod -R u+w $tmpd && rm -r $tmpd" EXIT

#=====================================================================
# 利用するデータを用意
#=====================================================================
# ログデータを取得
cat $logfile                    |
# 1:時刻 2:アクセス元IPアドレス 3:ポート番号 4:ユーザー名
#
retusel 1 1 2 3 4                   |
# 1:時刻 2:時刻 3:アクセス元IPアドレス 4:ポート番号
# 5:ユーザー名
#
# 時刻データフォーマット変換
datefmt 1.%Y%m%d%H%M%S.%Y-%m-%d             \
    2.%Y%m%d%H%M%S.%H:%M:%S             > $tmpd/IPアド
# 1:年月日 2:時分秒 3:アクセス元IPアドレス 4:ポート番号
# 5:ユーザー名

#=====================================================================
# HTMLデータを用意
#=====================================================================
# IPテーブル作成
cat<<EOF                        > $tmpd/行テンプレ
  <tr>
   <td>DATE</td>
   <td>TIME</td>
   <td>IP</td>
   <td>PORT</td>
   <td>USER</td>
  </tr>
EOF

cat<<EOF                        > $tmpd/テーブル
<table border="1">
 <thead>
  <tr>
   <th>日付</th>
   <th>アクセス元IP</th>
  </tr>
 </thead>
 <tbody>
EOF

# アクセス元IPアドレスをHTMLにはめ込み
cat $tmpd/IPアド                  |
ssv1txt -t $tmpd/行テンプレ                \
    1.DATE 2.TIME 3.IP 4.PORT 5.USER        >>$tmpd/テーブル

cat<<EOF                        >>$tmpd/テーブル
 </tbody>
</table>
EOF

#=====================================================================
# HTMLデータ出力
#=====================================================================
cat<<EOF
<!DOCTYPE html>
<html lang="ja">
<head>
 <meta charset="utf-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <title>sshd不正アクセス元IPアドレス一覧</title>
</head>
<body>
EOF

# IPテーブル出力
cat $tmpd/テーブル

cat<<EOF
</body>
</html>
EOF

先に動作を確認しておこう。このシェルスクリプトを実行すると次のような出力を得ることができる。

% ./ssh-check
<!DOCTYPE html>
<html lang="ja">
<head>
 <meta charset="utf-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <title>sshd不正アクセス元IPアドレス一覧</title>
</head>
<body>
<table border="1">
 <thead>
  <tr>
   <th>日付</th>
   <th>時刻</th>
   <th>アクセス元IP</th>
   <th>ポート番号</th>
   <th>ユーザー名</th><Paste>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>2019-07-08</td>
   <td>07:18:59</td>
   <td>170.80.226.29</td>
   <td>51154</td>
   <td>root</td>
  </tr>
  <tr>
   <td>2019-07-08</td>
   <td>07:19:07</td>
   <td>170.80.226.29</td>
   <td>51158</td>
   <td>root</td>
  </tr>
  <tr>
   <td>2019-07-08</td>
   <td>07:19:25</td>
   <td>170.80.226.29</td>
   <td>51170</td>
   <td>admin</td>
  </tr>
  <tr>
   <td>2019-07-08</td>
   <td>07:19:33</td>
   <td>170.80.226.29</td>
   <td>51176</td>
   <td>admin</td>
  </tr>
  <tr>
   <td>2019-07-08</td>
   <td>07:19:46</td>
   <td>170.80.226.29</td>
   <td>51186</td>
   <td>oracle</td>
  </tr>
  <tr>
   <td>2019-07-08</td>
   <td>23:40:29</td>
   <td>27.41.190.25</td>
   <td>28590</td>
   <td>root</td>
  </tr>
...略...
  <tr>
   <td>2019-09-24</td>
   <td>04:52:41</td>
   <td>111.162.224.181</td>
   <td>51398</td>
   <td>root</td>
  </tr>
  <tr>
   <td>2019-09-25</td>
   <td>01:51:37</td>
   <td>116.252.152.98</td>
   <td>47364</td>
   <td>root</td>
  </tr>
  <tr>
   <td>2019-09-25</td>
   <td>07:21:38</td>
   <td>151.233.240.164</td>
   <td>47170</td>
   <td>root</td>
  </tr>
 </tbody>
</table>
</body>
</html>
%

出力されるHTMLデータをファイルに保存してWebブラウザから閲覧すると、次のようになる。

生成されるHTMLデータの閲覧例

慣れればここまで組むのに数十分といったところだ。ツールを使いこなせるようになると、いろいろな作業が自動化できるようになる。

専用コマンドのサンプル

上記シェルスクリプトには、弊社で使っている次のような専用コマンド「retusel」「datefmt」「ssv1txt」が使われている。

  • retusel - 列を選択したり、入れ替えたりするコマンド
  • datefmt - 指定した列の日付フォーマットを変更するコマンド
  • ssv1txt - SSVデータをテキストファイルに組み込むコマンド


使い方は、次のようにヘルプを表示させればわかるようにしてある。

retuselコマンドのヘルプ

では、シェルスクリプトでの使用例をみてみよう。次に示すのは、ログデータからretuselコマンドでデータを取り出している処理の例だ。「awk ‘{print $1,$1,$2,$3,$4}’」と書くのと似ている。retuselコマンドはこうした処理専用に開発してあるため、処理が高速で、範囲指定や簡単な置換処理なども行えるようになっている。

retusel 1 1 2 3 4                   |
# 1:時刻 2:時刻 3:アクセス元IPアドレス 4:ポート番号
# 5:ユーザー名

ログデータの1列目は年月日時分秒データなわけだが、日付と時刻に分けたいので、上記処理で1列目を2列分取り出している。

次に、datefmtコマンドの使用例を示す。このコマンドは、列ごとに指定した日付データを指定したフォーマットへ変換するという処理を行う。日付のフォーマット変更は結構頻繁に発生する処理なので、このようにコマンドで実装しておくと処理が楽になり、さらにシェルスクリプトの見通しもよくなる。

# 時刻データフォーマット変換
datefmt 1.%Y%m%d%H%M%S.%Y-%m-%d             \
    2.%Y%m%d%H%M%S.%H:%M:%S             > $tmpd/IPアド
# 1:年月日 2:時分秒 3:アクセス元IPアドレス 4:ポート番号
# 5:ユーザー名

「1.%Y%m%d%H%M%S.%Y-%m-%d」と記述すると、「1列目は%Y%m%d%H%M%Sというフォーマットのデータで、これを%Y-%m-%dのように変換して出力する」という指定になる。このコマンドは、日付をずらす機能なども持っている。

次に示すのは、整理したSSVデータ(空白区切りデータ)を指定したテキストファイルに組み込む処理だ。「ssv1txt」というコマンドで、SSVデータの行ごとに指定したテンプレートファイルに値をはめ込んでいくことができる。「1.DATE 2.TIME 3.IP 4.PORT 5.USER」という指定で、テンプレートファイルのDATEという文字列をSSVデータの1列目のデータに、TIMEという文字列をSSVデータの2列目のデータに、といった置き換えを行っていくわけだ。SSVデータが100行あれば、行ごとにテンプレートファイルを置換したあとのデータが出力されることになる。

# アクセス元IPアドレスをHTMLにはめ込み
cat $tmpd/IPアド                  |
ssv1txt -t $tmpd/行テンプレ                \
    1.DATE 2.TIME 3.IP 4.PORT 5.USER        >>$tmpd/テーブル

上記はそれぞれは簡単な処理だが、結構汎用的に利用できるものだ。これらコマンドはオープンソースソフトウエアとして公開しているので、もし実装に興味があるならそちらをご覧いただければと思う(daichigoto/tttcmds - GitHub)。

最初はシェル関数やPythonでコマンドにするという手も

上記コマンドはC言語で作成してある。処理を高速化したいのでC言語で作ってあるが、最初は別にシェルスクリプトの関数として実装してもいいだろうし、Pythonで書いてもいいだろうし、自分の慣れているプログラミング言語で作成してみればよいと思う。

普段からコマンドを作ったり、アプリケーションを開発したりしていない人にとって「自分でコマンドを作る」というのはややハードルが高く感じるかもしれない。しかし、業務に特化したコマンドは、作業時間を大きく短縮してくれる。ちょっとばかり苦労してもそれに見合う見返りがあるので、ぜひ挑戦してみていただきたい。