rcmdnk's blog

tmux Taster

tmuxに移行するに当たり、GNU screenとWindow/Paneの取り扱いの違いがネックになりますが、 基本的にはscreen的な取り扱いが使い勝手が良いので tmuxでも同じように出来るように設定してみます。

Sponsored Links

やりたいこと

GNU screenでは各端末部分をWindowと呼び、 それらはscreenのセッション全体で管理されていて 分割表示しているような場合には管理しているWindowの中から 適宜選んで表示することが出来ます。

一方、tmuxでは各端末部分はPaneと呼ばれ、 WindowPaneをまとめた表示状態を指します。 各Paneは何れかのWindowに属していて、 そのWindowで必ず表示される様になっています。

tmuxに慣れてみる: tmuxとGNU screenの違いなど

screenの場合は複数端末を立ち上げて全体で管理してその時その時で 表示したいものだけを表示する、と言った感じ。 例えば分割表示してある中の1つのWindowだけ隠れてるWindowと交換、 みたいなことが簡単に出来ます。

一方、tmuxの場合は各Windowで必要な端末群を必要に応じて表示しておいて Window毎切り替えてそれぞれで使う、と言った感じ。(個人的感想。) こちらは現在表示されてるWindowの中の1つのPaneだけ他のWindowの 物と交換しようとするとswap-paneというコマンドがありますが、 現在表示されてないものが複数ある場合に順番に交換していく、みたいなことを するにはちょっと工夫が必要だったりします。

個人的にはscreenの様な使い方をしたいので、 tmuxの方で、

  • 最初のWindowsをscreenの通常表示画面
  • 2番めのWindowを、screenでの隠れてるWindowの管理場所

みたいな風にしてみたいと思います。

スクリプトの準備

tmux内のコマンドだけだと難しい点もあるので、 外部コマンドを使ってPaneの整理やステータス表示等を行います。

こんな感じのスクリプトを作ってPATHが通ってる所に置いておきます。

tmuxUtils
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
#!/usr/bin/env bash

function tmuxStatus {
  #printf "\e]2;$(hostname -s)\e\\"
  printf "\e]2;myhost\e\\"
}

function tmuxWindowName {
  IFS_ORIG=$IFS
  IFS=$'\n'
  wins=($(tmux list-windows -F "#I #{window_active}"))
  IFS=$IFS_ORIG
  current=0
  noview=0
  for line in "${wins[@]}";do
    iw=$(printf "$line"|cut -d' ' -f1)
    fw=$(printf "$line"|cut -d' ' -f2)
    if [ "$fw" -eq 1 ];then
      current=1
    else
      current=0
    fi
    IFS_ORIG=$IFS
    IFS=$'\n'
    panes=($(tmux list-panes -t $iw -F "#D #{pane_active} #{pane_current_path}"))
    IFS=$IFS_ORIG
    first=1
    status=""
    for line in "${panes[@]}";do
      ip=$(echo "$line"|cut -d' ' -f1|sed 's/%//')
      fp=$(echo "$line"|cut -d' ' -f2)
      pp=$(echo "$line"|cut -d' ' -f3)
      if [ $first -eq 1 ];then
        first=0
      else
        status="$status#[bg=colour238] #[bg=colour008]"
      fi
      if [ $current -eq 1 ] && [ $fp -eq 1 ];then
        status="$status#[bg=colour255]"
      else
        status="$status#[bg=colour008]"
      fi
      status="$status${ip}:$(hostname -s) $(basename $pp)"
    done
    status="$status#[bg=colour236] #[bg=colour008]"
    tmux rename-window -t :$iw "${status}"
  done
}

function tmuxLeft {
  IFS_ORIG=$IFS
  IFS=$'\n'
  wins=($(tmux list-windows -F "#I #{window_active}"))
  IFS=$IFS_ORIG
  current=0
  status=""
  noview=0
  for line in "${wins[@]}";do
    iw=$(printf "$line"|cut -d' ' -f1)
    fw=$(printf "$line"|cut -d' ' -f2)
    if [ "$fw" -eq 1 ];then
      current=1
    else
      current=0
    fi
    IFS_ORIG=$IFS
    IFS=$'\n'
    panes=($(tmux list-panes -t $iw -F "#D #{pane_active} #{pane_current_path} #T"))
    IFS=$IFS_ORIG
    first=1
    for line in "${panes[@]}";do
      ip=$(echo "$line"|cut -d' ' -f1|sed 's/%//')
      fp=$(echo "$line"|cut -d' ' -f2)
      pp=$(echo "$line"|cut -d' ' -f3)
      tp=$(echo "$line"|cut -d' ' -f4)
      if [ $first -eq 1 ];then
        first=0
      else
        status="$status#[bg=colour238] #[bg=colour008]"
        noview=$((noview+30))
      fi
      if [ $current -eq 1 ] && [ $fp -eq 1 ];then
        status="$status#[bg=colour255]"
        noview=$((noview+15))
      else
        status="$status#[bg=colour008]"
        noview=$((noview+15))
      fi
      status="$status${ip}@${tp} $(basename $pp)"
      #status="$status${iw}.${ip}:#h $(basename $pp)"
      #noview=$((noview-15))
    done
    status="$status#[bg=colour000]..#[bg=colour008]"
    noview=$((noview+30))
  done
  echo -n ${status: 0: $(($(tput cols)+noview-32))}
}

