読者です 読者をやめる 読者になる 読者になる

愛と勇気と缶ビール

ふしぎとぼくらはなにをしたらよいか

Rakeで俺たちはもっとtmuxと仲良くなれるんじゃないか?と思った

gist: http://gist.github.com/557598

きのこかたけのこかと問われればたけのこ、犬か猫かと問われれば猫、VimEmacsかと問われればVim、screenかtmuxかと問われればtmux。


というわけでtmuxを使っているのだけど、一年くらい使っているにも関わらず「新しいウィンドウ(端末)を開き、そのウィンドウで任意のコマンドを実行させる方法」を知らなかった。

昨日分かった、そのやり方をbash/zshの関数で書くとこんな感じ。

hoge() { tmux new-window -a -n $1; tmux send-keys -t:$1 "do something" C-m }

("-a"はtmux1.3からのオプションで、このオプションをつけると現在のウィンドウの真横に新しいウィンドウを開ける。)

tmux new-window -n で新しいwindowを名前をつけつつ開き、そいつにtmux send-keys -tでターゲットのウィンドウを指定してコマンドを投げる。実際にはコマンドというよりキーストロークを投げている?らしく、最後にC-mを送ってやらないと送ったコマンドが実行されない。


でこっからが本題なんだけど、screenとかtmuxで開くウィンドウって大体毎日決まっている。仕事だと特に「このディレクトリとこのディレクトリで作業する」というのは一つのプロジェクトが進行中であればほぼ変わらず、「今日はここで作業して明日はあっちで明後日はこっち」みたいにコロコロ切り替わることはあまりない。

なので当然「このウィンドウを開く→このウィンドウを開く→このウィンドウを開く」という毎日行っている流れを自動化したいものだ、という欲求がわく。「一回開けばそのままじゃん」という人もいるかもしれないが、何らかの事情で端末そのものがクラッシュしたり、tmuxがクラッシュしたり、OSの再起動を余儀なくされたり、というような不慮の事故で今までに開いた全てのウィンドウが水泡に帰することはよくある。

一番最初に思いつくのは「tmuxの設定ファイルに開きたいウィンドウを開くコマンドを列挙しておいて、起動時に開けばいいんじゃね?」という方法。この方法のあまりよろしくない点は、

  1. マルチディスプレイで複数の端末エミュレータを開いて作業する場合、どの端末でも設定ファイルに書き込んだウィンドウがまとめて起動されてしまう
  2. 一部のウィンドウ群を消してしまった場合そこだけを復活するとかできない
  3. 開きたいウィンドウが変わったときに設定ファイルそのものを毎回いじるのはあんまりうれしくない(家とその他で設定ファイルは出来る限り共通なのが望ましい)
  4. 所詮は設定ファイルなのであまり柔軟な記述ができない


どうも、いけてない。


screenやtmuxで開くウィンドウ群については、「作業Aをするときは、ここのデプロイ用サーバSにログインして、このディレクトリDにcdする」みたいな感じで、作業→ディレクトリ、サーバのような依存関係があるように思える。

なので、

  1. ある程度使い方を知っている
  2. 柔軟な記述ができる
  3. 依存関係を解決するのに適している

という3つの条件が揃ったツールとしてRakeを使い、作業の際に開くtmuxのウィンドウ集合をhomeの下のRakefileで定義することにした。


といってもそんなに難しいことをする必要はなくて、

def project(name, dir = nil)
  dir ||= name
  task name do
    sh "tmux new-window -n #{name}"
    sh "tmux send-keys -t:#{name} 'cd #{dir}; clear' C-m"
    yield name if block_given?
  end
end

def ssh(server, user = nil)
  user ||= ENV['USER']
  task server do
    sh "tmux new-window -n #{server}"
    sh "tmux send-keys -t:#{server} 'ssh #{user}@#{server}' C-m"
  end
end

とか書いておいて、後はそのうしろに

project(:lib, "project/lib")
project(:test, "project/test")
ssh(:dev001)

task :work => [:lib, :test, :dev001]

とか書いておくと、rake rbでlib, test, dev001という名前のついたウィンドウをそれぞれ開き、各ウィンドウでしかるべきディレクトリに移動し、サーバにsshする、という一つの作業準備の自動化タスクが定義できる。

こういう風に作業における依存性をRakefileに書いておき、rakeコマンドを通じてウィンドウの生成を自動化すると、いざというときでも中にRubyのコードを書いておいて複雑な処理をさせることが可能になり、気が変わった時も一々.tmux.conf自体をいじらなくてよくなる。libだけを開きたい時は"rake lib"でいけるし。


と思ったが、昨日今日始めてみたことなのでどの程度使いやすいかは未確定。このへんどういう風に自動化するのが便利なのだろうか。