GNU screen encoding的替代品,自制BBS转码脚本

之前一直想从screen迁移到tmux上来,因为后者相比较前者而言对脚本的支持更好,我可以轻松的将常用的几个窗口(window)打包成一个会话(session),然后可以轻松的在一个脚本中将整个会话启动。但是一直下不定决心,因为screen有encoding功能,而tmux没有:

encoding enc [enc]

Tell  screen  how  to  interpret  the  input/output. The first argument sets the
encoding of the current window. Each window can emulate  a  different  encoding.
The optional second parameter overwrites the encoding of the connected terminal.
It should never be needed as screen uses the locale setting to detect the encod‐
ing.   There is also a way to select a terminal encoding depending on the termi‐
nal type by using the "KJ" termcap entry.

Supported encodings are eucJP, SJIS, eucKR, eucCN, Big5,  GBK,  KOI8-R,  CP1251,
UTF-8,   ISO8859-2,   ISO8859-3,  ISO8859-4,  ISO8859-5,  ISO8859-6,  ISO8859-7,
ISO8859-8, ISO8859-9, ISO8859-10, ISO8859-15, jis.

See also "defencoding", which changes the default setting of a new window.

简言之,encoding可以让你为不同的窗口(window)设置不同的字符集编码(charset),并根据你的设定自动进行编码转换(当然,并不仅限于此,后面会细说),对于我们这些使用Linux服务器在天朝教育网挂各大BBS站的同学而言,这个功能是十分重要的,因为大陆的BBS都是GBK编码的(据我所知是这样的),但是自己的Linux服务器的系统locale一般都是UTF-8,以前曾经写过一些screen后台挂站的文章:

  1. [FAQ]控制台下登陆BBS2. CLI环境之BBS攻略

有同学可能要说了,如果要转码的话,用luit不就行了?比如``luit –encoding GBK ssh –1 guest@bbs.seu.edu.cn’’,单纯从转码的角度来说,luit确实是足够强劲了,这里先岔开来介绍一下luit:

Luit  is  a  filter that can be run between an arbitrary application and a UTF-8 terminal emulator.  It will convert application output from the locale’s  encoding  into  UTF-8, and convert terminal input from UTF-8 into the locale’s encoding.

但是,luit在putty和gnome-terminal下面处理ASCII艺术(ASCII art or ANSI art)的时候是让人失望的,比如如下两幅截图:

View BBS through luitView BBS through screen

左图是用luit登陆BBS时的惨状,右图是使用screen登陆BBS时的美景,真是对比出差距,不服不行啊……

其实我一开始也纳闷,按道理这两软件在对问题的处理上,都是先打开两个PTY,一个是对子程序的,以后是输出给用户的,然后自己通过在中间倒腾数据的时候做了相应的转码,怎么差距这么大呢……原来是screen在处理这些图形字符(line drawing characters)的时候,人为的给每个图形字符后面加了一个空格(有兴趣的同学可以翻翻screen的源代码,真相在encoding.c这个文件的recode_char_dw函数中;或者自己抓包看看),所以图形变美观了。至于为什么要交空格,这个要涉及到GBK编码的字符宽度问题,本来这些图形字符在GBK中应该是和汉字等宽的,结果转UTF-8之后变得和英文字母等宽了,要是不补这个空格的话就会和luit一样乱套了,所以前面说screen干的不仅仅局限在狭义的转字符集编码,还把这个问题也考虑进去了。

好了,这么一说,screen太牛了,就算原生脚本支持不太好,用shell脚本(参见:Run script that sends commands to the screen session it is being run in)凑合一下也行啊,可惜screen在encoding上有一个bug,当你想设置encoding之后的窗口中输入图形字符(比如``※’’)的时候,这个符号会变成两个问号??,这可把我郁闷坏了,之前忍了好久了……其实这个bug在07年得时候就有人提交了,但是screen官方一直没动静,我翻了翻源代码,问题应该是处在encoding.c文件中的WinSwitchEncoding函数中,里面对字符宽度的计算出了问题,直接把这类图形字符变成了两个问号??,有兴趣的同学修改一下交给patch给开发者吧,功德无量啊~~

有了“变问号”和“原生脚本支持不足”这两点,已经足够让人又造轮子的打算了。不过我写这个工具之前还是用Google搜了一遍,确保没有重复的造轮子,用了如下关键词:screen tmux encoding’’,luit encoding tmux’’,``BBS ANSI tmux’’等等等等。

确实没有找到现成的轮子,然后我就用python写了这个工具(里面包含了pexpect_ng.py文件,从pexpect修改而来),里面对图形字符的处理和screen一样,用正则表达式在每个图形字符之后加一个空格;名字现在叫做seu_bbs,但是其实把seu_bbs.py这个文件中第159行的bbs.seu.edu.cn’’改成你喜欢的BBS域名比如bbs.newsmth.org’’,用来登陆任何华语BBS应该是不成问题的。文件打包在这里:seu_bbs.py 0.3版 <挂站必备工具>,里面也有一些介绍,这里不罗嗦了,贴一下。./seu_bbs.py -h的输出:

usage: seu_bbs.py [-h] [-a] [-e CHARSET] [-u USERNAME] [-t TIMEOUT] [-v]

convert charset of BBS to locale

optional arguments:
  -h, –help            show this help message and exit
  -a                    whether or not seu_bbs.py take whole window.[default
                        disable]
  -e CHARSET, –encoding CHARSET
                        Set up seu_bbs.py to use CHARSET encoding.[default
                        GB18030]
  -u USERNAME, –username USERNAME
                        Set up seu_bbs.py to use USERNAME login bbs.[default
                        guest]
  -t TIMEOUT, –timeout TIMEOUT
                        Do someting when idle(unit is seconds, negtive value
                        will be ignored).[default -1]
  -v, –version

这个工具目前还是有一些问题的,在从BBS的GBK转码为UTF-8的过程中,个别字符序列会转换不成功,我还没有详细的去调试到底是什么字符(估计是控制字符或者图形字符)导致转码失败,不过python内置的unicode函数倒是可以比较漂亮的忽略这个别转码错误,所以暂时就不管了,以后有时间再说吧。同时这个脚本的存在可以取代以往使用expect进行挂站,所以是完全的绿色版啊(除了需要python版本新于2.3以外)

最后要在赞一下pexpect模块的作者,代码的可读性太棒了!

btw:关于screen和tmux的比较,有两篇文章已经讲的很好了:

  1. [LinuxTOY]从 screen 切换到 tmux2. Screen vs tmux