シェルで長いコマンドが動いた場合、コマンド終了時に音を鳴らす

ターミナルで作業中、たまに意図せず時間のかかるコマンドを叩いてしまうことがあります。例えば、curlでダウンロード仕掛けたは良いけどファイルサイズが大きかったり、無意識にcarthage update叩いてしまった場合などです。

そうなると、終わるまでちょっと待つかと思ってブラウザを開き、その結果ネットサーフィンが捗りコマンドが終わってからも十数分くらい想定していない時間の使い方をしてしまうことがあります。

最初から長くなるのがわかっているときは、以下のようにafplayコマンドを使って音を鳴らすようにしていました。

$ npm run deploy; afplay /System/Library/Sounds/Purr.aiff

この方法では、たまたま実行したコマンドがの実行時間が長かった場合に、音を鳴らすためには一度実行を止めなければなりません。

そこでシェルのフック機能を使って、コマンド実行が長かった場合は自動的に音が鳴るようにしてみました。

シェルの実行時フック

シェルでコマンドの実行前後をフックするという記事を参考にしました。僕はzshを使いつづけているのですが、zshではコマンド実行後というフックがないため、プロンプト表示前を代用としてフックすると良いそうです。 そのままではコマンドを入れずにreturnを叩いたときにもprecmdが動くので、preexecでフラグを立てておいて、それがある場合のみ実行してフラグを倒す、などの対応が必要そうです。

autoload -Uz add-zsh-hook

add-zsh-hook preexec my_preexec
add-zsh-hook precmd my_precmd

my_preexec() {
  echo "preexec"
}

my_precmd() {
  echo "precmd"
}

元記事にはfishとbashでの方法も書かれていました。

時間の取得

date +%sunix timestampがとれます。

組み合わせる

上記を組み合わせて、このような形で実装しました。

autoload -Uz add-zsh-hook

if [ "$(uname)" = 'Darwin' ]; then
  add-zsh-hook preexec my_preexec
  add-zsh-hook precmd my_precmd
fi

my_preexec() {
  MY_START_TIME=`date +%s`
}

my_precmd() {
  if [ -n "$MY_START_TIME" ]; then
    MY_END_TIME=`date +%s`
    MY_TIME_DELTA=`expr $MY_END_TIME - $MY_START_TIME`
    if [ $MY_TIME_DELTA -ge 20 ]; then
      afplay /System/Library/Sounds/Glass.aiff
    fi
    unset MY_START_TIME
    unset MY_END_TIME
    unset MY_TIME_DELTA
  fi
}
  • macOSの場合のみフックを登録するようにしておく
    • afplaymacOSでしか動かないので、.zshrcをLinuxに持って行ったときのための安全策
  • preexecで開始時間を変数に取る
  • precmdで開始時間と終了時間の差分を取って、20秒以上経っている場合は音を鳴らす
    • preexecで保存した開始時間は毎回unsetで消去して、開始時間がある場合のみprecmdが実行されないようにしておくことで、空打ちに対処する

その他

僕はMacからの音が聞こえる環境で作業しているのでafplayで事足りのですが、音を出せない環境で作業されている方は通知センターを使ってみてはいかがでしょうか。 ターミナルからmacの通知センターに投稿する