7
9
2013
4

把标准输出伪装成终端

fcitx-diagnose 是 fcitx 输入法的非常优秀的诊断脚本。当输出到终端时,fcitx-diagnose 会给输出加上易于区分不同类型的消息的彩色高亮。可是,当用户把输出重定向到文件以便让其他人帮助查看时,这些高亮就没了。fcitx-diagnose 的输出很长,但如果通过管道给 less 查看的看,这些彩色也会消失。

要是 fcitx-diagnose 支持--color=always这样的选项就好了。可是 yyc 说他懒得写。getopt我只在 C 里用过,好麻烦的,所以我也懒得写。于是,我还是用我的 ptyless 好了。后来又想到,用于改变 I/O 缓冲方式的 unbuffer 和 stdbuf 应该也可以。测试结果表明,只有 unbuffer 可行,因为它是和 ptyless 一样使用伪终端的。stdbuf 则是使用 LD_PRELOAD 载入一个动态链接库的方式来设置缓冲区。

不过,既然 stdbuf 用 LD_PRELOAD 来设置缓冲区,我何不来用相同的办法改变isatty()函数的返回值呢?同时,我也学学 stdbuf,试了下__attribute__ ((constructor))指令。

#include<stdarg.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<dlfcn.h>

static int (*orig_isatty)(int) = 0;

int isatty(int fd){
  if(fd == 1){
    return 1;
  }
  return orig_isatty(fd);
}

void die(char *fmt, ...) {
  va_list args;
  va_start(args, fmt);
  vfprintf(stderr, fmt, args);
  va_end(args);
  fprintf(stderr, "\n");
  fflush(stderr);
  exit(-1);
}

__attribute__ ((constructor)) static void setup(void) {
  void *libhdl;
  char *dlerr;

  if (!(libhdl=dlopen("libc.so.6", RTLD_LAZY)))
    die("Failed to patch library calls: %s", dlerror());

  orig_isatty = dlsym(libhdl, "isatty");
  if ((dlerr=dlerror()) != NULL)
    die("Failed to patch isatty() library call: %s", dlerr);
}

然后,像 stdbuf、proxychains 那样做了个包装,不用自己手动设置 LD_PRELOAD 环境变量了。这也是我第一次使用 CMake,比 GNU 的 autotools 那套简单多了 :-)

使用方法很简单:

  1. 克隆或者下载源码
  2. 编译之
    $ mkdir -p build && cd build
    $ cmake .. # 或者安装到 /usr 下: cmake .. -DCMAKE_INSTALL_PREFIX=/usr
    $ make
    
  3. 安装之
    $ sudo make install
    $ sudo ldconfig
    
  4. 可以使用了:
    $ stdoutisatty fcitx-diagnose | less
    
Category: Linux | Tags: C代码 linux 终端 shell
7
3
2013
7

手动保存/读取 zsh 历史记录

关于历史记录,zsh 有很多选项。我的配置是:

HISTFILE=~/.histfile
HISTSIZE=10000
SAVEHIST=10000

# 不保留重复的历史记录项
setopt hist_ignore_all_dups
# 在命令前添加空格,不将此命令添加到记录文件中
setopt hist_ignore_space
# zsh 4.3.6 doesn't have this option
setopt hist_fcntl_lock 2>/dev/null
setopt hist_reduce_blanks

最多保留一万行不重复的历史记录。对其的读取和保存没做额外的配置,因此 zsh 会在启动时读取一次,在退出时保存一次。这样,如果同时开了多个 zsh,它们不会共享启动后的历史记录项,因为还没有写到文件中去。

其实是有选项来方便在多个 zsh 中及时共享历史记录的:

setopt SHARE_HISTORY

但是这样的话,每次显示提示符时 zsh 均会读取一次历史记录,而每当新的历史记录产生时 zsh 都会写入一次。磁盘 I/O 太频繁了,我不喜欢。我只需要在我想的时候,能够手动保存和读取历史记录就可以了。读过长长的文档,发现fc可以做到这点:

# 读取历史记录
fc -IR
# 保存历史记录
fc -IA

-I表示「incremental」,只有新的项目被处理。-R是读取,而-A是写入。千万不要用-IW,这样会丢失原有的历史记录。

Category: shell | Tags: zsh
6
27
2013
10

使用 SQLite3 的第三方扩展来修改火狐历史记录中的 URL

在火狐 17 以前,我可以这样子访问我本地的 Python 文档的:

jar:file:///home/lilydjwg/docs/Python/python.zip!/index.html

访问的实际上是一个 zip 文件中的内容。网页这种纯文本的东西压缩率高,35M 的 Python 3.3 文档,压缩后只有 7.1M。一来节省磁盘空间(我的 /home 分区己用 98% 了 TwT),二来读取快。

