理系学生日記

おまえはいつまで学生気分なのか

tmux 上で pbcopy/pbpaste が動かない問題

pbcopy が動かない…動かない…と呪詛のように呟きつつ連休が終わりそうです。こんな悲しい週末があって良いのか。
このような悲劇が二度と起こらないよう、調べたことをまとめておきます。

問題

pbcopy、pbpaste も含めて、tmux セッションの下で実行すると、何をしても終了コード 1 で異常終了するという憂き目に遭っています。

# tmux セッション配下。標準入力無しで実行すると…
$ pbcopy
$ echo $?  
1 # ウワッ、終了コード 1 …

# tmux セッション配下。パイプ経由で標準入力に流し込んで実行しても…
$ ls | pbcopy
$ echo $?
1 # ウワッ、また終了コード 1 …
$ pbpaste
$ # もちろん何もペーストボードに入っていない…

解決策

1. reattach-to-user-namespace をインストールする

$ brew install reattach-to-user-namespace

2. .tmux.conf の冒頭に以下の設定を記述する

# お好みのシェルを指定してください。そのシェル環境で pbcopy とかが実行されます。
set-option -g default-command "reattach-to-user-namespace -l zsh"

3. tmux サーバを殺せ!

$ tmux kill-server

4. この後 tmux を再起動させると、pbcopy, pbpaste が使用できるようになります。

$ tmux
$ ls | head -2 | pbcopy
$ pbpaste
Desktop
Documents

何が悪かったのか

namespace

pbcopy/pbpaste は、その性質上、当然ながらペーストボードへのアクセスが必要です。
Mac OS X では、このあたりのアクセスを namespace と呼ばれるものによって制御しています。namespace は階層構造を持っていて、上位にある namespace は下位にある namespace で動いているサービスにアクセスが可能です。一方、逆はできません。
階層構造はだいたいこんな感じっぽくて、ペーストボードのサービスは、per-user bootstrap namespace に所属しています。

  • global bootstrap namespace (1つだけ作成される)
    • per-user bootstrap namespace (システム上のユーザ毎に作成される)
      • per-session bootstrap namespace (各ログインセッションで作成される)
tmux がペーストボードにアクセスできない

tmux はクライアントサーバモデルを採るソフトウェアであり、サーバは daemon(3) によって起動されます。で、どうも Mac OS X 10.5 から daemon(3) の仕様が変わったらしく、サーバが所属する namespace を root の namespace (その語感とは逆に下位の namespace らしい) に置くようになったのだとか。
そういうわけで、tmux からは、上位の namespace に所属しているペーストボードにアクセスできなくなった、というのが問題だった模様。

解決策がやっていること

reattach-to-user-namespace

問題を解決するために、tmux 上におけるコマンド実行の度に、ラッパプログラム (reattach-to-user-namespace) を実行することで per-user namespace に再接続を行うようにしています。
誤解させたくないのですが、tmux サーバを per-user namespace に接続させているわけではなく、あくまでコマンドを実行するランタイム上で再接続を行ってます。このあたりは reattach-to-user-namespace.c のソースを見てください。

また、これが tmux に本体に取り込まれていないのは、再接続は Apple の非公開 API で実施しているので、tmux の方にマージするわけにもいかないというのが理由です。そのため、ラッパプログラムでの回避、という方針になったわけですね。


tmux.conf に以下の設定を記述することで、

set-option -g default-command "reattach-to-user-namespace -l zsh"

外部プログラムを呼び出す際に、必ず reattach-to-user-namespace で user-namespace に接続済の環境でプログラムを実行できるようになります。
ということが ここ に書いてあるので読むといいとおもいます。