はじめに
シェルスクリプトの互換性問題で意外と困るのが日付処理です。date
コマンド自体は POSIX で規定されていますが共通で使えるオプションが少なく Linux (GNU) と macOS (BSD) 間での互換性は低いです。
そこで日付処理の選択肢の一つとして使える bash の printf
の機能を紹介します。bash 4.2 以上(または ksh93)を使うことが前提となりますがシェルに搭載されている機能であるため date
コマンドに依存することなく使用することができます。注意事項として macOS でデフォルトでインストールされている bash は 3.2.57 とバージョンが低いので Homebrew などで最新版をインストールする必要があります。
使い方
引数を何も指定しない場合、現在の日時が表示されます。
$ printf '%(%F %T)T\n'
2021-08-15 16:13:33
引数には UNIX 時間を指定することができます。
$ printf '%(%F %T)T\n' 1629011613
2021-08-15 16:13:33
また特殊な値として -1
(現在時刻) と -2
(シェルスクリプトを実行した時刻) を指定することができます。
$ printf '%(%F %T)T\n' -1
2021-08-15 16:22:47
$ printf '%(%F %T)T\n' -2
2021-08-15 16:20:25
書式
日付の書式は strftime
と同じ datefmt
の書式が利用可能です。(注意 全て問題なく使用できるかは検証していません)
$ printf '%(%s)T\n' # UNIX タイム
1629012592
$ printf '%(%Y/%m/%d %H:%M:%S)T\n'
2021/08/15 16:32:18
変数への割当
UNIX 時間を取得できたならば、それを変数に代入したくなることでしょう。では現在の UNIX 時間を変数へ代入・・・
now=$(printf '%(%s)T')
っと、このコードは書かないでください。コマンド置換はサブシェルが生成される為遅いです。こちらが推奨される方法です。
printf -v now '%(%s)T'
ちなみに 100 倍以上の差があります。
$ time for i in $(seq 10000); do printf -v now '%(%s)T'; done
real 0m0.090s
user 0m0.058s
sys 0m0.032s
$ time for i in $(seq 10000); do now=$(printf '%(%s)T'); done
real 0m12.482s
user 0m8.382s
sys 0m4.985s
さいごに
さて、実はこの日付出力機能は ksh93 の情報を調べていて気づきました。今までも目にしてた記憶はあるのですが読み飛ばしていましたね。bash の日付出力の機能の残念なところは、日常の日付から UNIX 時間への変換ができないということです。
$ bash -c "printf '%(%s)T\n' '2021-08-15 16:13:33'"
bash: line 1: printf: 2021-08-15 16:13:33: invalid number
2021
$ # ksh93 ならできる
$ ksh -c "printf '%(%s)T\n' '2021-08-15 16:13:33'"
1629011613
いや、実に惜しい。これさえできればシェルだけで簡単に時間計算ができたのですが。
でも大丈夫、UNIX 時間と日常の日付の相互変換はシェル関数だけ(外部コマンド不要)で実装することができます(参照「シェルスクリプトでUNIX時間⇔日付の相互変換を行う関数(POSIX準拠)」)。UNIX 時間にさえ変換してしまえば、1 週間前の日付を計算するのも簡単なわけで、これらを組み合わせると外部コマンドを使用することなく現在の日付の取得と時間計算を行うことができます。