えびちゃんの日記

えびちゃん(競プロ)の日記です。

えびちゃんのシェルのかわいいやつ

これに出てくる (*'-')b この子です。

つくりかた

この子の挙動は次の通りです。

  • 直前のコマンドが空なら何もしない
  • 直前のコマンドが空でないなら、実行ステータスに応じて話す

まず、現在の状態を示す変数 state を用意します。 この変数は次のように値が定められるとします。

  • standby:コマンドの入力中かつ直前のコマンドは空
  • executed:コマンドの入力中かつ直前のコマンドは空でない
  • executing:コマンドの実行中

これは、特殊な関数 precmdpreexec を用いて管理します。特定のタイミング(コマンドの入力前や実行前)で実行される関数です。

また、PS1 に関して、psvar と呼ばれる配列変数の要素数によって分岐できるので、executed のときは (1)、それ以外のときは () にするようにしておきます。

state=standby
precmd() {
    if [[ "$state" == executing ]]; then
        state=executed
        psvar=(1)
    else
        state=standby
        psvar=()
    fi
}
preexec() {
    state=executing
}

あとは、終了ステータスに応じて話させる関数を作るのと、PS1 を「psvar の大きさが 1 以上なら該当のコマンドに $? を渡して得た出力に、そうでなければ空文字列になる」ように設定すればいいです。

smart_exit_status() {
  if [[ "$1" == 0 ]]; then
    echo "%F{10}(*'-')b < Exited successfully.%f"
    return
  fi
  echo "%F{9}Exited with code $1"                                                               
  echo -n "(*'~')/ < "
  case "$1" in
    1) echo -n 'Something went wrong?';;
    127) echo -n 'Command not found?';;
    # よしなに
    130) echo -n 'Received SIGINT, Interrupt.';;
    134) echo -n 'Received SIGABRT, Aborted.';;
    137) echo -n 'Terminated with SIGKILL, Killed.';;
    139) echo -n 'Received SIGSEGV, Segmentation fault.';;
    # 何を示す値なのかを書いておくと、助かることが多いです。
    # 自分で調べるなどして書きましょう。
    *) echo -n '...';;
  esac
  echo '%f'
}
setopt prompt_subst
PS1+="%(1v_\$(smart_exit_status \$?)_)"

おまけ

ところで、Zsh だとコマンド入力時に C-c をしてもマーカーがつかないので、実際に実行したのか中断したのかわかりにくいですね。

上でつくった $state を利用するといい感じにできます。

TRAPINT() {
  if [[ "$state" != executing ]]; then
    print -P '%B%S^C%s%b'
  fi
  return $((128+$1))
}

なんですが、isearch mode などでうまくいってくれなかったりするのに気づいて、やめにしました。泣いています。