rcmdnk's blog

20140218_layout_200_200

GNU screenで領域を分割したり レイアウトを設定したり、 それらをショートカットキーで簡単に変更したりする設定についてやった GNU screenでのレイアウトの整理 の不満があったところを改善したことについて。

Sponsored Links

以前のレイアウト/ウィンドウ分割設定の不満点

GNU screenでのレイアウトの整理

上のページのところで画面分割やレイアウトを初期化する コマンドをまとめてキーバインドに割り当てたりしました。

一番不満があったのは、現状のウィンドウ数とかを把握する術を知らなかったので、 例えば

bind s eval 'screen' 'screen' 'screen' 'source $HOME/.screen/4-windows.layout'
bind r eval 'source $HOME/.screen/4-windows.layout'

の様に、

  • s1: screenコマンド3回行って現スクリーンと合わせて最低4つのウィンドウを 作ってから画面を4分割
  • r: ウィンドウを新たに作らずに画面を4分割

みたいな事をして、最初はsを、一度作業したりしてレイアウトを 変更したりした後に4分割に戻すにはrを、みたいなことをしていました。

ただ、途中でウィンドウを色々つくってから 小さい番号の方を消してしまうと、0-3のウィンドウが無いと 4分割コマンド後にはそれぞれの領域に何も無い状態になったり、 iをしてしまうと余計にウィンドウが出来たりしてしまって余り賢くありませんでした。

レイアウトの方は消してもあまり弊害が無いので同じ名前のレイアウトが存在してたら それを消してリセット、みたいな事をしたのでそれなりにいい感じではありましたが。

レイアウトコマンド時の表示を変更(、のちに元に戻す)

キーバインドでCtrl-qレイアウトモード みたいな感じでコマンドを続けられるようにして、 ステータスラインに

layout

こんな感じで、キーバインドの最後にlayout showを呼んで表示させる様にしていました。

ここに、使えるキーの説明を付けたいと思って、

exec /bin/bash -c "screen -X echo \"$(screen -X layout show && screen -Q lastmsg), n: next, p: prev, i: init/reset, s: save, c: new, m: set, x: remove, <n>: goto <n>\""

こんな感じで、screen -Qlayout showを取ってきて、 そこにコマンドの説明を加えて、ということを考えて、 上のコマンドを~/.screen/layout_showと言うファイルに入れて、 各キーバインドの最後にlayout showの代わりにsource ~/.screen/layout_show と、上のコマンドを呼ぶようにしました。

そうするとこんな感じ。

layout_command

ただし、 GNU screenでscreen内の情報をシェルスクリプトで取り出す & 問題点 で書いたように、 screen -Qを使うと途中で表示が崩れたり、特にCygwinでは全く動かなかったりしたので この設定はボツに。。。

後、途中でmsgminwaitを変えて、一定時間経ったら コマンド表示、みたいなことをやってみようかとも思って

msgminwait 3
layout show
echo "n: next, p: prev, i: init/reset, s: save, c: new, m: set, x: remove, <n>: goto <n>"
msgminwait 0

こんなこともしてみたんですが、 GNU screenでscreen -Xを使うときの注意点 でも書いたように、msgminwaitが上手く機能してくれずに、 待たずにすぐに最後のコマンドの結果が出てしまうので上手くいかず。 (何か勘違いしてるのかバグなのか。。。)

最後のmsgminwait 0を外してもダメ。

さらにこれやってる時に、echoがファイルの中で使われて をれをsourceで呼ぶと全く効かない状態になりました。 上のコマンドをキーバインドに一つ一つ順番に割り当てると最後のechoのものが 表示されるのですが、 上のコマンドをファイルに書いて、それをsource fileで呼ぶと 何故かechoコマンドだけは実行されてるようですが、何も表示しない、 という状態になります。 (前のlayout showの結果が残ってるわけではないし、 command -c layoutみたいなクラス指定については効いているので キーバインド自体は実行されて、さらにechoの部分まではたどり着いていて何らか実行されてるけど すぐに表示が消えてしまう状態。。。?)

これを

exec /bin/bash -c "screen -X echo \"xxx\""

みたいに無理やりexecからのscreen -Xにすると表示が出るようにはなりました。 この辺も謎な部分。

