1月 062012
 

標準出力をコマンド履歴に追加する | Inhale n’ Exhaleコマンドラインをviモードで編集するのエントリで「それどうやんの!?」と訊いてきた知人から、後日こんなメールをもらった。

viモードにしてその延長上でコマンドの結果に対してpromptで編集(?)みたいのってできるのですかね?

例えば

$ cat somefile.txt  # これにファイル名が幾つか入っている
src/file1.txt
src/file2.txt

でここでESCを押すとviモードになってjとかkとかでfile1.txtfile2.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というサブルーチンを書いてみた。

addhist()
{
	local tmpfile=`mktemp`
	while read cmd
	do
		[ -n "$cmd" ] && echo $cmd >> $tmpfile
	done
	history -r $tmpfile
	rm -f $tmpfile
}

とりま~/addhistとして保存して試してみる。

$ . ~/addhist
$ history -c
$ cat somefile.txt | addhist
$ history
1 cat somefile.txt | addhist
2 history

あれ?おかしいな。
デバッグ用に一行追加。

addhist()
{
	local tmpfile=`mktemp`
	while read cmd
	do
		[ -n "$cmd" ] && echo $cmd >> $tmpfile
	done
	history -r $tmpfile
	rm -f $tmpfile
	history
}
$ . ~/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に以下の数行を追記して、

shopt -s histappend
export HISTCONTROL=ignoreboth:erasedups
export HISTSIZE=100000
export HISTFILESIZE=$HISTSIZE
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に追記したコードは以下の通り。

shopt -s histappend
export HISTCONTROL=ignoreboth:erasedups
export HISTSIZE=100000
export HISTFILESIZE=$HISTSIZE

addhist()
{
	while read cmd
	do
		[ -n "$cmd" ] && echo $cmd >> $HISTFILE
	done
}

export PROMPT_COMMAND="history -a; history -c; history -r; $PROMPT_COMMAND"

  2 コメント

  1. これ超便利っす!
    ありがとうございました!

  2. 便利なとき

    svn st で新しいファイルがあるときに
    ? very/long/folder/path/newfile.txt
    なんて表示されているので

    svn st | addhist
    やってhistoryをさかのぼって
    svn add very/long/folder/path/newfile.txt
    できるのがすごーくうれしいです。

    以前のTIP
    set -o vi
    と組み合わせて使うとマウスいらずで超はやい!

 返信する

以下のHTML タグと属性が利用できます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください