Kotohaを使って実行前のコマンドラインにアニメの名言を入れるzshスクリプト

今なんでもと書かれていたので[Shell Script Advent Calendar 2017 - Qiita](https://qiita.com/advent-calendar/2017/shellscript)5日目ですが普段の記事です。

# 使い方

![isone.gif](https://qiita-image-store.s3.amazonaws.com/0/62354/d06a3f4d-fe81-b091-4ecb-0a0520d1c3fc.gif)

ブラウザと異なり`/`は多用するので`@/`をトリガーに変更。
`@/thoge`でタグ検索。`@/khoge`でキーワード検索です。
現状トリガーそのまま流す方法を用意していないので使わなさそうなのを。
Ajaxのような非同期通信は無理なので、エンターの実行で初めて通信・選択し、コマンド編集中に戻ってきます。

使いみちは特に無いです。
`wall`コマンドで君の好きな名言を叫ぼう！
自鯖立ててスニペットをタグ付きで登録していくなんて使い方もできるかな。
zshの設定ファイルの共有で十分かな。

~~bash版も作れそうだけど力尽きたー！~~

# コード

```zsh:isone.zsh
autoload -Uz add-zsh-hook
add-zsh-hook preexec -isone

-isone () {
  local cmd="${1}"

  local root_url='https://kotoha-server.herokuapp.com'
  local tag_pattern='@/t(\S+)'
  local keyword_pattern='@/k(\S+)'
  local query
  local replace_pattern

  #setopt BASH_REMATCH; を使えば$matchは無し。動きが微妙に違うので注意
  if [[ $cmd =~ $tag_pattern ]]; then
    query='?tag='$match[1]
    replace_pattern=$tag_pattern
  elif [[ $cmd =~ $keyword_pattern ]]; then
    query='?text='$match[1]
    replace_pattern=$keyword_pattern
  else
    return 0
  fi

  # ""を外すならjqに-rオプション
  local words=`wget -q -O - "${root_url}/api/phrases.json${query}" | jq '.[].text'`
  if [ -z $words ]; then
    echo "${fg[red]}No Results : $match[1]${reset_color}"
  else
    local word=`echo $words | peco`
    if [ -z $word ]; then
      echo "${fg[red]}Abort peco${reset_color}"
    else
      cmd=`echo ${cmd} | sed -r "s#${replace_pattern}#${word}#"`
    fi
  fi

  print -z $cmd
  kill -INT 0
}
```

## 読み込み

ファイルを置いて読み込みか直接書く。
読み込みの場合はshebangが必要な場合があるかもしれません。

```zsh:.zshrc
. (path)~/isone.zsh
```

## dependencies

* [peco/peco: Simplistic interactive filtering tool](https://github.com/peco/peco)  
バカの一つ覚えといいますか、他に1行選択できるやりかたを知らないので、他に良いのがあれば。[^1] 
[^1]: 不満は無いのですが、他を知らないので選んだ感がほしい。
* [stedolan/jq: Command-line JSON processor](https://github.com/stedolan/jq)


# 参考

見たものを無秩序に列挙


* [いかに危険なコマンド実行間違いを防ぐか。そのためにExeCopを書いてみた - Copy/Cut/Paste/Hatena](http://k1low.hatenablog.com/entry/2017/08/24/083000)  
  [k1LoW/execop: ExeCop is a checker that check commands and environment variables before execute command.](https://github.com/k1LoW/execop)  
  preexecタイミングでコマンドを検知・実行阻止できるかの実例。神。これ自体現場で使えそう。
* [bash で特定のコマンドを実行前にキャンセルする - tkuchikiの日記](http://tkuchiki.hatenablog.com/entry/2017/03/01/143823)  
  (はてブ的に)↑の元ネタのようなもの(さらにtrapの元コードのようなものはStack Overflowに)
* [zsh5.0.8に存在するローカル変数のパーサバグ - Qiita](https://qiita.com/mpyw/items/e9e4c3b872b30c7024ee)  
  yumで入れたzsh(5.0.2)でExeCop動かない問題。
* [CentOS 6.4にzsh最新版をソースから入れる - /dev/null](http://gitpub.hatenablog.com/entry/2013/07/07/182014)  
  なくなくソースから。
* [konifar/kotoha: Kotoha is useful chrome extension that help you to quote a good phrase.](https://github.com/heavenshell/vim-kotoha)  
 [アニメの名言を簡単に引用できるChrome extension『Kotoha』作りました - Konifar's WIP](http://konifar.hatenablog.com/entry/2015/07/24/014613)  
  偉大なるどらえもん。ギークではない自分には中身を見る気になる程度の表面の面白みは重要。
* [heavenshell/vim-kotoha: vim-kotoha is useful Vim plugin that help you to quote a good phrase!](https://github.com/heavenshell/vim-kotoha)  
  サードパーティがあるだけで実現可能性が見えてきて助かる。当然コードも大助かり。
* [zshにオプションや引数を補完できるキーバインドを設定しよう - Qiita](https://qiita.com/syui/items/82b080920a4241e96aed)  
  使えそうなzsh変数の解説。
* [jq コマンドを使う日常のご紹介 - Qiita](https://qiita.com/takeshinoda@github/items/2dec7a72930ec1f658af)  
  kotohaからのJSONレスポンスをそのまま使えてpecoで食べられる形へ整形。
* [test と &#91; と &#91;&#91; コマンドの違い - 拡張 POSIX シェルスクリプト Advent Calendar 2013 - ダメ出し Blog](https://fumiyas.github.io/2013/12/15/test.sh-advent-calendar.html)  
 [シェルスクリプトでの正規表現マッチ - Qiita](https://qiita.com/Linda_pp/items/31fa611766598715a172)  
  正規表現おさらい。
* [HTML特殊文字コード表](http://www.shurey.com/js/labo/character.html)  
  Markdownに&#91;あると崩れる問題。普段は消すけど上記リンクは消すと意味不明なので。
* [Bash 正規表現マッチした部分文字列を再利用する方法 · DQNEO起業日記](http://dqn.sakusakutto.jp/2013/06/bash_rematch_regexp.html)  
  後方参照。
* [zsh でシェルスクリプトを書くいくつかの理由](https://gist.github.com/s-age/9871497)  
  書き方からして`$BASH_REMATCH`相当の`$match`の存在を匂わす。
* [Regular Expressions](http://www.geocities.jp/maakun_gemini_ab/contents/pc/regexp3.html)  
 zshでの`BASH_REMATCH`の使い方
* [regex - BASH_REMATCH doesn't capture - Stack Overflow](https://stackoverflow.com/questions/31695600/bash-rematch-doesnt-capture)  
  やはり`$match`で。
* [シェルスクリプトで空文字列かどうか調べる。 - masa.edw the ハバネロブリーダー](http://d.hatena.ne.jp/masa_edw/20080422/1208834378)
* [pecoでzshのコマンド履歴検索を超快適にする！ - Qiita](https://qiita.com/shepabashi/items/f2bc2be37a31df49bca5)  
  zshでのpecoを使ったコマンドライン操作の確認と確証に。
* [逆順出力 tac と tail -r - Qiita](https://qiita.com/b4b4r07/items/e465efe0b020407bdd44)  
  上のコマンド履歴検索CentOSで動かないぞ問題の調査。
* [bash/zsh のヒストリを peco で便利にする - Qiita](https://qiita.com/comuttun/items/f54e755f22508a6c7d78)  
  [zsh と peco でヒストリ補完 - わ](http://n91.hatenablog.com/entry/2014/10/16/125900)  
  ヒストリ検索で調べようとする自分が悪いし参考にはなるんですけど、zle前提になるのが。
* [zleを使わないでコマンドラインに値を入力した状態にする - Qiita](https://qiita.com/troter/items/b737bc5827a3718c05b2)  
  神。ショートカット始動でなければ`BUFFER`操作できないのかと挫折しかけたときに。

# 余話:そもそも

タイプ精度が悪すぎてコマンドの同じtypoをしたり、よく使うパスが頻出したりしていた…。
コマンドのtypo自体は[これ](https://qiita.com/khsk/items/f024340c818377298fd1)で実行後に補正されるけれど、ちょっと遅い。
`alias`はalias自体を忘れかねず、また求めていたのはコマンドの修正だったので、ちょっと違う。
[abbr:fish流別名！入力後に展開される短縮コマンドを定義](http://fish.rubikitch.com/abbr/)
がtypoコマンド修正の意図通りで、かなりGoodなんですが、文字列リテラルには非対応のあくまでコマンド限定。
両方書き換えたいという貪欲な欲求に、そう言えばコマンド実行前イベントがあって[これ](https://qiita.com/khsk/items/f024340c818377298fd1)でコマンドの設定方法も知れたので、
あとは実行前に実行中断できればコマンド全体を好きに置換することができるなと調査しました。
結果ExeCopを見つけるものの、fishでは`kill -INT 0`が効かず断念。
zshで実現するかと入れているとき、常用しないもので実用的なものを作っても仕方ないと思い、同時期にちょうどFx版作りたいなと思っていたKotohaを動かすことに。こっちの方が面白いので。
だから本来はfishでやりたかったので、ご存じの方は中断方法を教えていただけるとうれしいです…

# Bash版(挫折)

月曜日に投稿しようとしたらshellscriptのアドベントカレンダーを見つけたので空いた火曜日に投稿することにしました。
空いた月曜でbash移植しようとおもったのですが…

そもそものExeCopが正常に動かない(denyがループに陥る)
という状況で困った…
プリントデバッグでは`kill -INT 0`で中断しようとしているコマンドで再度trapが呼び出されるようで、(confirm no or denyの間)無限ループに。`kill`の次の行に行かない。
手動で打つ`echo $$`とtrap内での`echo $$`のPIDの値が同じなので、まるごとkillからの復帰→コマンド継続とかになっている…？

ただ、ExeCopのアイデアもとになったスクリプトも同じ現象なので、異なる人が成功している時点でこちらの環境・バージョン依存濃厚です。

`GNU bash, version 4.2.46(1)-release (x86_64-redhat-linux-gnu)`

yumに更新があったので(`4.2.46-29.el7_4`)上げて
`GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
`
もループしました。うーん…。

それでも移植するだけしてみたら、`kill`による中断は出来るが、なぜか`trap`が一度しか反応せず、以後キーワードを入れても無視される現象に変わる。

そもそも`print -z`の代替が見つからないということで、ギブアップ。

```bash:正常に動かないコード
-isone () {
  local cmd="${1}"

  local root_url='https://kotoha-server.herokuapp.com'
  local tag_pattern='@/t(\S+)'
  local keyword_pattern='@/k(\S+)'
  local query
  local replace_pattern

  if [[ $cmd =~ $tag_pattern ]]; then
    query='?tag='${BASH_REMATCH[1]}
    replace_pattern=$tag_pattern
  elif [[ $cmd =~ $keyword_pattern ]]; then
    query='?text='${BASH_REMATCH[1]}
    replace_pattern=$keyword_pattern
  else
    return 0
  fi

  # ""を外すならjqに-rオプション
  local words="`wget -q -O - "${root_url}/api/phrases.json${query}" | jq '.[].text'`"
  if [ -z "$words" ]; then
    echo -e "\e[31mNo Results : $match[1]\e[m"
  else
    # 改行残して出力するため""追加
    local word=`echo "$words" | peco --layout top-down`
    if [ -z "$word" ]; then
      echo -e "\e[31mAbort peco\e[m"
    else
      cmd=`echo ${cmd} | sed -r "s#${replace_pattern}#${word}#"`
    fi
  fi

  #挿入方法不明
  COMP_LINE=$cmd
  READLINE_LINE="$cmd"
  kill -INT 0
}


# ここExeCopそのまま拝借
isone () {
    [ -n "$COMP_LINE" ] && return
    [ "$BASH_COMMAND" = "$PROMPT_COMMAND" ] && return
    local cmd=''
    cmd=$(HISTTIMEFORMAT='' history 1 | sed -e "s/^[ ]*[0-9]*[ ]*//");
    -isone "${cmd}"
}

# クォートしても最初に-があるとオプションとみなされもよう
trap 'isone' DEBUG
```


***

***


マネジメントとなぜかITエンジニア論がしばしば出てくる[SHIROBAKO Advent Calendar](https://adventar.org/calendars/2092)は(なんと！)2017年も開催中！です！