Vimを使い終わったらGNU Screenのステータスラインのタイトルを「** free **」に

この前のDeveloper Enviroments Conferenceでid:secondlifeさんが発表された資料を見て、

  • キーリピート加速ツール「kbdacc
  • GNU Screenのステータスラインのタイトルを、VimのBufEnterフックで現在編集しているバッファ名に

のtipsを普段使うようになりました。

ちなみに、WindowsLinuxの開発マシンにリモートログインして作業する時は

を主に使っています。
特に2つ目のkbdaccは本当に便利です。PuTTYのターミナル画面の描画処理の軽さに加えて、kbdaccでキーリピートを加速させることで、Vim利用時のhjklキーでのカーソル移動が非常に軽快になります。
あとGNU Screenには画面分割を縦に分割するパッチを当てています。これもなかなか便利です。ただ、画面を縦分割すると、なぜかターミナル画面の描画処理が少し重くなるところが△です。

さて、本題です。

id:secondlifeさんの紹介であった、Screenのステータスラインのタイトルを、VimのBufEnterフックで現在編集しているバッファ名にするtipは確かに便利です。しかし、このtipをこのまま使うと、vimを終了してもGNU Screenのステータスラインのタイトルに、バッファ名が残ったままになる問題があります。

そこで、以下のようにvimスクリプトを改良しました。「^[」の所は「Ctrl + v + [」で入力します。

% vim ~/.vimrc
[...]
function SetScreenTabName(name)
  let arg = '^[k' . a:name . '^[\\'
  silent! exe '!echo -n "' . arg . "\""
endfunction

if &term =~ "screen"
  autocmd VimLeave * call SetScreenTabName('** free **')
  autocmd BufEnter * if bufname("") !~ "^\[A-Za-z0-9\]*://" | call SetScreenTabName("%") | endif 
endif

これを.vimrcに書いておくと、vimを使い終わったら、GNU Screenのステータスラインのタイトルが「** free **」に変化します。これで使用していないスクリーンが一目でわかるようになります。

「** free **」という文字列は自由に変更できます。SetScreenTabName関数のパラメータを好きな文字列に変更するだけでOKです。

zsh: diffの結果をvimで色付けして表示するグローバルエイリアス

コードを書いていると、以前のコードと現在のコードとのdiffを取りたいことがよくあります。
でも、diffの結果はちょっと読みにくい。もしdiffの結果が色付けされていたら、もっと読みやすいじゃないかと、今日ふと思いました。

そこで、zshのグローバルエイリアスを使って、diffの結果をvimで色付けして表示するようにしてみました。(.vimrcには「syntax on」と書いておきます。)

% vim ~/.zshrc
[...]
# View command results(stdout) by vim
export VIM_TMP=/tmp/vim.tmp
alias -g V="> $VIM_TMP$$; vim $VIM_TMP$$"

仕組みは簡単で、一時ファイルにコマンドの実行結果を保存して、vimでその一時ファイルを開いているだけです。一時ファイルを作っている所が、ちょっとダサいです。。。
以下のように、vimが標準入力を開くようにすれば、スマートになりますが、vimを終了するときに「:q」ではなく、「:q!」で終了しなければならなくなってしまいます。なので、僕は一時ファイルでクッションをかませる方を使うようにしました。

alias -g V="| vim -"

以下のようなコマンドを実行すれば、色付けされたdiffの結果がvimで表示されます。コマンドの最後に「 V」を付けてやるだけです。

% svn diff V
% diff -u Makefile.in.orig Makefile.in V

わかりやすさのために、diffを例にしてみましたが、上記の.zshrcのグローバルエイリアスのコードからわかるように、diff以外にも応用可能です。vimが解釈可能な形式なら何でもシンタックスハイライトしてくれます。でも、今はちょっとdiff以外への応用例は思いついていません。

追記 (2006/11/02 18:15)

hibomaのはてなダイアリー - colordiffでdiffの色づけ

diffの色づけするだけだったらcolordiff.plというのがありまっせ。
http://colordiff.sourceforge.net/

おお!そんなのがあるんですね。早速、yumでインストールしてみました。

% sudo yum install colordiff

vimでの色付けと、何かちょっとテイストが違いますね。

確かにcolordiffも便利だと思います。ただ、diffの結果が画面に収まらないくらいの量だと、スクロールの問題が出てきます。colordiffを使うと、例えば

  • lessとの相性が悪い
  • screenのバックスクロール機能(Ctrl + t(a) + [)の操作性が△

といった問題があります。
なので、グローバルエイリアスのやつの方に軍配が上がりますね。。。

本筋である、zshのグローバスエイリアスvimを噛ませてうんぬん、はいろいろ応用が利きそうですね。素敵な発想なので期待アゲです。

また何か面白い発想が出たら紹介したいと思います。

追記2 (2006/11/02 18:30)

すみません。lessで-Rオプションをつけたら、colordiffの色付けもちゃんと出るみたいです。

追記3 (2006/11/03 13:00)

vimで色付けして表示するグローバルエイリアスがイイ

どうやら読み込み専用モードで開けば一時ファイルを使わなくても良さそうです。

alias -g V="| vim -R"

本当ですね!これで一時ファイルを作らなくても済みますね。
ありがとうございます。

あ、このエイリアス、標準入力から読むようにするために、最後に「-」付けとかないとダメですね。

alias -g V="| vim -R -"

追記4 (2006/11/03 21:30)

spiritlooseのはてなダイアリー - ページャでsyntax highlight

Vim 使いは less.sh + less.vim つかってるんじゃないかなぁと思ってたんだけど。

less.vim、知りませんでした。。。まだまだ知らないことばかりです。
時間があいた時にちょっと試してみようかと思います。


鳥獣保護区 - コマンドの結果を Vim で表示

ところで zsh ならグローバルエイリアスだけでなく、プロセス置換という手もあります。

% vim =(diff fileA fileB)

一時ファイルを作ってそれを開いてるんですが、vim を終了すれば消えてしまうし、読み込み専用でないので少し編集して保存したい時なんかに使ってます。

zshのプロセス置換。これもはじめて知りました。
面白いトリックです。
このトリックも、グローバルエイリアスのやつと同様に、色々応用ができそうですね。

追記5 (2006/12/17 10:45)

ひげぽん OSとか作っちゃうかMona- - Color-SVN

IRCで教えてもらった、colorsvnで svn コマンドの出力に色がつく。

入れてみたが良いかもしれない。

ターミナルの出力を色づけするツールは、バリエーションが沢山あって面白いですね。
Color-SVNソースコードをちょっと見てみました。

% wget http://freshmeat.net/redir/colorsvn/66402/url_tgz/colorsvn-0.3.1.tar.gz
% tar zxvf colorsvn-0.3.1.tar.gz
% cd colorsvn-0.3.1
% vim colorsvn-original
#! /usr/bin/env perl

# colorsvn
#
# based on colorgcc
#
# Requires the ANSIColor module from CPAN.
#
# Usage:
#
# In a directory that occurs in your PATH _before_ the directory
# where svn lives, create a softlink to colorsvn:
#
#    svn -> colorsvn
#
# That's it. When "svn" is invoked, colorsvn is run instead.
#
# The default settings can be overridden with ~/.colorsvnrc.
# See the colorsvnrc-sample for more information.
#
# Note:
#
# colorsvn will only emit color codes if:
# 
#    (1) tts STDOUT is a tty.
#    (2) the value of $TERM is not listed in the "nocolor" option.
#    (3) the svn command is not a commit or import (as the text editor
#    opened by svn will often be hampered by colorsvn).
#
# If colorsvn colorizes the output, svn's STDERR will be
# combined with STDOUT. Otherwise, colorsvn just passes the output from
# svn through without modification.
#
[...]

use Term::ANSIColor;
use IPC::Open3;

[...]

# Keep the pid of the svn process so we can get its return
# code and use that as our return code.
$svn_pid = open3('<&STDIN', \*SVNOUT, \*SVNOUT, $svnPath, @ARGV);

[...]

# Colorize the output from the svn program.
while(<SVNOUT>)
{
    chomp;
    if (m/^ (.).+/) # Property changed only
    {   
            print($propcolors{$1}, $_, color("reset"));
    }   
    elsif (m/^(.)(\s+).+/) # S filename
    {   
        print($colors{$1}, $_, color("reset"));
    }   
    elsif (m/warning:/) # warning
    {   
        print($colors{"warning"}, $_, color("reset"));
    }   
    elsif (m/^$svnName[^:]*: / || m/^svn server: /) # server message
    {   
        print($colors{"server"}, $_, color("reset"));
    }   
    else # Anything else
    {   
        # Print normally.
        print(color("reset"), $_);
    }   
    print "\n";
}
[...]

Color-SVNPerlで書かれているみたいで、CPANモジュールのTerm::ANSIColorで色づけしているようです。
仕組みとしては簡単で、

  1. svnコマンドをIPC::Open3のopen3()で起動し、svnコマンドのSTDOUTとSTDERRを乗っ取る
  2. svnコマンドが出力するSTDOUTとSTDERRに対して、あるルールに従ってTerm::ANSIColorで色づけ

といった感じです。

Term::ANSIColorがこんな所でも使われているのですね。勉強になりました。

あと、

これ最高です!
ありがとうございました!

追記6 (2006/12/17 11:15)

先ほど追記しましたColor-SVNは「colorgcc」というツールをベースにしているようです。スルーしてました(汗

% vim colorsvn-original
#! /usr/bin/env perl

# colorsvn
#
# based on colorgcc
[...]

colorgccは、gccがあるプログラムをコンパイル中に出力する警告やエラーメッセージを色づけしてくれます。これはすごい!

これまで、僕は、gccが吐くそれらのメッセージをTerm::ANSIColorで色づけする自作スクリプトと、zshのグローバルエイリアスを組み合わせてました。
「make C」をzshで実行すると、以下のグローバルエイリアスが働いて、例えばgccのエラーメッセージを色づけするという具合です。

% vim ~/.zshrc
[...]
alias -g C=" 2>&1 | perl ~/bin/colorize.pl"

colorgccなんていうツールがあるなら、乗り換えようかなぁ。

zshな環境でGNU Screenのセッションのレジューム機能を最小限の労力で利用する方法

今日, GNU Screenのセッションのレジュームをしようとした時にzshの有難さを再認識しました. zshGNU Screenを一緒に使われている方は, 今回紹介するtipはすでにご存知かもしれません.

今まで, GNU Screenのセッションをレジュームしたい時には, 以下のようなコマンドを打って, セッションの一覧を表示させ, -rオプションにセッションのPIDを手入力していました. ずっと前に, はてなid:aqlさんがMac Bookを操られている所を拝見させて頂いた時も, 同じようにPIDを手入力されていたので, これが一般的なやり方なんだとずっと思い込んでいました. でも, いちいちセッションのPIDを手入力しなければならないことに, 今まで不満を持ち続けていました.

% screen -r
There are several suitable screens on:
        32628.pts-10.devel      (Detached)
        31525.pts-1.devel       (Detached)
        29928.pts-7.devel       (Attached)
Type "screen [-d] -r [pid.]tty.host" to resume one of them.

% screen -r 32628

それで, 今日, シェルにzshを使っている場合, もっと手軽にセッションのレジュームができることを偶然発見しました.

手順は以下の通りです.

まず, ターミナルに「screen -r 」まで入力します. 次に, tabキーを押します. そうすると, レジューム可能なセッションの候補が補完されます. tabキーを押す毎に, 候補となるセッションが順に補完されます.

% screen -r 

(tabキーを押す)
% screen -r pts-10.devel
pts-10.devel      pts-1.devel

これによって, セッションのPIDを手入力するよりかは, 遥かに簡単にレジュームできます. また, PIDを打ち間違って, レジュームに失敗するといったことも無くなります.

今日はzshの補完機能にちょっと感動した1日でした.

はてなブックマークカウンターを任意のサイトに設置するGreasemonkeyスクリプト

提供が開始された はてなブックマークカウンターをネタにして、久しぶりにGreasemonkeyスクリプトを作ってみました。これから、はてなブックマークカウンターを設置するブログサイトが増えると思いますが、それまで待てないので、今見ているサイトのページの最後に はてなブックマークカウンターを自動的に設置するようにしてみました。(変な動機...)

使い方

  1. サイトのページの最後に表示される「Get ?B Count」のリンク上でマウスをフローティングさせます
  2. はてなブックマークカウンターが表示されます(この時はじめて はてなとの通信が発生します)
  3. 表示された はてなブックマークカウンターをクリックするとエントリー一覧のページに移動します

SoftEtherの登さんのインタビューを観よう

普段、Webアプリケーションのような高レイヤーでPerlとかRubyとかJavaScriptとかばっかりさわっている技術者は、是非このインタビューを観ることをお勧めします。というのも、今、低レイヤーの技術の重要性を再認識する絶好の機会だと思うからです。

以下、ちょっとした感想です。

このインタビューで、もっと低レイヤーの技術に注目するべきという、登さんの主張には非常に共感しました。低レイヤーの技術がさわれる技術者が日本からもっと出てきてほしいですね。僕も低レイヤーの技術をさわれるようになれるよう頑張りたいと思います。

あと、インタビューの最後の方で、Win32 APIのリファレンス本を通学中に1ページ目から順に見て、覚えていったという話も印象に残っています。このエピソードから、「プログラミングに必要となる知識を、徹底的に頭の中にたたき込むというプロセスって結構重要なんじゃないか?」と思いました。でも、これができている人って、なかなかいないんじゃないでしょうか。以下の動画は、結構過激だと思いますが、学習方法として参考になる話だと思います。

GDBと作業プロセスの記述の重要性

GDBは積極的に活用しないと、もったいない。

今では、そう思うようになりました。

GDBをさわるようになるまでは、C言語で実装したプログラムのデバッグは、いわゆるprintデバッグで行うことが多かったです。printデバッグは簡単だし、ちょっとしたデバッグをしたい時には、今でも便利な手法だと思っています。

でも、GDBが段々使えるようになり、printデバッグ以上に、GDBが便利なツールだということに気がつきました。今振り返ると、場当たり的なprintデバッグに頼りすぎて、GDBを勉強する、言い換えると「プログラムを理解する」機会を自ら妨げていたように思います。

プログラムの理解力とデバッグの技術をさらに磨きたい方は、GDBを積極的に活用していけば、スキルの幅が格段に広がるのではないでしょうか?

僕も引き続きGDBと一緒に、勉強してみます。

参考


さて、このエントリではGDBに加えて、もう1つ言いたいことがあります。それは、作業プロセスの記述の重要性についてです。

デバッグプロセスの記述の重要性に気がついたきっかけは、DECONで高林さんが紹介された「Binary Hacks in Action」の中にあった「WEBRickGDBでいじる」という話題です。この話題のおかげで、GDBで遊んでみようという気になりました。

なぜそのような気になったのか、ちょっと考えてみました。高林さんの教養の高さとスライドの内容自体が大変興味深いというのも、その要因として大きいとは思います。でも、やっぱり一番大きかったのは、「誰でも再現できるように作業プロセスが明記されていたこと」です。作業プロセスが明確に記されていなかったら、GDBを全然操れなかった自分としては、多分、GDBで遊んでみようという気にはならなかったことでしょう。

つまり、作業プロセスの記述の仕方によっては、それを読む側の人間を大きく動かす(成長させる)武器になるということが言いたいのです。(「プロセスを共有することは大切」という考えは、ミラクリナックスのCTOの吉岡さんのブログの影響が大きいです。)

世の中のエンジニアの各自がお互いに、blogのようなツールを使ってオープンにさまざまな問題の作業プロセスを共有する動きが活発になっていけば、非常に面白い世界になりそうな気がします。

「WEBRickをGDBでいじる」を実際に試して、バイナリーハックを体験してみよう

DECONで高林さんが紹介された「Binary Hacks in Action」のスライドを見ていて、「WEBRickGDBでいじる」という所がとても興味深く感じました。本当にそんなことができるのか?と一瞬思いました。そこで、ちょっと試してみました。最初はうまくいかなかったのですが、最終的にWEBRickGDBから本当に操作できることを確認しました。うまくいかなかった原因は、パッケージからRubyをインストールいたためのようです。(GDBが理解できるデバッグ情報が、パッケージからインストールしRubyには入っていなかったのが原因だと考えられます。) ソースからビルドしてインストールしたRubyでは、期待通りの結果が得られました。

準備

パッケージからインストールしたRubyでは、WEBRickGDBからうまくいじれなかったので、ソースからRubyをインストールしておきます。configureする時にprefixオプションでホームディレクトリの適当などこかを指定しておけば、root権限が無くても手軽にインストールして試せます。

手順

スライドに載っているWEBRickベースのWebサーバのコードを書きます。

[1] % vim webrick.rb
require 'webrick'
include WEBrick
s = HTTPServer.new(:Port => 2000)
s.mount("/", HTTPServlet::FileHandler, "/tmp", true)
trap("INT") { s.shutdown }
s.start

そして、WEBRickを起動します。

[1] % ruby webrick.rb
[2006-09-17 22:06:57] INFO  WEBrick 1.3.1
[2006-09-17 22:06:57] INFO  ruby 1.8.5 (2006-08-25) [i686-darwin8.5.2]
[2006-09-17 22:06:58] INFO  WEBrick::HTTPServer#start: pid=13645 port=2000

もう1つのターミナル[2]で、index.htmlを用意し、telnetで起動したWEBRickのプロセスに接続します。接続が完了したら、HTTPのGETメソッドで「/」(すなわち、index.html)を取得します。以下のように、index.htmlのコンテンツをうまく取得できることを確認できました。

[2] % echo "hello, world" > /tmp/index.html
[2] % telnet localhost 2000
Trying ::1...
Connected to localhost.
Escape character is '^]'.
GET /
hello, world
Connection closed by foreign host.

WEBRickのサーバのプロセスIDを調べて、そのプロセスIDでGDBからそのプロセスにアタッチします。僕の環境にはプロセスIDを簡単に調べられるpgrepはインストールされていないので、適当にコマンドを組み合わせてプロセスIDを取得することにしました。どうやら、うまくWEBRickのサーバにアタッチできたようです。

[3] % pid=`ps au | grep webrick | awk '{ print $2 }'`
[3] % gdb -p $pid
GNU gdb 6.1-20040303 (Apple version gdb-437) (Fri Jan 13 18:45:48 GMT 2006)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-apple-darwin".
Attaching to process 13671.
Reading symbols for shared libraries . done
Reading symbols for shared libraries ............... done
0x9001ab3c in select ()
(gdb) 

そして、rb_io_write関数にブレークポイントをセットします。「c」で再び実行状態にします。

(gdb) b rb_io_write
Breakpoint 1 at 0x257bf: file io.c, line 591.
(gdb) c
Continuing.

この状態で、他のターミナルからtelnetWEBRickのサーバを先ほどと同じように叩きます。そうすると、先ほどとは挙動が違って、GETリクエストを送信しても、WEBRickのサーバから応答がありません。

[2] % telnet localhost 2000
Trying ::1...
Connected to localhost.
Escape character is '^]'.
GET /

そこで、GDBを動かしているターミナルを見ると、先ほどセットしたブレークポイントで止まったのがわかります。あとは、スライドの説明にあるように、GDBでrb_io_write関数の第2引数のstrの内容を表示したり、strの内容を改ざんしたりして遊びます。

Breakpoint 1, rb_io_write (io=5368480, str=5362860) at io.c:591
591         return rb_funcall(io, id_write, 1, str);
(gdb) p ((struct RString*)str)->ptr
$1 = 0x112b1d0 "hello, world\n"
(gdb) set ((struct RString*)str)->ptr = "crazy"
(gdb) set ((struct RString*)str)->len = 5
(gdb) c
Continuing.

おわりに

今までGDBを全然使った経験がありませんでしたが、このバイナリーハックのおかげで、GDBの凄さがわかるようになりました。そして、バイナリーハックにはGDBが非常に強力なツールとなることを学びました。

あと、余談ですが、GDBについてちょっと調べてみて、GDBのbtコマンドでスタックトレースを表示したり、任意の呼び出し位置に移動したりする方法を知りました。こんな感じです。GDBを自由自在に使えるようになったら、かなり強い気がします。

(gdb) bt
#0  rb_io_write (io=5368480, str=4) at io.c:591
#1  0x00025802 in rb_io_addstr (io=5368480, str=4) at io.c:613
#2  0x0000c905 in rb_call0 (klass=2020060, recv=5368480, id=333, oid=333, argc=1, argv=0xbfff98d0, body=0x1ecbfc, flags=0) at eval.c:5810
#3  0x0000d451 in rb_call (klass=2020060, recv=5368480, mid=333, argc=1, argv=0xbfff98d0, scope=0) at eval.c:6048
#4  0x0000b341 in rb_eval (self=5368000, n=0x1) at eval.c:3443
#5  0x0000d109 in rb_call0 (klass=5725800, recv=5368000, id=19065, oid=19065, argc=2, argv=0xbfff9d50, body=0x5762c8, flags=2) at eval.c:5954
#6  0x0000d451 in rb_call (klass=5725800, recv=5368000, mid=19065, argc=2, argv=0xbfff9d50, scope=1) at eval.c:6048
#7  0x0000a321 in rb_eval (self=5368000, n=0x1) at eval.c:3458
#8  0x000093af in rb_eval (self=5368000, n=0x1) at eval.c:3097
#9  0x0000d109 in rb_call0 (klass=5725800, recv=5368000, id=19121, oid=19121, argc=4, argv=0xbfffa470, body=0x576458, flags=2) at eval.c:5954
#10 0x0000d451 in rb_call (klass=5725800, recv=5368000, mid=19121, argc=4, argv=0xbfffa470, scope=1) at eval.c:6048
[...]

(gdb) frame 1
#1  0x00025802 in rb_io_addstr (io=5368480, str=4) at io.c:613
613         rb_io_write(io, str);
(gdb) list
608
609     VALUE
610     rb_io_addstr(io, str)
611         VALUE io, str;
612     {
613         rb_io_write(io, str);
614         return io;
615     }
616
617     /*

Rubyソースコード完全解説

Rubyソースコード完全解説