ちょっと気になりまして、以下の記事に便乗してみました。
戻す方法として
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()関数を呼び出す方式になっていると思われます。
大体気になっていた点で押さえられるところは押さえられた気がしますので、一旦まとめます。
いや~、端末制御コードのエスケープシーケンス一つからここまで話が膨らみまして、大変面白いですね~。
なお、以下に今回実験した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する必要も無いわけだ・・・。
コメント