可是,自从火狐 17 开始,虽然 jar: 协议依旧支持,但是似乎其中的部分或者全部 JavaScript 脚本不会被执行。最明显的是,Python 这种用 Sphinx 制作的文档的搜索功能没了!

在拒绝升级火狐很长一段时间之后,Arch 把火狐 16 要用的库文件升级了……于是只好换回未压缩的一大堆文件。可问题是,我以前在火狐地址栏输入re就有 Python 正则表达式模块的文档的补全、输入py m就有 Python 标准库模块列表的补全,地址转换后,这些历史记录里的地址就全失效啦。

现在想想,其实我可以使用 Redirector 插件搞定的。但当时没想到,也是想更根本地解决问题,便直接修改火狐的地址记录数据库了。

此数据库是 SQLite3 数据库,位于火狐配置目录下的places.sqlite3文件中。moz_places表中记录了历史记录和书签中的项目的 URL 地址,只修改它就可以了。但问题是,这不像我当初 MediaWiki URL 路径中去掉index.php那样,用replace函数就可以搞定:

UPDATE OR REPLACE moz_places SET url = REPLACE(url, '/index.php', '') WHERE url LIKE 'http://localhost/wiki/index.php/%';

我需要正则表达式

于是找到了这个 glib_replace 模块,支持使用 glib 的正则表达式来进行替换。下回来编译成 .so 文件后这样子用:

SELECT load_extension('./glib_replace.so');
UPDATE OR REPLACE moz_places SET url = regex_replace('^jar:((?:.(?![^/]+\.zip!))+)(/[^/]+)\.zip!(.*)$', url, '\1\2\3') WHERE url LIKE 'jar:file:///home/lilydjwg/docs/Python/python%'; 

跑完就好啦!

PS: 如果你的 URL 中有 % 字符,记得在 like 操作符参数中转义成 %% 哦~

参考链接

Category: 火狐 | Tags: 正则表达式 火狐 sqlite3
6
2
2013
9

转换文本照片为 DjVu 格式

前几天听群里的网友聊起 DjVu 文档格式对扫描文本的压缩率很不错,又忆起自己手头有本书的照片版,不光近百兆占地方,而且一堆 JPEG 图片也不方便阅读,于是想着把它转成 DjVu 格式试试。

Google 了一下,发现有 DjVuLibre 这么套工具。用法没能指望上 Google,还是老老实实地看 man 文档的。

首先,把一张张的 JPEG 图片转成一个个单页的 DjVu 文档。命令名字很奇怪,叫c44(我还一不小心打成了c99囧)。转换比较费 CPU,所以用parallel来利用多核:

parallel c44 ::: *.JPG

然后当前目录下就出现了一堆与 JPEG 图片同名的.djvu文件。

接下来,把这些文档合并起来。命令叫djvmm想来指的是多页(multi-page)。

djvm -c doc.djvu *.djvu

这样就好啦。页面顺序是按照在命令行上给出的顺序。这里是按文件名排序的。看了看生成的doc.djvu,只有 15M 耶。我对比了下 DjVu 文档和原图片的质量,在放大的时候还是能看到差了一些的,不过文本清晰得足够阅读就好啦。

Category: Linux | Tags: DjVu
5
25
2013
8

给 Python 的正则匹配限制执行时间

看到这个标题,你也许会想,这个需要限制么?不是很快就出来结果了么?

感谢 Just Great Software,虽然我没买它的产品,但是其说明书(可免费下载)中的正则教程详细地论述了这点。所以我在自己的 xmpptalk 机器人中一直不敢接受用户输入的正则表达式。引述其中的一句话:「People with little regex experience have surprising skill at coming up with exponentially complex regular expressions.」(不太懂正则的人经常能令人惊奇地写出指数级复杂度的正则。)

但很不幸,我从这里抄到的匹配网址的正则就有这种问题。在将其的修改版给我的 XMPP 机器人 Lisa 使用后,Lisa 两次被含有括号的链接搞到没响应……

所以,如果要使用用户输入的正则,我必须限制其匹配时间。方法也很简单——使用信号就可以了。当 Python 在匹配正则时如果收到信号,会转而调用信号处理器,然后再接着匹配。如果信号处理器抛出了异常,那么此异常会传播到调用正则匹配的地方,从而中断匹配操作。

示例如下:

#!/usr/bin/env python3

import re
# import regex as re
import signal

def timed_out(b, c):
  print('alarmed')
  raise RuntimeError()

