[bash][GNU date]力技で先月の初日と末日を出す方法


2017/12/31 03:30 追記

この投稿の方法で前月の今日、みたいな日付の求め方をしてしまうと、31日まである月や2月のように、正確でない日付が出力されてしまいます。GNU dateでもっと確実に前月を表示する方法は以下の投稿に書きました。


あるシェルスクリプトで先月分のログを抽出しているのですが、毎回毎回 YYYYMMDDと引数を入れるのがかったるい。できればこういう処理は自動化できれば作業品質も上がるだろうということで、1行でやってみました。

そもそも月の初めは1日と決まっているので、文字列として決め打ちしてもよいのですが、プログラムの保守性を考えて、あえてそうしません。たぶん先に「先月の末日」を解説したほうがよいと思われるので、先月の初日については後述します。
■ 先月の最終日
実は先月の最終日を表示させたかったのが、このエントリを書くキッカケですがw
月によっては30日だったり31日だったり、2月は28日までだし、閏年は29日までだし、7月31日の次、8月も31日だしで、いちいちハードコードしてたら面倒です。
YYYY/MM/DD 形式の場合
$ sengetsu=$(date +%Y/%m --date="1 month ago") ; for i in $(echo {61..1}); do date +%Y/%m/%d "--date=$i days ago"; done | egrep $sengetsu | tail -1
2011/02/28
YYYYMMDD 形式の場合
$ sengetsu=$(date +%Y%m --date="1 month ago") ; for i in $(echo {61..1}); do date +%Y%m%d "--date=$i days ago"; done | egrep $sengetsu | tail -1
20110228
こんな感じです。詳細な解説は後回しにして、次に先月の初日を見てみましょう。
■ 先月の初日
YYYY/MM/DD 形式の場合
$ sengetsu=$(date +%Y/%m --date="1 month ago") ; for i in $(echo {61..1}); do date +%Y/%m/%d "--date=$i days ago"; done | egrep $sengetsu | head -1
2011/02/01
YYYYMMDD 形式の場合
$ sengetsu=$(date +%Y%m --date="1 month ago") ; for i in $(echo {61..1}); do date +%Y%m%d "--date=$i days ago"; done | egrep $sengetsu | head -1
20110201
これで先月の初日と最終日を表示させることができました。


先月の初日と最終日算出の違いは、パイプで渡されたコマンドだけです。初日はheadコマンド、最終日はtailコマンドで、それぞれ1行だけしか表示していません。それでは、コマンドの最初から解説します。

sengetsu=$(date +%Y%m --date="1 month ago")
sengetsu=$(date +%Y/%m --date="1 month ago")
これは、単純にGNU dateコマンドで使えるオプションを利用し、「sengetsu」という変数にいれています。
$ sengetsu=$(date +%Y%m --date="1 month ago") ; echo $sengetsu
201102
$ sengetsu=$(date +%Y/%m --date="1 month ago") ; echo $sengetsu
2011/02
実際、上記のように変数を指定してからechoで出力してみると一目瞭然ですね。
次に、for文で
for i in $(echo {61..1}); do date +%Y%m%d "--date=$i days ago"; done
for i in $(echo {61..1}); do date +%Y/%m/%d "--date=$i days ago"; done
なんてことやっていますが、これは61から1までをデクリメントさせた結果を変数「i」に渡し、dateコマンドの「~~days ago」という引数に渡しています。これにより、61日前から昨日までの日付を一気に表示させることができます。
では、何故61から始まるか?といいますと、もうお分かりですね。8月31日にこのコマンドを叩いたときに、先月、つまり7月の初日を表示させたいからです。7月と8月はどちらも31日あるので、7月1日は8月31日の61日前だからです。
しかし、他の月は、規則正しく30日と31日の月が並んでいますし、2月に至っては28日ないし29日しかありません。例えば3月6日に、単純に61日前の日付を求めるとこうなります。
$ date ; for i in $(echo {61..1}); do date +%Y%m%d "--date=$i days ago"; done | head -1
Sun Mar  6 16:44:03 JST 2011
20110104
さりとて、1日前だと
$ date ; for i in $(echo {61..1}); do date +%Y%m%d "--date=$i days ago"; done | tail -1
Sun Mar  6 16:46:31 JST 2011
20110305
こうなります。んじゃ、egrepで先月の分だけ抽出してあげればよくね?ということで、ここで最初に設定した変数「sengetsu」が生きてくるわけです。1行にすると、なかなか横に長くて「なんじゃこりゃ?」と思われるかも知れませんが、「;(セミコロン)」や「|(パイプ)」の区切りで分割してみると、意外と単純なロジックだったりします。
ね、簡単でしょう?