色々やりましたが、結局あまりうまくないので、とりあえずあとで簡単に変えられる様に、 ~/.screen/layout_showファイルを使うのは残して、 このファイル内に単にlayout showを実行するだけ、にしておきます。

dotfiles/.screen/layout_show

ウィンドウの存在を確認する

簡単にはscreen -Q windowsによってシェルスクリプト等で現在のウィンドウ一覧が取れるわけですが、 screen -Qは問題があるので これは避けます。

どうするかというと、screen -Xを使うのですが、このコマンドでは 同時に-p nと言う引数を指定すると、n番のウィンドウを指定して コマンドを実行出来ることを利用します。

ウィンドウが存在しないとコマンドが実行されないので、 それにより存在をチェックします。

チェックする方法は、writebufを使います。 これが今のところ思いつくscreen -Q以外で唯一のscreen外へ情報を取り出す 方法なので。

具体的には、まず、コピーレジスター(.)が空だと書き出しも行われないので、 適当な言葉(win_test)をコピーレジスターに書き込みます 2

screen -X register . win_test

その後、各ウィンドウにwritebufコマンドを送ってファイルが書き出されているか 確認します。

rm -f ~/test_file
screen -p 0 -X writebuf ~/test_file
date >/dev/null
if [ -f $test_file ];then
...

こんな感じで。 途中でdateとか余計な物が入ってますが、 これが無いと環境によってはwritebufが終了する前にファイルのチェックを行ってしまって 上手くいかなかったのでちょっとした待ち時間を入れるために入れています。

これを使ってウィンドウを用意するために作ったスクリプトがこちら:

win_prepare.shlink
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
#!/usr/bin/env bash

MAX=20
function debug () {
  if [ "$DEBUG" = "1" ];then
    echo "$*" >> ~/log
  fi
}
if [ "$DEBUG" = "1" ];then
  log=~/log
else
  log=/dev/null
fi

