7
29
2012
18

调教火狐14&15:地址栏显示网页图标,以及总结

好吧,我确认 Mozilla 已经脑残了,各种我喜欢的特性正在被去除,而我不喜欢的特性正在从 Google Chrome 抄袭过来。

火狐14开始,不再在地址栏显示网站图标了。Mozilla 说这样更安全,可我觉得,对于网站弄个挂锁图标就可以骗过的用户,地址栏图标去掉了他们依然会中招。而标签栏上的图标,我只用来识别标签页。地址栏图标的优势在于,不管标签页在哪里,它的位置总是固定的,用户不需要去判断当前标签页在哪里。于是我费了好久,终于通过查询火狐的源码库把这个特性加回来了。要补丁的请点击此处,我也提供打包好的 omni.ja 文件GFW认证。其中包含了自火狐7以来失去的双击标签页组创建新标签页的修改。

另外,火狐14地址栏默认自动填充到域名。可是我要域名干什么呢——我要访问的是页面!谁没事老去看人家网站的首页啊,当 RSS 不存在似的……好在我们还有个选项:browser.urlbar.autoFill。把它设置成false就可以了。


接下来,让我们怀念一下那些已经不再默认或者已经去除的特性(链接为找回该特性的办法)——

2012年8月30日更新:针对火狐15的补丁omni.ja文件地址不变。

2012年10月16日更新:针对火狐16的补丁omni.ja文件地址不变。

2013年4月16日更新:针对火狐20的补丁以及omni.ja文件地址在上述 Wuala 网盘地址中。

2013年11月27日更新:对于火狐 21 及以后,参见这里通过 userChrome 脚本的实现。

Category: 火狐 | Tags: 火狐
7
27
2012
8

fcitx-remote 接口通过 socat 跨主机使用

在使用 Mac OS X 时,我十分想念 fcitx.vim 插件在使用 Vim 时能智能切换输入法的激活状态。所以我换回 Arch Linux 了。关于 Mac OS X 与我的「不兼容」还是留到下次再说,这次解决的问题是,当我 ssh 到另一主机上使用 Vim 时,如何让 fcitx.vim 能够控制本机的输入法状态?

fcitx-remote 接口使用的是 UNIX 套接字文件,因此天生是不能跨主机通信的(因此不用担心局域网里其它人捣乱)。现在,为了进行跨主机通信,当然要使用网络套接字了。既然都是套接字,转发下就可以了嘛。于是想到 socat。

在远程机器监听一个套接字文件,转发到本地机器的 8989 端口:

socat UNIX-LISTEN:/tmp/fcitx-remote.sock,fork TCP:192.168.2.142:8989

在本地监听网络 8989 端口,转发到本地 fcitx 的套接字:

socat tcp-listen:8989,fork UNIX-CONNECT:/tmp/fcitx-socket-\\:0

fcitx.vim 使用更新后的 1.2 版,然后告诉它你要使用的套接字文件地址:

export FCITX_SOCKET=/tmp/fcitx-remote.sock                                                                                                                                               

然后就可以啦~

最后,贴一张测试过程中抓到的 htop 的图片,2 万多进程哦,htop 已经卡了,实际的 load 请看右下角的红色数字。我执行killall socat命令后等了几分钟,终于因为内存耗尽系统开始重新缓慢工作了。数次 killall 后终于恢复正常……再次测试前果断先ulimit -u 1000 :-)

Category: Linux | Tags: fcitx vim 网络 socat
6
20
2012
4

使用 pygit2 创建提交

pygit2 是 libgit2 的 Python 绑定,而 libgit2 是一个可动态链接的 git 库,除去头文件和 pkgconfig 信息就一个 .so 文件。它是我在 The Architecture of Open Source Applications(AOSA)第二巻讲 git 的部分中看到的。git 本身遵循了传统的 Unix 哲学,提供了一系列的命令来管理源码库。这对于 shell 脚本是非常不错,可是对于嵌入到其它应用(如 IDE、Web 服务)中却不太好用。于是,我们有了 libgit2。

很遗憾的是,我并没有找到 API 文档,只有一些示例性的用法介绍,更别提教程之类。即使在 pygit2 中,使用help命令能够得到的信息也很有限。所以,我只能在 Python 这样动态语言的交互式会话时独自探索。

下面是我搜索出来的使用 pygit2 进行提交的过程:

导入需要用到的模块:

import pygit2
import time

我的 git 仓库,还有 index:

