標準出力をコマンド履歴に追加する | Inhale n’ Exhaleコマンドラインをviモードで編集するのエントリで「それどうやんの!?」と訊いてきた知人から、後日こんなメールをもらった。
viモードにしてその延長上でコマンドの結果に対してpromptで編集(?)みたいのってできるのですかね?
例えば
$ cat somefile.txt # これにファイル名が幾つか入っている
src/file1.txt
src/file2.txt
でここでESCを押すとviモードになってj
とかk
とかでfile1.txt
とfile2.txt
がチラチラと見える。
最終的にはi
とか使って
$ vi src/file2.txt
とかがぱっとできたりするといいなーと思っている。
今までそういう発想をしたことがなかったが、欲しいシチュエーションは確かにありそうだ。
標準出力の結果をvimで編集することを前提にするならば、
$ cat somefile.txt | vim -R -
とやって、vim内でsrc/file1.txt
にカーソルを合わせて
src/file1.txt
src/file2.txt
gf
とタイプすれば、src/file1.txt
がそのまま編集可能になる。
蛇足だが、somefile.txt
が仮に
src
src/file1.txt
src/file2.txt
となっていて、src
にカーソルを合わせてgf
とタイプすれば、vimの中でディレクトリビューが表示される。

もちろん、カーソルの移動はvimのキーバインド。ファイル名やディレクトリ名(../
も含む)にカーソルを合わせてEnterを押せば、そのファイルやディレクトリを開くことができる。別のファイルを開くときに、いちいち:q
コマンドでvimを抜けてディレクトリを移動しなくて済むし、:e
コマンドや:sp
コマンドで別ファイルを開くときも、ディレクトリ名まで補完した状態でEnterを押せば、同じようにディレクトリビューが使える。
さて、話を戻そう。
コマンド履歴が保存されている場所として真っ先に思いつくのが~/.bash_history
ファイル。標準出力の結果を~/.bash_history
に追記すれば実現できるかもと思ったが、~/.bash_history
はログアウト時(ターミナルでexit
した時)に書き出されて、ログイン時(ターミナルを開いた時)に読み込まれる。セッション中のコマンド履歴はbashのメモリ内で保持されていて、どこかのファイルに書き出されているわけではない。
historyコマンドを使ってコマンド履歴の書き出し、読み込みはできるが、直接編集できるようなオプションはない。
$ cat somefile.txt > tmp
$ history -r tmp
$ rm -f tmp
とやれば、とりあえずコマンド履歴に追記することはできるが、いちいちテンポラリファイルを手動で作って消してとやるのは非効率的。~/.bashrc
あたりにサブルーチン化してしまうのが得策だろう。
で、addhist
というサブルーチンを書いてみた。
6 | [ -n "$cmd" ] && echo $cmd >> $tmpfile |
とりま~/addhist
として保存して試してみる。
$ . ~/addhist
$ history -c
$ cat somefile.txt | addhist
$ history
1 cat somefile.txt | addhist
2 history
あれ?おかしいな。
デバッグ用に一行追加。
6 | [ -n "$cmd" ] && echo $cmd >> $tmpfile |
$ . ~/addhist
$ history -c
$ cat somefile.txt | addhist
src/file1.txt
src/file2.txt
addhist
の中ではちゃんと履歴に追加されている。
あ~!パイプで渡してるからaddhistが別セッションになってしまってるわけね。呼び出し元のセッションには何の影響もないと。addhist
内で書き込んだhistoryをexportすることはできるのだろうか…頼りのGoogle先生が出した答えがコレだ。
Real-time history export amongst Bash terminal windows(2012/08/21 リンク切れ)
なるほど。$PROMPT_COMMAND
を使って、$HISTFILE
(=~/.bash_history
)経由で全セッションで同期させるのね。これができるなら、もうaddhist
は存在自体が否定されてしまうような…。
ひとまず、~/.bashrcに以下の数行を追記して、
2 | export HISTCONTROL=ignoreboth:erasedups |
4 | export HISTFILESIZE=$HISTSIZE |
5 | export PROMPT_COMMAND= "history -a; history -c; history -r; $PROMPT_COMMAND" |
bashの別セッションを開く。$PROMPT_COMMAND
が再帰してるので、テスト段階では.
(ドット)コマンドで読み込むと面倒なことになる。
$ bash
$ > $HISTFILE
$ history
1 > $HISTFILE
2 history
$ cat somefile.txt >> $HISTFILE
$ history
1 > $HISTFILE
2 src/file1.txt
3 src/file2.txt
4 cat somefile.txt >> $HISTFILE
5 history
おお、期待通りsomefile.txt
の内容がhistoryに追加できた。
でも個人的には
$ cat somefile.txt >> $HISTFILE
とリダイレクトを使うより、addhist
みたいにパイプでつなぐ方がいい。理由は2つあって、1つは$HISTFILE
のキーストロークがShift押しっぱなしで疲れる。もう1つはリダイレクトの>>
をうっかり>
とtypoしてしまうと履歴がきれいに消えてしまうという悲惨な結果になる。これは結構ありがちなパターンかと。
ということで、終盤にきてaddhist
復活!
最終的に~/.bashrc
に追記したコードは以下の通り。
2 | export HISTCONTROL=ignoreboth:erasedups |
4 | export HISTFILESIZE=$HISTSIZE |
10 | [ -n "$cmd" ] && echo $cmd >> $HISTFILE |
14 | export PROMPT_COMMAND= "history -a; history -c; history -r; $PROMPT_COMMAND" |
これ超便利っす!
ありがとうございました!
便利なとき
svn st で新しいファイルがあるときに
? very/long/folder/path/newfile.txt
なんて表示されているので
svn st | addhist
やってhistoryをさかのぼって
svn add very/long/folder/path/newfile.txt
できるのがすごーくうれしいです。
以前のTIP
set -o vi
と組み合わせて使うとマウスいらずで超はやい!