n_windows=8
n_create=4
align=1
w_exist=()
w_non=()
if [ $# -gt 0 ];then
  n_windows=$1
  if [ $# -gt 1 ];then
    n_create=$2
    if [ $# -gt 2 ];then
      align=$3
    fi
  fi
fi

# Get first/last windows
# select doesn't work well in screen's commands...
# it seems that `screen -Q` makes very high load (at cygwin, screen -Q stops the terminal...)
#f_n=$(screen -X select - && screen -X next && screen -Q number|cut -d' ' -f1)
#l_n=$(screen -X select - && screen -X next && screen -X prev && screen -Q number|cut -d' ' -f1)
f_n=0
l_n=$((MAX-1))

n=0
non_exist=()

screen -X register . win_test >> $log 2>&1
mkdir -p ~/.screen/
test_file=~/.screen/win_test.txt
rm -f $test_file
for i in $(seq $f_n $l_n);do
  #msg=$(screen -p $i -X echo test && screen -Q lastmsg)
  #if [ "$msg" = "test" ];then
  screen -p $i -X writebuf $test_file >>$log 2>&1
  #usleep 1
  date >/dev/null
  #while [ 1 ];do
  #  if ! ps -u$USER |grep "screen -p $i -X writebuf $test_file";then
  #    break
  #  fi
  #  debug writing buffer....
  #done
  debug check $i
  if [ -f $test_file ];then
    debug "*** found $i"
    rm -f $test_file
    screen -X setenv win$n $i >>$log 2>&1
    w_exist=(${w_exist[@]} $i)
    ((n++))
    if [ $n -ge $n_windows ];then
      break
    fi
  else
    non_exist=(${non_exist[@]} $i)
  fi
done

for i in ${non_exist[@]};do
  debug try non_exist
  debug i=$i, n=$n, n_windows=$n_windows
  if [ $n -ge $n_windows ];then
    break
  fi
  if [ $n -lt $n_create ];then
    debug create non_exist $i
    screen -X screen >>$log 2>&1
    w_exist=(${w_exist[@]} $i)
  else
    debug push non_exist $i
    w_non=(${w_non[@]} $i)
  fi
  screen -X setenv win$n $i >>$log 2>&1
  ((n++))
done

if [ $align -eq 1 ];then
  n=0
  w_exist=($(for w in ${w_exist[@]};do echo $w;done|sort -n))
  w_non=($(for w in ${w_non[@]};do echo $w;done|sort -n))
  for i in ${w_exist[@]};do
    screen -X setenv win$n $i >>$log 2>&1
    ((n++))
  done
  for i in ${w_non[@]};do
    screen -X setenv win$n $i >>$log 2>&1
    ((n++))
  done
fi

#screen -wipe > /dev/null >>$log 2>&1

こんな感じ。 最初のループのところで、 番号を小さい方から適当な数(ここでは0-19)のウィンドウをチェックしていって、 ほしい数(n_windows)分だけあるかどうかチェックしています。

見つかった場合に、screen内でwin$nという変数にその番号を宛てる様にしています。

これを使ってあとで、select $win0と言うように順番に詰めていくことが出来ます。

さらに2番めのループでは飛ばされた番号3 があればそこに新たなウィンドウを、 3番目のループではそれ以降の番号にウィンドウを必要なだけ作っていきます。

n_windowswin$nで使いたい量、 n_createが最低限欲しいウィンドウの数です。 select $win7とやった時にwin7が定義されてないと下手なエラーを表示するので それを避けるため、ウィンドウが無くても使うwin$nは埋めておく、と言った感じで。 また、リセットされた時にこれらが正しく変更されるようにも必要です。

レイアウト設定

前に用意していた レイアウト設定スクリプトを

4-windows.layout
1
2
3
4
5
6
7
8
9
10
11
12
only
select $win0
split
split -v
focus
select $win1
focus
select $win2
split -v
focus
select $win3
focus

の様に変更して、これを呼ぶ前にwin_prepare.shを呼べば いくつウィンドウが存在していようとも必要な数だけ用意して それぞれの領域にアサインしてくれます。

上のスクリプトの中では最後にウィンドウをソートしていますが、 新しく出来たものは後ろに、としたいのであれば

$ win_prepare.sh 8 4 0

と3つめに引数として0を渡せばOK。 (面倒できちんと作ってないので引数の使い方が雑。。。)

これを使うと、4分割キーバインドは

bind r eval 'next' 'exec /bin/sh -c $HOME/.screen/win_prepare.sh' 'source ~/.screen/4-windows.layout'

の一つで良くなります 4

実際には .screen/layout.sh というスクリプトをもう一つかませて、 この中でwin_prepare.shを呼んで、 レイアウト名やらautosaveやらの設定もする、 みたいなことをしています。

これを、いくつかのレイアウトをまとめて作るスクリプト (.screen/all_layout.sh) でまとめています。

今回は、4分割を二つ作って、最初の4分割は autosaveoffにして基本維持、 2つ目の方をonにして変更がすぐに変更されるようにみたいなのを作っています。

autosaveoffにしているものがあるので、ウィンドウを切り替えたとかを保持したいとき、 saveを簡単にするために .screen/layout_save.sh というスクリプトも作りました。

saveするときには必ず名前が必要なので、 現在のレイアウトをそのまま保存、みたいなことをしたいときには 現在のレイアウトの名前を取ってくる必要があるのですが、 これが結局screen -Qを使う以外に上手く出来なかったので、 この中だけではscreen -Qを使っています(Cygwinでは使えない。。。)。

全てのレイアウト関連のシェル/screenスクリプトは~/.screenディレクトリに入れるようにしています。

dotfiles/.screen

dotfiles/.screenrc

Sponsored Links
  1. 以下のキーも含めてscreen内のキーバインドについてはCtrl-a(デフォルト)のエスケープキーは省略しています

  2. ここで元の値があれば保存してあとで戻す、みたいな事をしても良いですが、 multi_clipboard があれば特に問題ないかな、ということでとりあえず書きっぱなしに。

  3. それより大きな番号のウィンドウがあるが 消されたとかでウィンドウが存在しない番号。screenコマンドでウィンドウを作るときには 使われていない番号の中から低い方から順に新しいウィンドウができるので

  4. 最初のnextは、Windowが空の領域(select -で選べる領域)に居ると execコマンドが呼べないようなのでその対策。

Sponsored Links

« Macで入力ソースにいつの間にかU.S.が入ってきて消せない時の対処法 Vim+NeoBundleでユーザー名やパスワードを聞かれる現象 »