signal.signal(signal.SIGALRM, timed_out)
signal.setitimer(signal.ITIMER_REAL, 0.1, 0)
s = '<aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>'
r = re.compile(r'''(?:<(?:[^<>]+)*>)+b''')
try:
  r.findall(s)
except RuntimeError:
  print('time exceeded')

被注释掉的那句是调用mrab-regex-hg这个正则引擎的;它不会回溯时出这种问题。

优化下代码,写成方便使用(使用了TimeoutError,所以适用于 Python 3.3+):

import contextlib
import signal

@contextlib.contextmanager
def execution_timeout(timeout):
  def timed_out(signum, sigframe):
    raise TimeoutError

  old_hdl = signal.signal(signal.SIGALRM, timed_out)
  old_itimer = signal.setitimer(signal.ITIMER_REAL, timeout, 0)
  yield
  signal.setitimer(signal.ITIMER_REAL, *old_itimer)
  signal.signal(signal.SIGALRM, old_hdl)
Category: python | Tags: linux python 正则表达式
5
13
2013
4

我的 neocomplcache 升级步骤

志以备忘。

  1. 下载新的 neocomplcache 压缩包
  2. 找到最近的关于 neocomplcache 的「移除 text mode 支持」和「允许使用 sudo」的补丁,依次删除之
  3. 找到最近一次的 neocomplcache 安装,删除之
  4. 解压新的 neocomplcache,并删除vest目录,重新检出README.md
  5. 提交
  6. cherry-pick 最后一次「允许使用 sudo」的提交
  7. cherry-pick 最后一次「移除 text mode 支持」的提交,解决冲突。在必要的时候搜索包含「is_text_mode」的代码并删除之。提交
  8. 2013年7月18日更新cherry-pick 最后一次「安装 neocomplcache 时对 ftplugin/snippet 的修改」的提交。Tab 大小非八个空格的都给我去死!
  9. 更新 Vim 文档的 tags 文件
  10. 测试没有问题后推送到远程仓库

附注:

移除 text mode 支持」提交是为了在写一般文字(注释、git 提交信息、文章等)时依旧能够精确地按原大小写补全。因为我是中文用户,含有大写的单词是专有名词(如 Python、gVim、iOS、FreeBSD)的概率要远高于句首大写首字母单词。

允许使用 sudo」。在使用 sudo 命令并且$HOME环境变量被保持的时候不要显示错误信息并禁用 neocomplcache。在这种情况下继续使用 neocomplcache 可能造成生成的缓存文件无法被当前用户修改。我很少遇到这种情况,因为只有少数特定文件我才会使用 sudo 去编辑。作者给出了另外两种(对我来说不可行的)解决方案:

  1. 使用 sudo.vim 插件。这样将不会有备份文件,撒消文件的情况不清楚,交换文件亦可能有问题。没有备份文件将导致错误的配置无法被简单地撒消。sudo.vim 使用tee覆写文件,在文件还没写完时停机或者硬盘空间不足时将导致数据丢失,并且因为编辑的通常是重要系统文件而导致系统故障。(参见如何更安全地覆写数据文件。)就算没有遇到这种极端的情况,覆写文件亦将对正在使用此文件的其它进程造成不可预料的影响。

  2. 让 sudo 重置 $HOME 到相应用户的家目录。这样我自己的 Vim、zsh 等配置就用不了了。

Category: Vim | Tags: vim neocomplcache
5
8
2013
3

编译 Android 版 zsh,以及 strace

说明一下,示例中使用的是 zsh 语法。bash 用户的话——先在电脑上用上 zsh 再考虑给手机装吧 ^_^

在网上能够找到 zshaolin 这么个包含 zsh 的 Android 包,但是它是收费的。于是,在成功编译了不少 Android 的东东后, 我决定自己编译个 zsh。

首先,把 Android NDK 放到 $PATH 里来:

path+=/opt/android-ndk/toolchains/arm-linux-androideabi-4.7/prebuilt/linux-x86/bin

其次是./configure命令。其中那个LDFLAGS的路径里有我之前编译的 ncurses。注意这里必须指定使用 ncurses 库,否则会莫名其妙地失败。使用 Android 自己的 Bionic 这个 C 库的话,就只好禁用多字节字符的支持了。

另外,ncurses 编译的时候记得禁用 C++ 支持。

LDFLAGS=-L/ldata/media/temp/android/installed_binaries/lib \
  CC='arm-linux-androideabi-gcc --sysroot=/opt/android-ndk/platforms/android-14/arch-arm' \
  ./configure --host=arm-linux-gnu --prefix=/system --with-term-lib=ncurses \
  --disable-multibyte --bindir=/system/xbin

