qemu で -nographic
や -serial mon:stdio
などでシリアルコンソールからログインすると、端末サイズが 80x24 扱いになって、表示が崩れることがあって不便なので、Arch Linux の Wiki を参考にして対処しました。
最終結果
.bashrc
に以下の内容を追加しました。
rsz () if [[ -t 0 ]]; then local escape r c prompt=$(printf '\e7\e[r\e[999;999H\e[6n\e8'); IFS='[;' read -sd R -p "$prompt" escape r c; stty cols $c rows $r; fi
rsz
zsh
でも同じものが使える予定だったのですが、 read -p
を使ってしまったので、共通で使えるものは、後述するように、たまに ^[[80;200R
のような表示が出てしまうことがありますが、以下になります。
rsz () if [[ -t 0 ]]; then local escape r c; printf '\e7\e[r\e[999;999H\e[6n\e8'; IFS='[;' read -sd R escape r c; stty cols $c rows $r; fi
参考
Resizing a terminal や ターミナルのサイズ変更 の以下の実装を参考にしました。
rsz() {
if [[ -t 0 && $# -eq 0 ]];then
local IFS='[;' escape geometry x y
print -n '\e7\e[r\e[999;999H\e[6n\e8'
read -sd R escape geometry
x=${geometry##*;} y=${geometry%%;*}
if [[ ${COLUMNS} -eq ${x} && ${LINES} -eq ${y} ]];then
print "${TERM} ${x}x${y}"
else
print "${COLUMNS}x${LINES} -> ${x}x${y}"
stty cols ${x} rows ${y}
fi
else
[[ -n ${commands[repo-elephant]} ]] && repo-elephant || print 'Usage: rsz' ## Easter egg here :)
fi
}
Wiki の説明にあるように、 xterm
パッケージをインストールしても良いなら、 resize
コマンドを使う方が簡単です。
動作確認のために最低限の実装に変更
Easter egg などは不要で、 if
での条件分岐も削って以下のような実装で確認しました。 zsh 依存をなくすため、 print
も printf
に変更しました。
rsz() {
local IFS='[;' escape geometry x y
printf '\e7\e[r\e[999;999H\e[6n\e8'
read -sd R escape geometry
x=${geometry##*;} y=${geometry%%;*}
stty cols ${x} rows ${y}
}
printf の内容
対応制御シーケンスを参考にして内容を読みとくと、以下のようになりました。
-
\e7
=ESC 7
= カーソル位置を保存する -
\e[r
=CSI r
= DECSTBM = 上下マージン(スクロールリージョン)を設定する。引数を省略しているので 1 から画面下端。 -
\e[999;999H
=CSI Ps1 ; Ps2 H
= CUP = カーソルを Ps1 行目の Ps2 桁目に移動する。(大きい値を指定することで右下に移動している。1000 以上のサイズの端末なら誤動作しそう。) -
\e[6n
=CSI Ps n
= DSR = 端末の状態を報告する。 Ps = 6 なので「カーソルの位置を報告する。(CPR)」で、応答:CSI r ; c R
-
\e8
=ESC 8
= DECRC = 保存したカーソル位置を復元する (ESC 7
で保存した元のカーソル位置に戻す)
「左右マージン(スクロールリージョン)を設定する。DECLRMM がセットされている時のみ有効。」という機能もあるようですが、滅多に設定されていないのに対応状況の調査が必要になって複雑になるからか、対応していないようです。
read の引数修正
bash の man の説明によると、「余った単語とそれらの間の区切り文字は、最後の name に代入されます。」ということなので、 IFS
に ;
も入れているのに read
で分割されていないのは引数に渡している変数名不足が原因とわかりました。 そこで、引数を増やして Parameter Expansion で取り出している処理を削りました。
rsz() {
local IFS='[;' escape r c
printf '\e7\e[r\e[999;999H\e[6n\e8'
read -sd R escape r c
stty cols $c rows $r
}
IFS の位置変更
IFS
の変更が必要なのは read
だけなので、 read
だけに影響する位置に変更しました。
rsz() {
local escape r c
printf '\e7\e[r\e[999;999H\e[6n\e8'
IFS='[;' read -sd R escape r c
stty cols $c rows $r
}
if を再度追加
関数の本体は複合コマンド (Compound Commands) であれば { list; }
の代わりに if list; then list; fi
でも良いので、 {}
を if ... fi
に置き換えました。 わかりにくいし、他の文を追加するときにバグが入りやすくなるので、普通は {}
でくくる方が良いでしょう。
条件の $# -eq 0
は不要だったので削りました。
そしてコピペしやすいように 1 行にしました。
rsz () if [[ -t 0 ]]; then local escape r c; printf '\e7\e[r\e[999;999H\e[6n\e8'; IFS='[;' read -sd R escape r c; stty cols $c rows $r; fi
たまに応答が表示されてしまうのを修正
printf
してから read
の -s
オプションで非表示にするまでの間に応答が来てしまうと、たまに ^[[80;200R
のような表示が出てしまうことがあります。 printf
の前に stty -echo
で止められるのですが、 stty -g
を保存しておいて復元が必要になって複雑になってしまうので、避けました。 代わりに bash
依存の read
の -p
を使うことにしました。
rsz () if [[ -t 0 ]]; then local escape r c prompt=$(printf '\e7\e[r\e[999;999H\e[6n\e8'); IFS='[;' read -sd R -p "$prompt" escape r c; stty cols $c rows $r; fi
この状態で .bashrc
に追加しました。
まとめ
簡単な処理だと思っていたら、良い感じにしようとしたら意外と面倒でしたが、それなりにシンプルな感じにできました。