長時間かかったコマンドの実行完了を bash でも terminal-notifier でお知らせしたい

[2016.02.19 追記]
スクリプトにバグがあったので修正しました
[追記ここまで]

世の中には CLI から OS X の通知を出せる terminal-notifier という素晴らしいツールがありまして、

github.com

これを brew でインストールしたりなんかすると

$ terminal-notifier -message test

とかやるだけで

f:id:bearmini:20160216215515p:plain

こんな感じにお知らせが出ます。

一方、何かコマンドを実行したら結果が出るまでに10分かかるとか1時間かかるとかそういうことってたまにありますよね。

「ビルドを開始したらちょっと休憩しよう」とか「デプロイを開始したらお昼ごはん行ってこよう」とか「これが終わるまで Twitter でも眺めてるか」ってこと、よくあると思います。

休憩したりお昼ごはんに行ってくるだけならだいたい目論見通りの時間で帰ってくることができますが、Twitter やら Facebook やらを見に行ったりしちゃったが最後、コマンドの実行はとっくに終わってるのになかなか帰ってこれなくて気づいたらすごい時間が経ってしまっていた・・・ってこともよくあると思います。

コマンドの実行が完了したらわかりやすく通知してくれればいいのに、と何度も思いました。

調べてみると、zsh では terminal-notifier を使ってそういうことを実現している方がたくさんいらっしゃるようです。

僕も zsh に改宗・・・と一瞬心が揺らぎましたが、bash でもできないかどうか調べてみました。

すると、こんなものを発見。

github.com

説明に書いてあるとおり、zsh の preexec と precmd をエミュレートするものです。
これがあれば、
preexec でタイムスタンプを保存
precmd でそのタイムスタンプと現在時刻の差を調べ、一定以上の時間が経っていたら terminal-notifier で通知を出す
という処理をすれば望んだ処理が実現できそうです。

ということで ~/.bashrc の末尾あたりに以下のようなコードを書いてみました。

[[ -f ~/.bash-preexec.sh ]] && source ~/.bash-preexec.sh
_tn_timestamp=`date +%s`
_tn_cmd=''
preexec() {
    _tn_timestamp=`date +%s`
    _tn_cmd=$1
}
precmd() {
    now=`date +%s`
    dur=$(( $now - $_tn_timestamp ))
    if [[ $_tn_cmd == "" ]]; then
        return
    fi
    if [[ $dur -gt 60 ]]; then
        terminal-notifier -message "Finished: $_tn_cmd"
        echo elapsed time: $dur seconds
    fi
    _tn_cmd=''
}

preexec はコマンド実行直前に呼ばれる関数で、precmd はコマンド完了後(より厳密には、プロンプト表示直前)に呼ばれる関数です。
プロンプトで何も入力せずに Enter だけ押すと preexec が呼ばれずに precmd が呼ばれたりしますのでそれを回避するコードが入ってたりします。

これで、実行開始して 60 秒以上経ってから実行が完了したら通知が表示されるようになりました。

しかし問題もあって、たとえば vim で何かのファイルを編集して 1 分以上経過して戻ってきたら通知が出ちゃったり、ssh で他のホストにログインして 1 分以上作業してからログアウトして戻ってきたりすると通知が出ちゃったりします。
ブラックリスト的に vim とか ssh だったら通知を出さないというような処理も入れる必要があるかもしれません。

今のところは vimssh の後に通知が表示されてもそれほど困ってないので放置してありますが、何か良い解決策をお持ちの方・思いついた方はお知らせいただければと思います。