然后做点小修改,将Src/init.cTIOCSETD相关的两处代码注释掉,不然程序会卡在这个ioctl调用上。还要把所有_mktemp函数调用换掉。在config.h中注释掉HAVE__MKTEMP宏的定义即可。

接下来是编译。完成之后修改最后一行链接代码,将 ncurses 静态链接进去:

arm-linux-androideabi-gcc --sysroot=/opt/android-ndk/platforms/android-14/arch-arm \
  -L/ldata/media/temp/android/installed_binaries/lib -rdynamic \
  -o zsh main.o `cat stamp-modobjs` -ldl -lm -lc /ldata/media/temp/android/installed_binaries/lib/libncurses.a

然后make install安装,把生成的zsh可执行文件弄到手机上任何你喜欢放可执行文件的地方,share目录弄到配置的目录里去(我这里是/system)。或者我猜设置fpath变量也行?

我第一次编译成功的 zsh 没有去掉TIOCSETDioctl调用,于是启动时卡在那里一动也不动了。要调试这种情况当然是 strace 了。但很不幸的是,strace 用到了太多 Bionic 不支持的宏定义、结构体成员等。后来我换了 zshaolin 使用的这个工具链静态链接了个 strace,终于可以用了。

最后,我编译好的二进制文件下载链接。要注意的是,此 zsh 并不支持多字节字符(如中文)(但是人家 Kindle 上的就支持呢)。

2014年3月8日更新:如果遇到任务控制(job control)时无法将后台任务切换到前台,打印出「unknown signal」的问题,可将Src/signal.c:435处的WCONTINUED改成WSTOPPED(注意不是WIFSTOPPED),即:

--- Src/signal.c
+++ Src/signal.c
@@ -432,7 +432,7 @@
          */
 #if defined(HAVE_WAIT3) || defined(HAVE_WAITPID)
 # ifdef WCONTINUED
-# define WAITFLAGS (WNOHANG|WUNTRACED|WCONTINUED)
+# define WAITFLAGS (WNOHANG|WUNTRACED|WSTOPPED)
 # else
 # define WAITFLAGS (WNOHANG|WUNTRACED)
 # endif

这个方案是我通过 strace 手机上的 busybox 的 sh 学来的 =w= 新编译的 zsh 5.0.5 可以由此下载

Category: Linux | Tags: linux Android zsh 交叉编译
5
8
2013
6

禁用 Xfce4 终端的 F1 和 F11 快捷键

我在 Xfce4 终端 0.6.1 的首选项中禁用掉了菜单快捷键,但是却没有找到禁用F1打开一个无趣的帮助窗口的方法。不过还好这个快捷键我很少用到。现在可能是经历一些升级之后,我发现F11也被拦截,变成全屏快捷键了……

解决方案与更改 Nautilus 的删除快捷键差不多,将以下内容写到~/.config/xfce4/terminal/accels.scm文件中:

(gtk_accel_path "<Actions>/terminal-window/fullscreen" "")
(gtk_accel_path "<Actions>/terminal-window/contents" "")

此方法来自 SuperUser

Category: Linux | Tags: linux gtk
4
30
2013
5

编译了点 Android 的网络命令行工具

在 Android 这个奇怪的平台想弄点 Linux-style 的东西用真不容易。网上现成的东西也比较少,这里有 stunnel、redsocks 和 iptables 等。另外 GAEProxy 里有 redsocks、iptables 和 Python 2.7,在/data/data/org.gaeproxy目录下。但它的 Python 不支持 readline,redsocks 不支持 UDP。

下边是我自己编译的几个工具和其特点(全部没有第三方库依赖):

  • redsocks:取自 git 版本,支持 UDP。其中,支持 UDP 需要search.h头和相关库函数,但是 Android 的 C 库中没有。我使用了 musl 这个 C 库中的相关文件。
  • socat:支持 readline 和 OpenSSL。openssl 这个命令行工具也作为附加文件得到了,但是感觉用处不大。
  • tcpdump:著名的网络抓包工具。没什么特别的。

编译全部使用的是 Android NDK。编译命令基本上类似于:

CC='arm-linux-androideabi-gcc --sysroot=/opt/android-ndk/platforms/android-14/arch-arm' \
  ./configure --host=arm-linux-androideabi --prefix=/ldata/media/temp/android/installed_binaries

但是不同的软件通常都会需要一些修改。比如上边说到的 redsocks。最无痛编译成功的是 LuaJIT 了,但是我这里没找到什么实际用途。另外记得把生成的可执行文件用arm-linux-androideabi-strip处理下,减小体积。

以上三个工具打包下载请点击这里备用地址)。

Category: Linux | Tags: linux 网络 Android 交叉编译

Mastodon | Theme: Aeros 2.0 by TheBuckmaker.com