repo = pygit2.Repository('/home/lilydjwg/.vim/.git')
ind = repo.index

先看看未提交到 index 的修改(相当于git diff

print(ind.diff())

唔,我看到就一个plugin/colorizer.vim文件修改了。把它加到 index 中(相当于git add)。如果是git rm的话就用del ind[filename]了。操作之后要调用write()方法写入更改。

ind.add('plugin/colorizer.vim')
ind.write()

写入 tree 对象,其返回值是二进制编码的 hash 值(使用binascii.b2a_hex可编码成 git 命令中使用的字符串)

oid = ind.write_tree()

作者和提交者的信息,其中最后一个参数(offset)是以分钟计的时区偏移(当然是相对于 UTC)。邮件地址很显然被打码了 :-)

author = pygit2.Signature('依云', 'a@b.c', int(time.time()), 480)

创建提交。其中HEAD是个「符号引用」(symbolic reference),而repo.head就是当前最后一个提交了,oid属性还是二进制编码的 hash 啦。这里,提交者和作者是同一人,因此我都使用刚刚创建的author对象了。这步就是git commit命令了。

repo.create_commit('HEAD', author, author, 'colorizer: solved name color conflict', oid, [repo.head.oid])

在命令行下看看结果是否正确:

>>> git cat-file -p HEAD
tree 20e8937d41b6df16da2c8c5661f9c4a8dd31b5a1
parent ab9c662ce0d1cb2deac7a9ae388ecb40d8ec5e15
author 依云 <a@b.c> 1340188028 +0800
committer 依云 <a@b.c> 1340188028 +0800

colorizer: solved name color conflict
Category: python | Tags: python Git
6
17
2012
13

彻底关闭火狐13新建标签页的缩略图导航

我一直在反对新版火狐某些特性。火狐13在新建标签页时添加了和 Opera、Google Chrome 一样的缩略图导航,这个对我一点用处也没有,因此也得干掉。

我并不满足于单纯地点击右上角那个小图标,因为这样之后,在打开新标签页时,我还是能看到 Status-4-Evar 显示于地址栏的进度条一闪而过。虽然加载新标签页的速度依然远快于 IE8,但是我不会轻易满足的。打开 about:config 页面,搜索「newtab」,我找到了这么一项:

browser.newtab.url,默认值是about:newtab。果断将其改成about:blank,恢复之前的空白页。

2012年6月27日更新:根据这篇文章,如果要禁止火狐在后台生成缩略图,还需要将选项browser.newtabpage.enabled置为false。(感谢巴蛮子指出。)

Category: 火狐 | Tags: 火狐
6
11
2012
11

rpysh——Windows Python 命令行也要 readline!

rpysh 是为习惯 Linux 的 Pythoners 在不得不处理 Windows 上的事务时写的远程 shell。

源起

前些天,我尝试了使用 Python 控制 Word。但我对 Windows 下的交互式 Python shell 很不满意。

首先,我尝试的是 cmd.exe 那个黑窗口。太难用了!只有最基本的行编辑、在不知不觉中历史记录被窜改、复制粘贴极其麻烦。补全当然也是没有的。

于是,尝试 IDLE。这家伙我选了「IDLE Classic Unix」,但是能工作的键并不多。比如我刚刚尝试的Ctrl-u就不管用。而Ctrl-p竟然是把光标向上移动,回车才会把那行的内容取到输入命令的那行。这样一来,想再次执行最后一条语句,需要视上条命令输出的行数按几下Ctrl-p。另外,鼠标在窗口内点击后光标会被移开。这样,我使用鼠标从其它窗口切回来时,还得再手动定位光标,极其不爽。至于补全么,太智能了,所以在我输入时不时会出现这种情况:

乱七八糟的补全

还有一个问题:我查资料、做笔记、写代码都在 Linux 上,虽然Ctrl-CCtrl-V在物理机和虚拟机间能够无缝操作,但比起选中+中键粘贴的 X 主选区还是麻烦多了!

没办法,我只好重拾很久以前的想法——写个程序,在 Linux 上操作,在 Windows 上执行!

——等等!这和 ssh 差不多吗?或者 telnet?

——不不,Cygwin 的 ssh 跑不了 Windows 控制台程序,而且,不还是没 readline 支持么?

实现

