小ネタ集

この投稿は [作ったアプリ自慢！ Advent Calendar 2016](http://qiita.com/advent-calendar/2016/application-jiman)の17日目の記事です。

自宅サーバーを管理するときなどに作ったツール類をいくつか紹介します…と意気込んでは見たものの、細部は忘れてるわ、用途も内容もどこを説明すべきか悩ましくなってくるわで…

それでも公開していれば誰かの役に立つことももしかしたらあるかもしれませんよね。興味があればコメントなどください。ニーズがあればメンテナンスします。

## システムログで、すべてのログを見たい

システムログは、logrotateという機能により、古いログは1、2、…という数字のついたファイルに分割され、
さらに古いファイルはgz形式で圧縮されます。
古い分も含めてログを一括で見たい場合に、次のシェルスクリプトlogcatを作りました。

```Bash:logcat
#!/bin/sh
# logcat
#   指定したログを、logrotateした分も含めてすべて出力
#
gunzip -c $1.[0-9]* | cat - $1.[0-9]* $1 | sort
```

Ubuntu Linuxでのdpkgの履歴は、このlogcatを使った次のコマンドで確認できます。

```Bash:dpkg-history
#!/bin/sh
#
# dpkg-history
#   dpkgによるINSTALL/REMOVE/UPGRADE/PURGEの履歴を表示する
# 依存: logcat
#
logcat /var/log/dpkg.log | grep '^[12][09][0-9][0-9]-[01][0-9]-[0-3][0-9] [0-2][0-9]:[0-5][0-9]:[0-5][0-9] \(install\)\|\(remove\)\|\(upgrade\)\|\(purge\) '
```

## システムクロックの確認

以前、自宅サーバーを立てようとしたときに、時計が狂うらしいことに気がつきました。
実際にどの程度狂うのか。`ntpdate`を使って調べるのが次のコマンドです。
`ntpdate`を間隔を置いて実行するため、1回の実行に1分程度時間がかかります。

```Bash:check-clock
#!/bin/sh
#
# ntpdateでNTPサーバーへ問い合わせ、システム時計のズレをチェックする
# 引数1で指定された回数チェック。指定がないときは6回

NTPDATE=ntpdate
SED=sed

# 問い合わせ回数の初期値
D=6;

# 問い合わせの間隔。秒単位
INT=10;

# NTPサーバー
SERVER=ntp.nict.jp

if test -z $1; then
    N=$D
else
    N=$1
fi
for i in $(seq 1 $N); do
    if test $i -gt 1; then
        sleep ${INT}
    fi
    ${NTPDATE} -q -4 ${SERVER}  | ${SED} -n -e "/adjust time server/s/.*offset //p"
done
```

## バックアップスクリプト

バックアップは重要です。ということで、dumpコマンドを使ったバックアップスクリプト。
バックアップ用のHDDをバックアップの実行時だけマウントし、バックアップが終わったらアンマウントするようにしています。
rootユーザーが使えるように、このファイルは`/usr/local/bin`に配置しています。

```bash:backup-dump
#!/bin/sh
#
# backup-dump
# バックアップファイルの名前を自動生成し、バックアップする。
# 前回までの同レベル以上のバックアップファイルはサブディレクトリーへ移動する
#     引数1: バックアップ対象ディレクトリー
#     引数2: レベル
#     引数3: バックアップ先ディレクトリー
# Usage:
#     backup-dump /mnt/backup /home 0    # /homeをレベル0で/mnt/backupディレクトリーへバックアップ（フルバックアップ）
#     backup-dump /mnt/backup / 1        # /をレベル1で/mnt/backupディレクトリーへバックアップ

CUT=/usr/bin/cut
DATE=/bin/date
DATEFLAG='+%F'
DUMP=/sbin/dump
ECHO=/bin/echo
EXPR=/usr/bin/expr
GZIP=/bin/gzip
HOSTNAME=/bin/hostname
HOSTNAMEFLAG=-s
MKDIR=/bin/mkdir
MOUNT=/bin/mount
MOUNTFLAGS=-v
MOUNTPOINT=/bin/mountpoint
MV=/bin/mv
MVFLAG=-vb
RM=/bin/rm
RMFLAG=-vf
SED=/bin/sed
UMOUNT=/bin/umount
UMOUNTFLAGS=-v
TR=tr
SEQ=seq

${ECHO} "Backup-dump: Start"
# 引数1をチェック
# 実在のディレクトリーではない場合はメッセージを表示して終了
if ! test -d $1; then
    ${ECHO} "Invalid 1st argument. Specify directory." >&2
    exit 1
fi
# 末尾に付く/を削除し、バックアップ先ディレクトリーを設定
TODIR=$(${ECHO} $1 | ${SED} -e "s|/*$||")

# 引数2をチェック
# ない場合は、/がバックアップ対象
if test -z $2 -o "$2" = '/'; then
    TARGET='/'
else
    # 実在のディレクトリーではない場合はメッセージを表示して終了
    if ! test -d $2; then
        ${ECHO} "Invalid 2nd argument. Specify backup target directory." >&2
        exit 1
    fi
    # 末尾に付く/を削除し、バックアップ対象ディレクトリーを設定
    TARGET=$(${ECHO} $2 | ${SED} -e "s|/*$||")
fi
# バックアップ対象の名前。
# /（ルートディレクトリー）の場合は、「root」
if test $TARGET = '/'; then
    TARGETNAME=root
# /rootの場合は、「_root」
elif test $TARGET = '/root'; then
    TARGETNAME=_root
else
    # /とroot以外の場合は、先頭から/を削除しそれ以外の/を_に置換した名前
    # 例: 「/home」の場合、「home」
    #     「/user/local」の場合、「usr_local」
    TARGETNAME=$(${ECHO} $TARGET | ${SED} -e "s|^/||" -e "s|/|_|g")
fi

# 引数3をチェック
# ない場合は、レベル0
if test -z $3; then
    LEVEL=0
else
    # 0以上9以下の数値ではない場合はメッセージを表示して終了
    if ! test $3 -ge 0 -a $3 -le 9; then
        ${ECHO} "Invalid 3rd argument. Specify backup level [0-9]." >&2
        exit 1
    fi
    LEVEL=$3
fi

# マウント
${MOUNTPOINT} ${TODIR} || ${MOUNT} ${MOUNTFLAGS} ${TODIR}

# バックアップ先ファイル名の元
TOBASE=${TODIR}/$(${HOSTNAME} ${HOSTNAMEFLAG})-${TARGETNAME}-

# バックアップ先ファイルのフルパス
TO=${TOBASE}${LEVEL}.dump

# 前回までの同レベル以上のバックアップファイルとディレクトリー
PREVFILES=${TOBASE}[$($SEQ ${LEVEL} 9 | $TR -d 'n')].dump*
PREVDIRS=${TOBASE}[$($SEQ $($EXPR ${LEVEL} + 1) 9 | $TR -d 'n')]_*

# 前回までの同レベル以上のバックアップファイルがある場合、
# 新ディレクトリーを作成してバックアップファイルを移動
if test -e $($ECHO ${PREVFILES} | $CUT -d" " -f1) -o -e $($ECHO ${PREVDIRS} | $CUT -d" " -f1); then
    # ディレクトリー名設定
    # 末尾に数字を付け、既存ディレクトリーとの重複を回避
    NEWPREVDIR=${PREVDIRBASE}
    N=1
    while test -e ${NEWPREVDIR}; do
        NEWPREVDIR=${PREVDIRBASE}_${N}
        N=$(expr ${N} + 1)
    done
    #ディレクトリー作成
    ${MKDIR} ${NEWPREVDIR}
    # 移動
    ${ECHO} "Moving previous backup files to ${NEWPREVDIR}"
    ${MV} ${MVFLAG} ${PREVFILES} ${PREVDIRS} ${NEWPREVDIR}/
fi

# dumpコマンド実行
${ECHO} "Backup to ${TO} by dump: TARGET=${TARGET} LEVEL=${LEVEL}"
DUMPCMD="${DUMP} -${LEVEL}uf ${DUMPFLAG} ${TO} ${TARGET}"
echo ${DUMPCMD}
${DUMPCMD}

# レベル0の場合、前回までのバックアップファイルを削除
if test $? -eq 0; then
    ${RM} -r ${RMFLAG} ${TOBASE}${LEVEL}_*
fi

# アンマウント
${UMOUNT} ${UMOUNTFLAGS} ${TODIR}

# バックアップ終了のメッセージ
${ECHO} "Backup-dump: End"
```

このコマンドを呼び出すため、rootユーザーのcrontabを次のように設定しています。
`/media/backup`がバックアップディスクのマウント先。
実行結果は、システムログに出力されるようにしています。

```Bash:crontab
# Backup
30 04 * * 1 /usr/local/bin/backup-dump /media/backup /home 0 2>&1 | /usr/bin/logger
30 04 * * 2 /usr/local/bin/backup-dump /media/backup / 0 2>&1 | /usr/bin/logger
30 04 * * 0,3-6 /usr/local/bin/backup-dump /media/backup /home 1 2>&1 | /usr/bin/logger
```