Mon Dec 09 2019

先日作成したアドベントカレンダーに登録されてる記事が自分の書いた1件というのは寂しいので水増しします. これは Shell Script Advent Calendar 2019 の 9 日目の記事です.

今年書いて良かった .zshrc 2019

これは2018年に書いた 今年書いて良かった .zshrc の 2019 年版です.

zshrc もシェルスクリプトなのでね.

去年の記事について

本題に入る前に、去年書いた設定を今年どれだけ使ったか反省したいと思います.

2018年に書いた記事を見ると Ctrl-Rhistory から peco するとかありますが、 fzf が提供する zsh スクリプトにまんまこれがあるのをついこないだ知りました. 今はそれを使ってます.

Ctrl-G Ctrl-R で履歴から cd するやつ. これは普通に使ってます. peco 部分は fzf に変えてもいいと思ってますが、 お好みで. 私は普段 GNU screen の上で生きてるのですが、 これだと fzf の preview は表示がよく壊れるので preview は切っており、 peco とそんなに変わらないので peco のままにしてます.

今年新たに書いた設定

bindkey で screen の起動

ターミナルを新たに開いた時、 ほとんどの場合は screen を新たに起動するか、 既存のセッションへのアタッチをします. 私は. 毎回 screen -x ... とか入力するのだだるいので、 bindkey で呼べるようにしました. (ターミナルを新たに開いたら強制的に screen を起動する画面に入るくらい積極的にする手も考えられますが、 今はそこまでしてないです)

# https://github.com/cympfh/dots/blob/326eda0f388864eab751c06a47e8a0d61b8ab99f/sh/bindkey.zsh#L50

insert-screen-x() {
    BUFFER=screen-x
    CURSOR=$#BUFFER
    zle .accept-line
}
zle -N insert-screen-x
bindkey "^K^X" insert-screen-x

insert-screen-s() {
    BUFFER=screen-s
    CURSOR=$#BUFFER
    zle .accept-line
}
zle -N insert-screen-s
bindkey "^K^S" insert-screen-s

BUFFER に文字列を代入することで強引にコマンドを入力して、 zle .accept-line でエンターを押すのをシミュレートしてます. こんなややこしいことをせずに直接コマンドを関数から呼べばいいのでは? とはじめは思ったのですが、 screen のようにターミナルを奪うコマンドを bindkey から呼ぶとなかなかややこしいようなので、上のような方式にしました.

ちなみに screen-x とか screen-sscreen -x とか screen -S をいい感じに呼ぶエイリアスです ( screen.sh#L19 ). 1つ目は既存のセッション名を列挙して peco で選んでそれを起動します. 後者は今いるディレクトリ名からいい感じにセッション名を決めて新しく screen を起動します.

やけに時間の掛かったコマンドは終わり際に経過時間を報告する & 効果音を鳴らす

ところで私のプロンプトは次のようになっています. 画像と見比べて意味はだいたい察してください.

# https://github.com/cympfh/dots/blob/326eda0f388864eab751c06a47e8a0d61b8ab99f/sh/prompt.sh#L5
PROMPT="
\`prompt-time\` \`prompt-host\`\`prompt-pwd\`\`prompt-git-status\`
   "

このようにプロンプトの頭に時刻を出すことにしています. それはもちろんいつでも今の時刻を素早く知りたいから、ではなくて、あとからスクロールして見返したときに、このコマンドを打った時刻はいつだったかを知れると便利だからです. ちなみに iTerm2 の上で tmux を使ってるみなさんは iTerm2 に "show timestamp" という設定項目があり、 これとだいたい同じことをもっと格好良くやってくれるので、それで十分です.

ところで他にも使いみちがあり、プロンプトとプロンプトの間の時刻の差を見ることで、コマンド実行に掛かった時間を計測することができます. 秒単位なので厳密な計測ではありませんが、そういえばさっきやった処理は思ったより時間が掛かったけどどのくらい掛かったんだろう、というのが後から思い返せるのは便利です.

(PROMPT) (time1) command1
runnning

(PROMPT) (time2) command2
...

思い出したときに (time2) - (time1) を計算することで command1 の実行時間が分かる.

これも罠があって、プロンプトの入力待ち状態で数分間コマンドを打たずにぼーっとしていると、時刻だけが更新しています. つまりプロンプトに表示される時刻は正確にはその直後のコマンドを実行し始めた時刻であり、直前のコマンド実行が終わった時刻ではないことです.

前々から不便だと思っていたので、真面目に計測しておくことにしました. すべてのコマンドの時間を計測しておきます. コマンド実行が数分未満も掛からなかったような場合は、掛かった時間も知りたくないでしょうから、数分以上掛かった場合のみ、時間を報告することにします.

スクリプト全体を貼ると大変なので抜粋

# https://github.com/cympfh/dots/blob/326eda0f388864eab751c06a47e8a0d61b8ab99f/sh/exec_time_report.sh

# コマンドを実行したら時刻を記録
preexec_lastcommand() {
    last_command=$1
    last_prompt_time=$(date "+%s")
    time_report_waiting=1
}

# コマンドが終わった == 次のコマンドの precmd
precmd_timereport() {
    cur_prompt_time=$(date "+%s")
    duration="$(( cur_prompt_time - last_prompt_time ))"
    # duration をいい感じに報告
    time_report_waiting=0
}

date "+%s" で unixtime を出してるので単純に引き算すれば掛かった秒数が得られます.