毫无疑问是网络通信了。距离上一次不成功的尝试已经过去很久了,我不仅更加了解了code模块的能力,也知道 Python 命令行补全是怎么回事了。也就是说,Windows 版的 Python 是有补全的接口的,只是没有 readline 的等价物来调用。跑在 Windows 上的服务端要完成以下操作:

  1. 重写相关方法,把用户数据由标准输入改到从客户端读取
  2. 标准输出重定向到网络 socket
  3. 收到客户端的补全请求后,使用rlcompleter模块获取补全结果,再回送给客户端

对于第一点,实际上取代code.InteractiveConsole实例的raw_input方法就行。它和内建的input()函数具有相同的输入和输出形式,也就是会接收命令提示符。将这个直接发给客户端好了。

第二点很简单,直接socket.makefile然后把sys.stdout指过去。

第三点,为了简单起见,我另开了个线程和 socket,专门用于补全。需要传递的参数和返回值全部 pickle 了扔给对方就是了。

写完这些我才发现,其实我的raw_input方法和补全函数具有相似的执行逻辑:发送参数到网络,再从网络获取执行结果——也就是远程过程调用呵。

使用方法

rpyshd.py可选一个参数作为端口号,为方便起见,提供默认值8980。也是为了方便双击执行起见,我添加了.py后缀。

rpyshc相当于telnet命令了,直接接主机地址和端口号两个参数即可。

缺陷

  • 从标准输入读数据时在服务端
  • 偶尔提示符出现不及时
  • 虽然我实现了Ctrl-C,但是实际上没什么用,因为收到消息时之前的操作肯定已经执行完了
Category: python | Tags: linux python readline windows
6
6
2012
12

编程获取本机IPv4及IPv6地址

首先,我要通过编程直接获取,而不是去读诸如ifconfig等命令的输出。

其实是只想获取IPv6地址的,不过我猜想它们差不多,也确实看到不少相关搜索结果,于是顺带着看了。

首先,使用gethostbyname查自己通常是不行的,因为可能得到127.0.0.1,而且我猜,这样不能处理拥有多个IPv4地址的情况。另外一种方式是连上某个主机,然后调用getsockname。这样需要能够直接连上那个主机,好处是如果有多个网络接口,这样可以知道到底走的是哪个接口,调试网络时不错。我最满意的方案在这里,使用ioctl来获取。这个方法可以获取指定网络接口的IPv4地址。至于有哪些网络接口嘛,直接读/proc/net/dev吧。

import fcntl
import socket
import struct
ifname = b'eth0'
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 0x8915 是 SIOCGIFADDR
ip = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15]))[20:24])
print(ip)

然而,这样只能获取IPv4地址。创建个AF_INET6的 socket 传过去会报错「Inappropriate ioctl for device」。那怎么办呢?Google 没找到,我去搜了下内核源码。inet_ioctl里有对SIOCGIFADDR的处理。但是,inet6_ioctl里却没有了。

于是,我只好去下载ifconfig所属的 net-tools 的源码,找到相关代码:

