【シェル芸人への道】dateコマンドと親しむ

# **dateコマンド**

何気なく打っている `date` コマンド。
こいつで少し遊んでみた。

# **環境**

```
[root@sandbox ~]# cat /etc/centos-release
CentOS release 6.7 (Final)

[root@sandbox ~]# bash --version
GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
```

# **基本**

```
[root@controller ~]# date
Tue Feb  2 16:41:59 GMT 2016
```

`date` コマンド。現在の日付が出ます。
明日や昨日も出せます。

```
[root@sandbox ~]# date --date="1 days"
Wed Feb  3 17:31:35 GMT 2016

[root@sandbox ~]# date --date="1 days ago"
Mon Feb  1 17:31:40 GMT 2016
```

え、英語が読めない？ ~~うるせぇ、黙ってろ。~~
`date` コマンドのデフォルト出力は環境変数で指定されているロケールに従います。
ロケールの確認には `locale` コマンド。

```
[root@sandbox ~]# locale
LANG=en_GB.UTF-8
LC_CTYPE="en_GB.UTF-8"
LC_NUMERIC="en_GB.UTF-8"
LC_TIME="en_GB.UTF-8"
LC_COLLATE="en_GB.UTF-8"
LC_MONETARY="en_GB.UTF-8"
LC_MESSAGES="en_GB.UTF-8"
LC_PAPER="en_GB.UTF-8"
LC_NAME="en_GB.UTF-8"
LC_ADDRESS="en_GB.UTF-8"
LC_TELEPHONE="en_GB.UTF-8"
LC_MEASUREMENT="en_GB.UTF-8"
LC_IDENTIFICATION="en_GB.UTF-8"
LC_ALL=
```

いろいろありますが、一旦 `date` コマンドということであれば **`LANG=en_GB.UTF-8`** に注目です。
こいつを変えてもう一度 `date` コマンドを打ってみましょう。

```
[root@sandbox ~]# LANG="ja_JP.UTF-8"
[root@sandbox ~]# date
2016年  2月  2日 火曜日 16:50:39 GMT
```

え、次はフォーマットが気に入らない？

```
[root@sandbox ~]# date +"%Y/%m/%d %H:%M:%S"
2016/02/02 16:55:07
```

え、0埋めが邪魔？

```
[root@sandbox ~]# date +"%Y/%-m/%-d %H:%M:%S"
2016/2/2 16:56:48
```

よし。

# **小ネタ**

## **月末日かどうか判定する**

月末処理を書く都合で、月末日かどうか判定する必要がありました。

```
[root@sandbox ~]# date --date="1 days" +"%-d"
3
```

出力が1なら月末日です。
明日が1日なら今日は月末日。

## **先月末の日を取得する**

```
[root@sandbox ~]# date +"%Y/%m/%d"
2016/02/02

[root@sandbox ~]# date --date="$(date +"%d") days ago" +"%Y/%m/%d"
2016/01/31
```

2月2日から2日戻ったら先月末。
月末処理をするバッチが遅延起動するケースを考えてこのコマンドを考えた記憶があります。

## **直近1年の素数日を取得する**

```
[root@sandbox ~]# for i in {1..365} ; do date +"%Y%m%d" -d "${i} day" | factor | awk '{ if(NF==2) print $2 }' ; done | sort -n | sed -e "s/^\(....\)\(..\)\(..\)$/\1年\2月\3日/g"
2017年01月21日
2017年02月19日
2017年02月23日
...
```

何を言っているのかわからないかもしれませんが、ムショーに調べたくなったので書いてみました。
プログラミング言語で素数判定する場合、Mathクラスなんかで `num.isPrime` とかやると `true/false` 判定できたりしますが、シェル(芸界)では

```
[root@sandbox ~]# echo 4 | factor | awk '{ if(NF==2) print $2 }'
[root@sandbox ~]# echo 3 | factor | awk '{ if(NF==2) print $2 }'
3
```

などとすることで判定できたりします。素敵ですね。

なお、年号を含まない場合はこちらで。

```
[root@sandbox ~]# for i in {1..365} ; do date +"%m%d" -d "${i} day" | factor | awk '{ if(NF==2) print $2 }'; done | sort -n | sed -e "s/^\([0-9]\{3\}\)$/0\1/g" -e "s/^\(..\)\(..\)$/\1月\2日/g"
01月01日
01月03日
01月07日
...
```

うるう年の2月29日、229は素数なのですが直近1年に含まれていないと出てきません。残念ですね。

## **直近5分以内にあるerrorログを表示**

`/var/log/messages` 内に `error` という文字列を含むログが直近5分以内にあるかどうかを知りたいです。
**ワンライナー** で。
ちなみに `/var/log/messages` の日付フォーマットは `Feb 2 16:51:42` のような感じです。

```
[root@sandbox ~]# IFSBK=${IFS} ; IFS=$'\n' ; for record in $(cat /var/log/messages ) ; do if [ $(( $(date +"%s") - 300 )) -lt $(echo ${record} | cut -d" " -f 1,2,3 | date --date="$(cat -)" +"%s") ] ; then echo ${record
} ; fi ; done | grep error ; IFS=${IFSBK}
```

大体こんな流れで動きます。

1. for文がスペース等に惑わされないようデリミタ(IFS)を改行(\n)に設定
2. 対象ファイルのレコードをfor文でまわす
3. 現在時刻をUNIX時間(秒)で出力し、300秒(5分)前を計算
4. 各レコードの第3カラムまでを抜き出し、dateコマンドでUNIX時間(秒)に変換
5. 現在時刻 - 300秒のほうが小さければ、レコード全体を出力する
6. 最後に全体をerrorでgrepする
7. デリミタを元に戻す

ログから抜き出した日付を `date` コマンドで整形して比較可能なUNIX時間に変換するのが大事なんですが、このときの `--date` オプションの有能さに救われました。

このコマンドをやりたいがためにいろいろ調べた結果をまとめたのがこの記事なのでした。おわり。