home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

技術/UNIX/端末やターミナルの文字化け対処(clear,reset,stty sane,tput,ncurses)

技術/UNIX/端末やターミナルの文字化け対処(clear,reset,stty sane,tput,ncurses)

技術 / UNIX / 端末やターミナルの文字化け対処(clear,reset,stty sane,tput,ncurses)
id: 1247 所有者: msakamoto-sf    作成日: 2013-12-31 12:58:02
カテゴリ: Linux UNIX 

ちょっと気になりまして、以下の記事に便乗してみました。

戻す方法として

echo ^[c

が紹介されてるわけですが、元記事ではこれを"clear2"にalias設定してまして、これがちょっと気になりました。

「clearコマンドと "echo ^[c" って何が違うの?」

というところですね。で、しかもはてブコメントを見てみますと"reset"コマンドでOK、というのもありました。他にも"stty sane"というのも出てきてます。

ちょっと確認してみます。

いずれも端末の設定や制御に絡んできてます。特にclear/resetのtput(1)というのは、以下で実験しているCentOS6系ではncursesが提供しているコマンドになりますのでドンピシャです(rpm -ql ncurses)。なおCentOS6系のncursesは 5.7 のバージョンになりまして、terminfoは "/etc/terminfo/" 以下ではなく、 "/usr/share/terminfo" 以下にありました(rpm -ql ncurses-base)。

ではtputってなんだろう?というところですが、以下の記事が参考になりそうです。

つまり、本来は端末ごとに制御コードが微妙に異なっていて統一されていないところを、terminfoというデータベースで共通の属性名にmappingされるようにしたことで、そうした端末ごとの細かい差異は気にせず、抽象化された属性名だけで操作できるようになりました、ということですね。多分。

あと参考になりそうなのはこの辺でしょうか。Linuxのncursesなら"terminfo"、BSD系だと"termcap"という名前の場合もあるようです。

どんな属性名で共通化されているか?それについてはterminfo(5)のmanページで確認できます。clear, reset コマンドで使われているのを拾い上げてみますと・・・

$ man 5 termifno

Variable                Cap-name   TCap Code    Description
clear_screen            clear      cl           clear screen and home cursor (P*)
exit_alt_charset_mode   rmacs      ae           end alternate char-acter set (P)
meta_off                rmm        mo           turn off meta mode
exit_underline_mode     rmul       ue           exit underline mode
reset_1string           rs1        r1           reset string
reset_2string           rs2        r2           reset string
reset_3string           rs3        r3           reset string

詳細は置いておくとして、resetだと上記すべてをtputで使ってますので、なんとなーく「色々不用意にONになっちゃったものをOFFにして、あとリセットできるものはリセットしておく」みたいな心意気を感じ取れますね。

実際の制御コードはどうなっているのでしょうか?CentOS6系でのterminfoを確認してみましょう。今回はCentOS 6.3で、ncurses系は以下のパッケージがインストールされてる状態で実験しています。

$ rpm -qa | grep ncurses | sort
ncurses-5.7-3.20090208.el6.x86_64
ncurses-base-5.7-3.20090208.el6.x86_64
ncurses-devel-5.7-3.20090208.el6.x86_64
ncurses-libs-5.7-3.20090208.el6.x86_64
ncurses-static-5.7-3.20090208.el6.x86_64
ncurses-term-5.7-3.20090208.el6.x86_64

まず、今ログイン中の端末の種類を確認します。WindowsのCygwinが提供しているmintty上でsshログインしているためか、"cygwin"であることを確認できました。

$ echo $TERM
cygwin

では、これに対応するterminfoファイルを探してみます。といっても/usr/share/terminfo/の下で、端末種類の頭文字でまずディレクトリが分けられてるのですぐに見つかります。

$ ls -l /usr/share/terminfo/c/cygwin
-rw-r--r--. 1 root root 1529 Aug 19  2010 /usr/share/terminfo/c/cygwin
$ file /usr/share/terminfo/c/cygwin
/usr/share/terminfo/c/cygwin: Compiled terminfo entry

"Compiled terminfo entry" と出てしまいまして・・・これだと専用のバイナリファイルになってますので、元々の人間が読める定義ファイルになってないようです・・・。
"/etc/terminfo/"は今回の環境では空っぽでしたので、ちょっと元の定義ファイルがわかりませんね。ncurses系パッケージはフルで入れてますので、ソースRPM見てみないとわからないかもしれません。

・・・が、最新かどうかはわからないのですが、おおよそどんな感じになっているのかは以下のサイトからDLして確認できました。

上のサイトから"terminfo"のりんくをクリックしてtermtypes.ti.gzをDLしたら展開します。termtypes.tiをテキストエディタで開いてみると、"clear"などtputの引数に対応する実際の制御コードの定義が確認できます。

...
ansi+erase, 
	clear=\E[H\E[J, ed=\E[J, el=\E[K, 
...

ただやっぱりこれが最新かどうかはわからないですし、ソースRPM展開して確認するのもいい加減面倒くさくなってきましたので、straceコマンドで見れるか試してみましょう。straceはプログラムのシステムコールと、もしファイルのI/Oがあればその中身も多少は見れる便利なデバッグツールです。まだ入れてない人は "yum install strace" で入れて下さい。

まずclearコマンドを試してみます。そのまま "strace clear" すると、clearコマンドが出力する制御コードで端末がクリアされてしまってstraceの出力が見れないという情けない状態になりますので、clearコマンドの出力をファイルにリダイレクトします。

※以下、Linuxのシステムコール系の関数の話が何の前提も無く出てきますが、一応コメントは入れますので、なんとなく「terminfoのファイルを読み込んで、cygwinの端末だとあーいう制御コードがclearに割り当てられるんだなー」というのを感じ取ってくれればまずはOKかと。

$ strace clear > clear.dat
execve("/usr/bin/clear", ["clear"], [/* 24 vars */]) = 0
(しばらくは実行ファイルを実行する準備部分はスルー)

(ここから、cygwin端末に対応するterminfoファイルを開き、中身を読み取ります。)
access("/etc/terminfo/c/cygwin", R_OK)  = -1 ENOENT (No such file or directory)
stat("/usr/share/terminfo", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
access("/usr/share/terminfo/c/cygwin", R_OK) = 0
open("/usr/share/terminfo/c/cygwin", O_RDONLY) = 3
read(3, "\32\1!\0\25\0\17\0}\1\237\2cygwin|ansi emulatio"..., 4097) = 1529
close(3)                                = 0

(ioctl(1)の第一引数が2=stderrなので、これは・・・なんでしょうかね。標準エラー向けに何かしてるのかな?)
ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(2, TIOCGWINSZ, {ws_row=30, ws_col=140, ws_xpixel=1120, ws_ypixel=510}) = 0

(これもよくわかりませんが、大筋には影響しないと思うのでスルーします)
fstat(1, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f932a679000

(ここが、write(2)の第一引数=1、つまり標準出力に制御コードを出力しているところになります。
write(1, "\33[H\33[J", 6)               = 6
exit_group(0)                           = ?

実際にclearコマンドの出力をリダイレクトしたデータファイルを覗いてみます。

$ hexdump -C clear.dat
00000000  1b 5b 48 1b 5b 4a                                 |.[H.[J|
00000006


1b 5b 48 = ESC [ H = \33[H
1b 5b 4a = ESC [ J = \33[J

となり、write(2)で標準出力に書き出しているものと一緒ですね。

resetはどうでしょうか?

$ strace reset > reset.dat
→なんか標準エラーも細工してるようでstraceの出力が崩れたので省略

$ hexdump -C reset.dat
(空っぽ)

→標準出力は細工してないようなので、標準エラーをファイルにリダイレクトしてみる。
$ reset 2>reset.dat
$ hexdump -C reset.dat
00000000  72 65 73 65 74 3a 20 73  74 61 6e 64 61 72 64 20  |reset: standard |
00000010  65 72 72 6f 72 3a 20 49  6e 61 70 70 72 6f 70 72  |error: Inappropr|
00000020  69 61 74 65 20 69 6f 63  74 6c 20 66 6f 72 20 64  |iate ioctl for d|
00000030  65 76 69 63 65 0a 0a                              |evice..|
00000037

→stat(2)かioctl(2)か何かでファイルディスクリプタの種類を判別しているっぽい・・・。

そういえば、straceって "-o" オプションでログファイルに書き出せるんだった。
$ strace -o reset.log reset
$ more reset.log
...
ioctl(2, TIOCGWINSZ, {ws_row=30, ws_col=140, ws_xpixel=1120, ws_ypixel=510}) = 0
ioctl(2, SNDCTL_TMR_STOP or TCSETSW, {B38400 -opost isig icanon echo ...}) = 0
write(2, "\33", 1)                      = 1
write(2, "c", 1)                        = 1    ## →上のと合わせれば "ESC c" になる!
write(2, "\33", 1)                      = 1
write(2, "]", 1)                        = 1
write(2, "R", 1)                        = 1
write(2, "\r", 1)                       = 1
nanosleep({1, 0}, 0x7fff2074f680)       = 0
ioctl(2, SNDCTL_TMR_STOP or TCSETSW, {B38400 opost isig icanon echo ...}) = 0
...

resetの場合は標準エラーの方に制御コードを送ってる様子を確認できました。しかもその中には、便乗元の記事にある "ESC c" の制御コードも(多分)含まれてるのを確認できました。

ただ、clearもresetも、tput(1)を起動している様子は確認できませんでした。stringsコマンドでバイナリ中の文字列も抜き出してみたんですが、tputs(man 3 curs_terminfo) は見つかったものの、tputは見当たらず。

$ strings /usr/bin/clear
...
tputs
...
$ strings /usr/bin/reset
...
tputs
...

lddで依存関係を見てみますと、ncurses-libsの提供している /lib64/libtinfo.so.5.7 に依存しています。

$ ldd /usr/bin/clear
        linux-vdso.so.1 =>  (0x00007fffaddff000)
        libtinfo.so.5 => /lib64/libtinfo.so.5 (0x00007f12c5d2d000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f12c599a000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f12c5f53000)
$ ldd /usr/bin/reset
        linux-vdso.so.1 =>  (0x00007fff35bff000)
        libtinfo.so.5 => /lib64/libtinfo.so.5 (0x00007fdcc5130000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fdcc4d9d000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fdcc5356000)
$ ls -l /lib64/libtinfo.so.5
lrwxrwxrwx. 1 root root 15 Nov 24 18:21 /lib64/libtinfo.so.5 -> libtinfo.so.5.7
$ rpm -qf /lib64/libtinfo.so.5.7
ncurses-libs-5.7-3.20090208.el6.x86_64

ということで、恐らくCentOS6系のclear/resetコマンドは、tputを呼び出す方式ではなく、ncursesに依存してそちらのtputs()関数を呼び出す方式になっていると思われます。

大体気になっていた点で押さえられるところは押さえられた気がしますので、一旦まとめます。

  • clearコマンドと "ESC c" の違いは?
    • clearコマンドはterminfoで共通化された"clear"コードを使って端末をクリアしている。terminfoに依存することで、端末間の制御コードの細かい差異を吸収できる。
    • "ESC c"がどの機能になるのかは、元記事からはTERMが読み取れなかったので(TeraTermっぽいスクリーンショットがあることから、vt100 or xterm系かその互換?)分からないが、clearコマンドではカバーしきれない機能に相当していた可能性がある。
  • 結局どれが良いのか?
    • まずシステムが提供しているコマンドが複数種類あるので、試すと良い。clear, reset, "stty sane" を試す。これで大体カバー出来るはず。
    • それでも駄目なら、まず "echo $TERM" して端末の種類を調べ、それと"clear", "reset" あたりをキーワードに組み合わせてググる。マイナーな端末種別だと、terminfo/termcapに存在せず、その端末の開発MLとかのアーカイブを調べると見つかるかもしれない。

いや~、端末制御コードのエスケープシーケンス一つからここまで話が膨らみまして、大変面白いですね~。

なお、以下に今回実験したCentOS6での各種パッケージバージョン情報を記載します。

$ rpm -qa | grep ncurses | sort
ncurses-5.7-3.20090208.el6.x86_64
ncurses-base-5.7-3.20090208.el6.x86_64
ncurses-devel-5.7-3.20090208.el6.x86_64
ncurses-libs-5.7-3.20090208.el6.x86_64
ncurses-static-5.7-3.20090208.el6.x86_64
ncurses-term-5.7-3.20090208.el6.x86_64

$ rpm -qf /usr/bin/clear
ncurses-5.7-3.20090208.el6.x86_64

$ rpm -qf /usr/bin/reset
ncurses-5.7-3.20090208.el6.x86_64

・・・clearもreestも、ncursesパッケージ提供だったんですね。そりゃ、わざわざtputをexecする必要も無いわけだ・・・。



プレーンテキスト形式でダウンロード
現在のバージョン : 1
更新者: msakamoto-sf
更新日: 2013-12-31 13:00:24
md5:7fae0bdaf9a902547fa484d88ee9c063
sha1:5ed313d5283a2a84b851417d5dbed98bd8368e31
コメント
コメントを投稿するにはログインして下さい。