#if HAVE_AFINET6
    /* FIXME: should be integrated into interface.c.   */

    if ((f = fopen(_PATH_PROCNET_IFINET6, "r")) != NULL) {
    while (fscanf(f, "%4s%4s%4s%4s%4s%4s%4s%4s %08x %02x %02x %02x %20s\n",
              addr6p[0], addr6p[1], addr6p[2], addr6p[3],
              addr6p[4], addr6p[5], addr6p[6], addr6p[7],
          &if_idx, &plen, &scope, &dad_status, devname) != EOF) {
        if (!strcmp(devname, ptr->name)) {
        sprintf(addr6, "%s:%s:%s:%s:%s:%s:%s:%s",
            addr6p[0], addr6p[1], addr6p[2], addr6p[3],
            addr6p[4], addr6p[5], addr6p[6], addr6p[7]);

这里就是ifconfig输出IPv6部分的代码了。可以看到它打开了一个奇怪的文件。跟过去,发现是

#define _PATH_PROCNET_IFINET6       "/proc/net/if_inet6"

囧,这个文件我早就发现过了的。看来和IPv4的情况不同,IPv6地址只能通过/proc里的文件获取了。而且输出成人可读格式不容易(ifconfig是自己实现的)。

PS: 我还发现了件好玩的事,在 Linux 源码的include/linux/sockios.h中,SIOCGIFINDEX中的字母 C 写漏了。通过git blame我发现,这个拼写错误在至少七年前 Linux 内核代码迁移到 git 前就修正了。Linus Torvalds 说之前的代码导入到 git 后有 3.2GB。我不得不承认这是个无比正确的决定,因为现在的.git已经有600多兆了,git 不支持断点续传,clone 下来已经很不容易了。

另外,我还联想到了 Unix 系统调用中的creat,以及 HTTP 协议中的referer :D

#define SIOCGIFINDEX    0x8933      /* name -> if_index mapping */
#define SIOGIFINDEX SIOCGIFINDEX    /* misprint compatibility :-)   */
Category: Linux | Tags: C代码 linux python 网络
5
14
2012
8

xmpptalk 聊天机器人及 Gtalk 群推荐

xmpptalk是一个搭建 XMPP 群(通常称 Gtalk 群)的软件。它使用 Python 编写,但与之前的 gaetalk 不同,它不受限于平台,而是可在任何 Linux 系统上均可运行,比如各种 VPS。其它类 Unix 平台尚未测试,但是即使有问题,也应该能够很快解决。

如何搭建

搭建 XMPP 群首先需要有一台运行类 Unix 系统的服务器。其次需要一个 XMPP 帐号。请不要使用@gmail.com的帐号,因为频繁地发送带链接的消息,或者过快地发送消息,会被 Gtalk 阻止。这里有一个免费 XMPP 服务器列表。XMPP 服务器也可自行搭建,推荐使用prosody。我曾经遇到 ejabberd 在发送长消息时网络阻塞严重,甚至导致机器人与服务器的连接断掉。

然后是 xmpptalk 的使用。目前版本的 xmpptalk 还处理 Alpha 阶段,搭建有些复杂,有不少依赖,请参阅这里的简要说明。主要的依赖有:

  • Python 3.2+
  • MongoDB
  • pyxmpp2
  • mongokit

其中 mongokit 是修改过的,请从安装脚本中寻找地址并下载。

因为依赖复杂,所以我在项目的scripts目录下提供了一个自动化脚本quickinstall.sh以便安装各种依赖,请直接运行(而不要用sh来运行)。不过如果源里有的软件,还是推荐从源里安装(比如那个脚本需要安装 git 和 hg)。在同一目录下还有 MongoDB 的示例配置文件以及建立相关目录和用户的脚本。

配置群,请修改config.py文件。此文件是 Python 语法,请按注释进行配置。

依赖都满足并且配置完成后请运行以下命令对数据库进行初始化:

python3 dbman.py

如果没有出错说明一切正常,可以运行./main.py开群了。如果出错了请修正后重新运行。不过要是数据库已经建立的话,请先使用 Mongo Shell 删除数据库。

如果希望群在后台运行(而不是像我把它放在 tmux 会话中),可以使用如下命令:

./main.py --fork

注意:由于未知原因,群对成员的在线与否可能会出错,建议每隔一段时间重启一次(管理员使用-restart命令即可)。

2012年8月24日更新:StarBrilliant 写了一篇更为详细的安装与配置记录

2013年3月17日更新:感谢苏学姐,她写了一篇关于在 OpenShift 搭建群的详细教程

群的简单使用

加入群只需要添加群帐号为好友即可。成功的话会收到一条欢迎消息,告知用户的默认昵称。这个昵称从用户的设置信息(vCard)中读取,如果失败,会生成一个在本群内唯一的 id,其前半部分是用户 JID 的用户名,后半部分是加盐 hash 后的域名,以防止用户 JID 泄漏。不过鉴于大部分用户都是@gmail.com,所以有心人还是可以猜,不过没有办法确定猜得是否正确(除非能够访问群数据库或者配置信息)。

欢迎信息会告诉新加入的用户使用-nick命令可以修改自己在群里的昵称。像很多其它的 XMPP 群一样,本群软件提供了不少用户命令。使用-help可以获得一个简要帮助。为了方便用户,实际上求助的命令是模糊匹配的,在所有我去过的群中的求助命令都会有效。群命令全部为小写,大写无效。并且,命令前不可有空格,否则作为普通消息发给群成员。

昵称有一些限制,主要是不允许各种标点和特殊符号,以及长度有限。昵称的最大「宽度」(一个汉字相当于两个英文字母)和昵称中允许出现的非字母字符是可以配置的。

本群软件支持用户间的私信,使用-pm 对方昵称 消息内容发送。因为昵称里可以有空格,所以这里的昵称允许使用\来转义,也可以用双引号引起来。私信设计为向特定用户发送不想让其它人看到的消息,比如自己的 JID 或者邮件地址。它不宜用作私下交流;此情况请加对方单聊。私信的发送是不可靠的。群总是会把私信发出去,但是不确定对方是否成功收到(比如某些服务器会拒收离线消息,又或者暂时无法连接对方服务器)。所以私信的发送并不会像其它命令一样会有回应。

如果暂时不想接收群消息,可以让群在一定时间内不向自己发送消息。命令为-stop 时间。时间可指定单位m(分钟)、h(小时)、d(天)。不指定则为秒。如-stop 2h就会在接下来的两小时收不到群消息。私信不会被阻止。在停止接收群消息的时候可以使用命令,包括使用-stop命令来修改要暂停的时长。如果在停止接收群消息的时候发送消息,或者发送ping消息,停止状态将取消。

ping消息是一个特殊的消息。向群发送只包含ping的消息,可以用于以下情况:

  • 看看自己是不是掉线了,以及群是不是出故障了
  • 取消停止接收群消息
  • 查看当前的日期时间星期几(时区在群里配置里,可用-about命令查看)

群推荐

注意:为了避免有人不停改昵称给正常聊天造成困扰,以下介绍的群,技术群的昵称每十天才允许改一次,而水群是三天。不过如果手误改错了可联系管理员修正。

技术群 JID:test@vim-cn.com

这是一个关于 Vim、Linux、Python 等的讨论群。无关话题最好不要讨论,可能会被禁言的哦。另外有个 GTK 专用群:mop@vim-cn.com。

水群 JID:water@vim-cn.com

此群不限话题,但不建议大量讨论政治、军事、游戏、IT技术等等具有非常明确的话题归类的内容。

最后,贴个自己搭建的网页版客户端,方便没有客户端或者客户端不给力的人使用:https://chat.vim-cn.com/,可以登录任何互联网上的 XMPP 服务器。证书是自签名的,只作加密用。

Category: python | Tags: python XMPP
5
12
2012
0

用 Python 控制 MS Word 之手册在哪里

通过 pywin32 模块使用Python來控制MS Word的文章不少,可是看过后我发现他们有一个共同的问题:参考手册在哪里?

因为是通过 COM 接口进行通信的,不能使用help()或者dir()命令来获取 API 帮助,所以迫切地需要详细的文档,才能知道那些对象拥有的属性和方法。我在网上尝试找了 VBA 的参考手册,这个微软倒是有,但没找到离线版。虽说有也多半是 Windows 专用程序来阅读,颇为不便,但至少不受制于网络嘛。最后找来找去,原来 Word 2010 自带了!一如既往地隐蔽哦。点右上角的帮助图标出现「Word 帮助」窗口后,点「搜索」按钮的下拉箭头,选择「开发人员参考」,里边那个「Word 2010 开发人员参考」就是了。

「开发人员参考」菜单项

别问不同的帮助主题和「搜索」有什么关系,也别问「Visual Basic 语言参考」和「Microsoft 窗体参考」为什么会在这里。微软一向是忽视逻辑的。

另外,工具栏最右边那两个按钮也很有用。

4
29
2012
53

放弃 gnome-terminal,转到 Xfce 终端

前日进行了系统升级,结果分外悲剧。在 Awesome 下,Empathy 和 gnome-terminal 都不能正常使用了。当然,Awesome 并没有升级,它很稳定。关于 Empathy 的事下篇再写,本篇只吐槽 gnome-terminal。

早些时候,我就发现我经常不能在 gnome-terminal 中成功打开输入法。昨天将 gnome-terminal 从 3.2.1-1 版本升级到 3.4.1.1-1 (以及相伴的其它组件升级)后,我发现我已经很难遇到输入法「恰巧」能够打开的时候了。于是给 GNOME 他们报了 bug。而昨天和今天早些时候,只好使用 vimim 或者复制的方式来在终端里输入中文。今天,收到 gnome-terminal 开发者 Christian Persch 的回复:

Please test if this is reproducible with either gnome-shell or metacity (latest versions from gnome 3.4) in click-to-focus mode. Anything else is entirely unsupported.

他说,如果不能在 GNOME 的组件中重现,那么他们不会修复。操你妹啊,你丫又没有平铺式WM!这不就相当于我说你们的某家电在我家里无法使用,经销商却说你们要是不能在商场里重现问题那不关他们的事么!

于是决心放弃一切有问题的 GNOME 组件,换终端了。我不想用 guake 那种特别的终端,因为一个普通的正常终端在 Awesome 下已经被我调教得很听话了。先尝试的是 lxterminal。忽略掉不完整的中文翻译,开始迁移自己的配置。终端光标不能更改为竖线就算了,竟然没办法通过命令行参数的方式指定窗口的任何可以用作区分的参数。原来 gnome-terminal 我是指定 class 的,这样我可以设置一个流动终端——按一个组合键把它叫过来,用完后再按个键让它离开。但这样做必须能够匹配到那个窗口;我还有些放 mosh 会话的终端,我可不希望它们也跟过来。

又尝试了 Xfce 的终端。这个长得和 gnome-terminal 已经比较像了。看看命令行选项,能设置 role。这个足够用了。配置下字体、颜色什么的,再去改改 Awesome 的配置就可以用了。途中还修整了下run_or_raise函数的匹配逻辑。

PS: GTK 3 更新到 3.4 后,UI 发生了很大的变化,如:

  • 数字输入框的加减号变大了,更占地方,点击时也需要移动更远的距离了;
  • 滚动条变窄变丑了,点中它更难了;
  • gnome-terminal 的颜色设置比 Windows 的取色器还难用了;
  • 好端端的复选框不用,非得弄成滑块,这个是要让用户练习鼠标的拖动操作么?
Category: Linux | Tags: GNOME gtk 终端 linux awesome
4
18
2012
5

Haskell 实战:惰性地读取子进程输出

突然想给 locate 命令写个 wrapper,把输出中的家目录和一些因加密而引入的软链接显示为~。自然,这需要读取 locate 命令的输出。在 process 这个库中看到了readProcess函数,似乎是自己想要的(完整代码):

readLocate :: [String] -> IO String
readLocate args = getArgs >>= \cmd ->
  let args' = args ++ cmd
  in readProcess "locate" args' ""

结果却发现,原本 locate 命令是边查找边输出的,现在变成了先静默,然后一下子全部吐出来。没有按 Haskell 惯常的「懒惰」脾气来。这样一来,当我发现输出项目太多想按Ctrl-C中断时已经晚了。

Google 了一下,找到这个

I guess people who want laziness can implement it themselves directly, taking care to get whatever laziness it is that they want.

好吧。我先下回 process 库的源码看看readProcess为什么不是惰性的:

readProcess 
    :: FilePath                 -- ^ command to run
    -> [String]                 -- ^ any arguments
    -> String                   -- ^ standard input
    -> IO String                -- ^ stdout
readProcess cmd args input = do
    (Just inh, Just outh, _, pid) <-
        createProcess (proc cmd args){ std_in  = CreatePipe,
                                       std_out = CreatePipe,
                                       std_err = Inherit }

    -- fork off a thread to start consuming the output
    output  <- hGetContents outh
    outMVar <- newEmptyMVar
    _ <- forkIO $ C.evaluate (length output) >> putMVar outMVar ()

    -- now write and flush any input
    when (not (null input)) $ do hPutStr inh input; hFlush inh
    hClose inh -- done with stdin

    -- wait on the output
    takeMVar outMVar
    hClose outh

    -- wait on the process
    ex <- waitForProcess pid

    case ex of
     ExitSuccess   -> return output
     ExitFailure r -> 
      ioError (mkIOError OtherError ("readProcess: " ++ cmd ++ 
                                     ' ':unwords (map show args) ++ 
                                     " (exit " ++ show r ++ ")")
                                 Nothing Nothing)

原来是另开了一 IO 线程读输出,然后等待进程结束后关闭管道。这解释为什么它不是惰性的——它得进程善后处理。

那好吧,改用createProcess好了:

doLocate :: IO (String, ProcessHandle)
doLocate = do
  argv0 <- getProgName
  let args = case argv0 of
                  "lre" -> ["-b", "--regex"]
                  _ -> []
  args' <- getArgs
  let args'' = args ++ args'
  (_, Just out, _, p) <- createProcess (proc "locate" args''){ std_in = Inherit,
                                                               std_out = CreatePipe,
                                                               std_err = Inherit }
  hSetBuffering out LineBuffering
  (,) <$> hGetContents out <*> return p

改进后的程序,不会等待进程结束,而是返回输出和进程句柄。进程句柄用来等待子进程结束,同时获取退出状态。至于那个管道就不关闭了,留给操作系统解决好了。

main = do
  (out, p) <- doLocate
  putStr $ transform out
  waitForProcess p >>= exitWith

改进版的完整程序在此

Category: Haskell | Tags: Haskell linux

Mastodon | Theme: Aeros 2.0 by TheBuckmaker.com