#navi_header|技術| ちょっと気になりまして、以下の記事に便乗してみました。 - 誤ってバイナリファイルを開いてしまってターミナルが文字化けした場合の対処方法 - みちしるべ -- http://orangeclover.hatenablog.com/entry/20110201/1296511181 戻す方法として echo ^[c が紹介されてるわけですが、元記事ではこれを"clear2"にalias設定してまして、これがちょっと気になりました。 「clearコマンドと "echo ^[c" って何が違うの?」 というところですね。で、しかもはてブコメントを見てみますと"reset"コマンドでOK、というのもありました。他にも"stty sane"というのも出てきてます。 ちょっと確認してみます。 - Man page of CLEAR -- http://linuxjm.sourceforge.jp/html/util-linux/man1/clear.1.html --- tput(1)を clear 引数を使って呼んでるそうです。 - Man page of RESET -- http://linuxjm.sourceforge.jp/html/util-linux/man1/reset.1.html --- こちらはtput(1)を clear,rmacs,rmm,rmul,rs1,rs2,rs3 引数を使って呼んでるそうです。 - Man page of STTY -- http://linuxjm.sourceforge.jp/html/GNU_sh-utils/man1/stty.1.html --- "sane"だと "cread -ignbrk ... echoke と同じ。また同時にすべてのスペシャルキャラクタをデフォルトの値に戻す。"そうです。 いずれも端末の設定や制御に絡んできてます。特にclear/resetのtput(1)というのは、以下で実験しているCentOS6系ではncursesが提供しているコマンドになりますのでドンピシャです(rpm -ql ncurses)。なおCentOS6系のncursesは 5.7 のバージョンになりまして、terminfoは "/etc/terminfo/" 以下ではなく、 "/usr/share/terminfo" 以下にありました(rpm -ql ncurses-base)。 ではtputってなんだろう?というところですが、以下の記事が参考になりそうです。 - tput を理解する -- http://www.ibm.com/developerworks/jp/linux/aix/library/au-learningtput/ つまり、本来は端末ごとに制御コードが微妙に異なっていて統一されていないところを、terminfoというデータベースで共通の属性名にmappingされるようにしたことで、そうした端末ごとの細かい差異は気にせず、抽象化された属性名だけで操作できるようになりました、ということですね。多分。 あと参考になりそうなのはこの辺でしょうか。Linuxのncursesなら"terminfo"、BSD系だと"termcap"という名前の場合もあるようです。 - Text-Terminal-HOWTO: Terminfo and Termcap (detailed) -- http://www.tldp.org/HOWTO/Text-Terminal-HOWTO-16.html - The Termcap Library -- http://www.delorie.com/gnu/docs/termcap/termcap_toc.html どんな属性名で共通化されているか?それについてはterminfo(5)のmanページで確認できます。clear, reset コマンドで使われているのを拾い上げてみますと・・・ #pre||> $ 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/termcap Resource Page -- http://catb.org/terminfo/ 上のサイトから"terminfo"のりんくをクリックしてtermtypes.ti.gzをDLしたら展開します。termtypes.tiをテキストエディタで開いてみると、"clear"などtputの引数に対応する実際の制御コードの定義が確認できます。 #pre||> ... 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かと。 #pre||> $ 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はどうでしょうか? #pre||> $ 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は見当たらず。 #pre||> $ strings /usr/bin/clear ... tputs ... $ strings /usr/bin/reset ... tputs ... ||< lddで依存関係を見てみますと、ncurses-libsの提供している /lib64/libtinfo.so.5.7 に依存しています。 #pre||> $ 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での各種パッケージバージョン情報を記載します。 #pre||> $ 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する必要も無いわけだ・・・。 #navi_footer|技術|