実行時間が長いコマンドが終わったら、zshが勝手にGrowl通知してくる (Mac/Linux/SSH接続先)


Linuxカーネルのmakeとか、実行に時間のかかるコマンドを実行して、終わるのを待って眺めるのは辛い。でもその隙に別のことをやると、そのまま完了を待っているのを忘れてしまって時間を無駄にした気持ちになって辛い。` && growlnotify` とかコマンドの後につけるのを忘れたことに気がついて、Ctrl-Cで一旦止めるかどうか悩むのが辛い。

というわけで

zshのフックを適当に使って、普通にzshでコマンドを叩くだけで、閾値以上の時間がかかるコマンドが終わると通知してくれる設定を考えてみた。

* MacのGrowl 対応 ( growlnotify 必須 )
* Linuxのデスクトップ通知対応 ( notify-send 必須 )
* SSH接続先ではトリガ用文字列を出力するので、iTerm2などを使うと通知可能
  * トリガ設定はコメント参照
  * iTerm2の設定→ [Mac - iTerm2で特定文字列をハイライトやGrowl通知する - Qiita [キータ]](http://qiita.com/yaotti/items/3764572ea1e1972ba928) 

`.zshrc`などに追加するだけで動くはず。

`__timetrack_threshold`に設定する秒数で、通知の閾値を設定できる。デフォルトは20秒。
実行に時間がかかっても通知されたくないコマンド(vimでのファイル編集とかsshとか)を `__timetrack_ignore_progs` に列挙すると、それらは通知されなくなる。

ちょっと書き換えれば、メール通知とかも一応出来ると思うけどやらないほうが地球にやさしいと思う。

~~~zsh
autoload -U add-zsh-hook 2>/dev/null || return

#
# Notification of local host command
# ----------------------------------
#
# Automatic notification via growlnotify / notify-send
#
#
# Notification of remote host command
# -----------------------------------
#
# "==ZSH LONGRUN COMMAND TRACKER==" is printed after long run command execution
# You can utilize it as a trigger
#
# ## Example: iTerm2 trigger( http://qiita.com/yaotti/items/3764572ea1e1972ba928 )
#
#  * Trigger regex: ==ZSH LONGRUN COMMAND TRACKER==(.*)
#  * Parameters: \1
#

__timetrack_threshold=20 # seconds
read -r -d '' __timetrack_ignore_progs <<EOF
less
emacs vi vim
ssh mosh telnet nc netcat
gdb
EOF

export __timetrack_threshold
export __timetrack_ignore_progs

function __my_preexec_start_timetrack() {
    local command=$1

    export __timetrack_start=`date +%s`
    export __timetrack_command="$command"
}

function __my_preexec_end_timetrack() {
    local exec_time
    local command=$__timetrack_command
    local prog=$(echo $command|awk '{print $1}')
    local notify_method
    local message

    export __timetrack_end=`date +%s`

    if test -n "${REMOTEHOST}${SSH_CONNECTION}"; then
        notify_method="remotehost"
    elif which growlnotify >/dev/null 2>&1; then
        notify_method="growlnotify"
    elif which notify-send >/dev/null 2>&1; then
        notify_method="notify-send"
    else
        return
    fi

    if [ -z "$__timetrack_start" ] || [ -z "$__timetrack_threshold" ]; then
        return
    fi

    for ignore_prog in $(echo $__timetrack_ignore_progs); do
        [ "$prog" = "$ignore_prog" ] && return
    done

    exec_time=$((__timetrack_end-__timetrack_start))
    if [ -z "$command" ]; then
        command="<UNKNOWN>"
    fi

    message="Command finished!\nTime: $exec_time seconds\nCOMMAND: $command"

    if [ "$exec_time" -ge "$__timetrack_threshold" ]; then
        case $notify_method in
            "remotehost" )
		# show trigger string
                echo -e "\e[0;30m==ZSH LONGRUN COMMAND TRACKER==$(hostname -s): $command ($exec_time seconds)\e[m"
		sleep 1
		# wait 1 sec, and then delete trigger string
		echo -e "\e[1A\e[2K"
                ;;
            "growlnotify" )
                echo "$message" | growlnotify -n "ZSH timetracker" --appIcon Terminal
                ;;
            "notify-send" )
                notify-send "ZSH timetracker" "$message"
                ;;
        esac
    fi

    unset __timetrack_start
    unset __timetrack_command
}

if which growlnotify >/dev/null 2>&1 ||
    which notify-send >/dev/null 2>&1 ||
    test -n "${REMOTEHOST}${SSH_CONNECTION}"; then
    add-zsh-hook preexec __my_preexec_start_timetrack
    add-zsh-hook precmd __my_preexec_end_timetrack
fi
~~~