function tmuxAlign {
  wins=($(tmux list-windows -F "#I"))
  if [ ${#wins[@]} -lt 2 ];then
    return
  fi
  flag=$(tmux list-windows -F "#{window_active} #I"|grep ^1|sed 's/^1 //')
  if [ "$flag" -ne 0 ];then
    tmux swap-window -t:+
  fi
  for((i=2;i<${#wins[@]};i++));do
    panes=($(tmux list-panes -t ${wins[$i]} -F "#P"))
    for((j=0;j<${#panes[@]};j++));do
      tmux join-pane -d -s :${wins[$i]}.${panes[$j]} -t :${wins[1]}.bottom
    done
  done
}

function tmuxCreate {
  wins=($(tmux list-windows -F "#I"))
  if [ ${#wins[@]} -eq 1 ];then
    tmux new-window -d
    tmux swap-pane -d -s :+
  else
    tmux split-window -d -t :+.bottom
    tmux swap-pane -s :+.bottom
  fi
  tmuxAlign
}

function tmuxSplit {
  wins=($(tmux list-windows -F "#I"))
  if [ ${#wins[@]} -eq 1 ];then
    tmux split-window -v
  else
    tmux join-pane -v -s :+.top
  fi
  tmuxAlign
}

function tmuxVSplit {
  wins=($(tmux list-windows -F "#I"))
  if [ ${#wins[@]} -eq 1 ];then
    tmux split-window -h
  else
    tmux join-pane -h -s :+.top
  fi
  tmuxAlign
}

function tmuxOnly {
  wins=($(tmux list-windows -F "#I"))
  if [ ${#wins[@]} -eq 1 ];then
    tmux break-pane
  else
    IFS_ORIG=$IFS
    IFS=$'\n'
    panes=($(tmux list-panes -F "#D #{pane_active}"))
    IFS=$IFS_ORIG
    for line in "${panes[@]}";do
      ip=$(echo "$line"|cut -d' ' -f1)
      fp=$(echo "$line"|cut -d' ' -f2)
      if [ $fp -ne 1 ];then
        tmux move-pane -d -s ${ip} -t :+
      fi
    done
  fi
  tmuxAlign
}

function tmuxMove {
  wins=($(tmux list-windows -F "#I"))
  if [ ${#wins[@]} -eq 1 ];then
    tmux break-pane
    tmux select-window -t:+
  else
    IFS_ORIG=$IFS
    IFS=$'\n'
    panes=($(tmux list-panes -F "#D #{pane_active}"))
    IFS=$IFS_ORIG
    for line in "${panes[@]}";do
      ip=$(echo "$line"|cut -d' ' -f1)
      fp=$(echo "$line"|cut -d' ' -f2)
      if [ $fp -eq 1 ];then
        tmux move-pane -d -s ${ip} -t :+
      fi
    done
  fi
  tmuxAlign
}

function tmuxNext {
  tmux swap-pane -s :+.top
  tmux rotate-window -U -t :+
  paneinfo=$(tmux list-panes -F "#{pane_active} #D@#T #{pane_current_path}"|grep ^1|sed 's/^1 %//')
  tmux display-message "$paneinfo"
}

function tmuxPrev {
  tmux swap-pane -s :+.bottom
  tmux rotate-window -D -t :+
  paneinfo=$(tmux list-panes -F "#{pane_active} #D@#T #{pane_current_path}"|grep ^1|sed 's/^1 %//')
  tmux display-message "$paneinfo"
}

function tmuxClipborad {
  if [ "$CLX" = "" ];then
    if [[ "$OSTYPE" =~ linux ]];then
      if which -s xsel;then
        CLX="xsel"
      elif which -s xsel;then
        CLX="xclip"
      fi
    elif [[ "$OSTYPE" =~ cygwin ]];then
      if which -s putclip;then
        CLX="putclip"
      elif which -s xsel;then
        CLX="xsel"
      elif which -s xsel;then
        CLX="xclip"
      fi
    elif [[ "$OSTYPE" =~ darwin ]];then
      if which -s pbcopy;then
        CLX="pbcopy"
      fi
    fi
  fi
  if [ "$CLX" != "" ];then
    tmux save-buffer -| $CLX
    tmux display-message "clipboard: $(tmux save-buffer -)"
  else
    tmux display-message "No clipboard software is found."
  fi
}

if [ "$1" = "status" ];then
  tmuxStatus
elif [ "$1" = "winname" ];then
  tmuxWindowName
elif [ "$1" = "left" ];then
  tmuxLeft
elif [ "$1" = "create" ];then
  tmuxCreate
elif [ "$1" = "align" ];then
  tmuxAlign
elif [ "$1" = "split" ];then
  tmuxSplit
elif [ "$1" = "vsplit" ];then
  tmuxVSplit
elif [ "$1" = "only" ];then
  tmuxOnly
elif [ "$1" = "move" ];then
  tmuxMove
elif [ "$1" = "next" ];then
  tmuxNext
elif [ "$1" = "prev" ];then
  tmuxPrev
elif [ "$1" = "clip" ];then
  tmuxClipborad
fi

scripts/tmuxUtils

各関数は

  • tmuxSatus (status): 現在のPaneのタイトルを変更する(ホスト名にする)。
  • tmuxWindowName (winname): 現在のWindowの名前にPane情報等を表示。
  • tmuxLeft (left): 全WindowPaneの情報表示。(status-leftで使うつもりなのでleftにしてあります。)
  • tmuxCreate (create): 今いるPaneを次のWindowに移し、今いる領域に新しいPaneを作成して表示。
  • tmuxAlign (align): 3つ以上Windowがあったりする場合、2つ目以降のWindowを1つにまとめる。
  • tmuxSplit (split): 横分割して、もし他のWindowPaneがあればそれを持ってくる、無ければ新しいPaneを作成。
  • tmuxVSplit (vsplit): 縦分割して、もし他のWindowPaneがあればそれを持ってくる、無ければ新しいPaneを作成。
  • tmuxOnly (only): 今いるPaneだけのWindowを作成し、他のPaneを1つのWindowにまとめる。
  • tmuxMove (move): 今いるPaneを次のWindowに移動させる。
  • tmuxNext (next): 今いるPaneを次のWindowの最初のPaneと交換。
  • tmuxPrev (prev): 今いるPaneを次のWindowの最後のPaneと交換。
  • tmuxClipborad (clip): tmuxのクリップボードをOSのクリップボードへ渡す。

となります。

最初のPaneタイトルをホスト名にするところですが、これはsshの接続先でも この設定を入れておくことでタイトルに現在居るホスト名を出すことが可能になります。

tmuxWindowNameは使ってませんが、最初、 ステータス表示をWindow Nameを使って表示しようとした時に作ってみた物。

今はtmuxLeftの方を使って左領域にコマンド結果を出力するようにしています。

ステータス表示は、Window Nameの方はWindowの情報が変わる度に変更され、 status-leftに指定する左側領域は status-intervalで指定する時間(秒数)で変更されます。

上の様な設定でPROMPT_COMMAND等にtmuxUtils winnameを指定して毎回実行させる、 みたいなことをしてみましたが、 表示が変更されるタイミングはWindowが変更された直後なので これだと変更直後には上手く反映されません。

Window Nameでやりたい場合にはWindowの変更を行う前に名前を予め変えておいてから Windowを操作、みたいな事をしないといけません。 さらに内部でもともとあるコマンドとかも全部ラップかけないと上手くいかないので余り 上手くないので辞めました。

一方でstatus-leftの方は最短1秒毎で自動アップデートされるので、 一瞬タイムラグがありますが、Window Nameの場合と違って変更後必ず 反映はされるのでこちらにしました。

また、Window Nameは表示幅に比べて長すぎる場合、適当に余分な部分をカットして 表示してくれますが、 status-leftに長いものを入れると何も表示されなくなります。

そのため、最後に

echo -n ${status: 0: $(($(tput cols)+noview-32))}

と、適当に表示部分を短くしてますが、#[bg=colour008]等の部分は 実際には表示されないので差っ引いて考える必要があるので、 その分余分に表示するようnoviewという変数を入れています。 最後に32文字分引いてますが、この辺イマイチ表示文字と計算と合わないんですが、 このくらい引いておくと上手く表示されたので取り敢えずの数字。

また、tmuxに慣れてみる: tmuxとGNU screenの違いなど にも書いたとおり、tmuxではscreenやVimと縦分割、横分割の意味合いが逆なので、 上で言っている横分割はscreenやvimでの意味です。 (なのでコマンドは横分割の方が-vで縦分割の方が-hになっています。)

最後のはPane操作とは関係ありませんが、クリップボード渡し用の関数です。

.bashrc

ステータスを更新するため、

.bashrc
1
2
3
4
5
if [ "$TERM" = screen ]; then
  if [ "$TMUX" != "" ];then
    PROMPT_COMMAND="${PROMPT_COMMAND:+${PROMPT_COMMAND};}tmuxUtils status"
  fi
fi

.tmux.conf

.tmux.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# Split
#unbind-key '"'
#unbind-key %
bind-key s run 'tmuxUtils split'
bind-key S run 'tmuxUtils split'
bind-key C-s run 'tmuxUtils split'
bind-key v run 'tmuxUtils vsplit'
bind-key V run 'tmuxUtils vsplit'
bind-key C-v run 'tmuxUtils vsplit'

# window/pane
bind-key n run 'tmuxUtils next'
bind-key C-n run 'tmuxUtils next'
bind-key p run 'tmuxUtils prev'
bind-key C-p run 'tmuxUtils prev'

# create new window
bind-key c run 'tmuxUtils create'
bind-key C-c run 'tmuxUtils create'

# Only one pane, and reverse
bind-key q run 'tmuxUtils only'

# Move current pane to back
bind-key m run 'tmuxUtils move'

# Same size windows
bind-key r select-layout tiled \; run 'tmuxUtils align'
bind-key C-r select-layout tiled \; run 'tmuxUtils align'

# Change pane
bind-key h select-pane -L
bind-key j select-pane -D
bind-key k select-pane -U
bind-key l select-pane -R
bind-key -r C-h select-pane -L
bind-key -r C-j select-pane -D
bind-key -r C-k select-pane -U
bind-key -r C-l select-pane -R

# Resize pane
bind-key -r C-y resize-pane -L
bind-key -r C-u resize-pane -D
bind-key -r C-i resize-pane -U
bind-key -r C-o resize-pane -R

# Clipboard
bind-key a choose-buffer
bind-key C-a choose-buffer
bind-key A run "tmuxUtils clip"

# Border
set-option -g pane-border-style "fg=colour008,bg=colour008"
set-option -g pane-active-border-style "fg=white,bg=white"

# Status line
set-option -g status on
set-option -g status-interval 1
set-option -g status-style "bg=colour236,fg=black"
set-option -g status-left "#(tmuxUtils left)"
set-option -g status-left-length 1000
set-option -g status-right ""

set-window-option -g window-status-format ""
set-window-option -g window-status-current-format ""

set-window-option -g message-style "bg=colour255,fg=black"

まとめると通常作業では、

  • Prefix cで新規Paneを現在居る所に作成。
  • Prefix s/vで横分割、縦分割(既に隠れてるPaneがあればそれを持ってくる。なければ新しいPaneを作成して設置。)
  • Prefix h/j/k/lで左/下/上/右へPane間移動。
  • Prefix n/pで次/前のPaneを現領域に表示。
  • Prefix Ctrl-y/u/i/oPaneの幅を変更。
  • Prefix qで今いるPaneだけのWindowに。(Prefix zで一時的に一つだけ表示にして戻せる設定がデフォルトである。一方、この場合はリセットされるので一からレイアウトを作り直したいときに使う。)
  • Prefix mで今いるPaneを隠す。(次のWindowに移す。)
  • Prefix r等間隔になるようにPaneを並び替える。

Prefix nで今いるPaneと次のWindow(:+)の最初の Pane(.top)と交換して、次のWindowの順序を回します 1

これで、Prefix nを続けることで次々と 隠れているPaneを選択することが出来 screenとおなじ感覚で使えるようになります。

Prefix pの方は逆回り。

さらに、通常ステータスラインにはカレントディレクトリのディレクトリ名だけ 表示してるので、 選択時には全パス見れる様にdisplay-messageで表示を行っています。

Prefix n/pはデフォルトでWindowの移動に 割り当てられてますが、この設定では基本的にWindowは 最初の表示用Windowと残りのPaneを置いておくための WindowだけなのでWindowを移動する必要が無いので潰しました。

必要なら:next-window/previoous-windowで。

tmux

Sponsored Links
Sponsored Links

« tmuxに慣れてみる: tmuxとGNU screenの違いなど ブログがコピーされた事をGoogleアナリティクスでイベントトラッキングするトラッキングコード »