Oct 12

从前,恋爱像 TCP 一样,一旦建立了连接,就永远地连接上了,直到生命。而现代,恋爱像 UDP 一样,连接上一个 peer 之后,也随时可以断开连接,重新连接到另一个地址

Oct 2

因为工作上一直在用 nginx,对 nginx 配置的了解日益深入,而对 Apache httpd 配置的了解依然非常少以至于不知道如何添加一个虚拟主机的配置而不用修改大量已有配置,决定将自己本地的 Apache httpd 替换成 nginx。一开始这个 httpd 只是跑了一个 MediaWiki、一些静态文件和单独的 PHP 脚本,随着时间的流逝,后来陆续添加了 phpPgAdmin、RockMongo、PHP Xcache、into2html CGI 脚本等东西。于是配置起来似乎也不那么容易了。

首先第一点:这么多 PHP 的服务,我不想每一个 location 块里边一堆相同或者相似的 FastCGI 配置。于是把相关配置写到一个文件里去。(直到这时我才意识到 location 是可以嵌套使用的!)

index   index.php index.html;
location ~ (.+\.php\d?)($|/) {
        fastcgi_pass    unix:/run/php-fpm/php-fpm.sock;
        fastcgi_index   index.php;
        set     $script $request_filename;
        if ($request_filename ~ ^(.+\.php\d?)(/.*)$){
                set $script     $1;
                set $pathinfo   $2;
        }
        fastcgi_param   PATH_INFO       $pathinfo if_not_empty;
        fastcgi_param   SCRIPT_FILENAME $script;
        include         fastcgi_params;
}

因为是嵌套的 location,所以得在外边也写一下index,不然 nginx 会不知道的。

另一个问题是默认的 fastcgi.conf 里定义的SCRIPT_FILENAME$document_root$fastcgi_script_name。但是我的 MediaWiki 使用了alias而不是root,于是$document_root会访问错地方。网上似乎没人完全地解决或者绕过了这个问题,大概是因为他们的配置不会被包含到多个 location 里吧。后来使用$request_filename这方案是我自己读了文档之后「发明」的。

另外,nginx 自定义的变量似乎是词法作用域,不能被 include 进来的配置访问到。大概因为 FastCGI 脚本路径是自己处理的,PATH_INFO也得自己处理。

然后就可以这么用啦(这是我的 MediaWiki 配置):

location /w/ {
        alias /usr/share/webapps/mediawiki/;
        include php;
}
location /wiki {
        rewrite ^/wiki(/.*)?$ /w/index.php$1 last;
}

以前的 Apache httpd 的配置是这样的:

Alias /w /usr/share/webapps/mediawiki

RewriteEngine On
RewriteRule ^/?wiki/(.*)$ /w/index.php/$1 [PT,L,QSA]
RewriteRule ^/?wiki$ /w/index.php [PT,L,QSA]

<Directory /usr/share/webapps/mediawiki>
        Options +FollowSymLinks
        AllowOverride All
        Require all granted
</Directory>

另外一个被我写成单独的配置文件以便被 include 的限制只允许本地访问用的:

allow   127.0.0.1;
allow   ::1;
deny    all;

比 httpd 的好理解一些。

另一个问题是 CGI 脚本。nginx 是有两个方案的,fcgiwrap 或者 nginx-fcgi。前者是个二进制程序,在 Arch 和 Debian 源里都有。后者是个 Perl 脚本,已经难以下载到了(给的链接是我从互联网存档取得的)。

Arch 的 fcgiwrap 包提供了一个 systemd socket 文件,直接启动它就可以了。启动 .service 服务似乎会有问题。

$ sudo systemctl start fcgiwrap.socket

nginx 里就这么写就可以了:

fastcgi_pass    unix:/run/fcgiwrap.sock;
fastcgi_param   QUERY_STRING       $query_string;
fastcgi_param   REQUEST_METHOD     $request_method;
fastcgi_param   CONTENT_TYPE       $content_type;
fastcgi_param   CONTENT_LENGTH     $content_length;
fastcgi_param   SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param   REQUEST_URI        $request_uri;
fastcgi_param   DOCUMENT_URI       $document_uri;
fastcgi_param   DOCUMENT_ROOT      $document_root;
fastcgi_param   SERVER_PROTOCOL    $server_protocol;
fastcgi_param   GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param   SERVER_SOFTWARE    nginx;
fastcgi_param   REMOTE_ADDR        $remote_addr;
fastcgi_param   REMOTE_PORT        $remote_port;
fastcgi_param   SERVER_ADDR        $server_addr;
fastcgi_param   SERVER_PORT        $server_port;
fastcgi_param   SERVER_NAME        $server_name;
fastcgi_param   REMOTE_USER        $remote_user;

引用的时候就是这样子:

location /cgi-bin {
        root /srv/http;
        include fcgiwrap;
}

经过测试,默认参数时 php-fpm 空闲时有三个 worker,高峰时会多一点,但是 Apache httpd 平时就有八个 worker,高峰时更多。每个 worker 对内存的占用是差不多的。至于响应速度,对于 MediaWiki nginx 稍慢 httpd 一点,但是服务启动后第一次访问会很多,而且 ab 测试时失败数少不少。更重要的是,systemd-analyze blame表示 nginx + php-fpm 组合启动时间远远少于 Apache httpd(0.1 秒 vs 好几秒)!

PS: 谁能告诉我 systemd-analyze 怎么查看以前启动的时间信息呀?

Sep 29

Tcl 的 expect 工具是一个十分有用的自动化工具,经常被用来喂 ssh 密码什么的。不过配置 ssh Control Master英文介绍, 中文翻译之后,我发现第一次连接之后退出,expect 脚本不肯退出了,而且Ctrl-C什么的都不管用,除非我杀掉实际连接到远程主机的 ssh 控制进程。很显然,这是因为 ssh fork 出来的 ssh 进程依旧保持着伪终端的打开状态,expect 依旧在等待来自其上的输出。

研究之后,我发现虽然我可以给我的 expect 脚本加上命令行参数的处理之类的功能,但是interact之后必须等待伪终端关闭没有办法绕过。罢了,本来对 Tcl 就不熟,拿 Python 重写一个吧。

Python 也有个类似的库,叫 pexpect,不过上次我看它的时候还只支持 Python 2,而且使用起来似乎有点问题。所以干脆自制一个满足自己需求的好了。

程序不长,一百多行,但也比预期的长了不少。主要都是些终端和文件描述符的处理。哦还有信号。

我以是否存在子进程来作为判断是否结束interact的标志,而不管是不是还有进程在用这个伪终端。在收到SIGCHLD信号时使用waitid系统调用可以清理任意一已终止的子进程,使用WNOHANG标志在有子进程但是没有已退出者时不要等待、直接返回。于是,在有子进程退出时,反复调用waitid直到它报错就说明已经没有子进程存在啦。

另一个需要处理的信号是SIGWINCH,就是终端大小改变时以此终端为控制终端的前台进程会收到的那个信号。当脚本所在的终端大小改变时,需要重新设置脚本创建的伪终端的大小。不知道为什么,pexpect 竟然没有自己处理这个。

代码照旧在 GitHub 上。以下是使用示例:

#!/usr/bin/env python3

import sys

import expect

def main(host):
  p = expect.Expect()
  p.spawn(['ssh', host])
  p.expect_line('# ')
  p.send('. ./tide\n')
  p.send('TERM=screen-256color exec zsh -l\n')
  p.interact()

if __name__ == '__main__':
  host = 'phone'
  if len(sys.argv) == 2:
    host += sys.argv[1]
  main(host)

这是之前有问题的 expect 脚本:

#!/usr/bin/expect -f

set host [if {$argc eq 0} {lindex phone} {lindex phone$argv}]
spawn -noecho ssh $host
expect "*# "
send ". ./tide\n"
send "TERM=screen-256color exec zsh -l\n"
interact

这是我的 ssh control master 相关配置:

ControlPath ~/.ssh/master-%r@%h:%p
ControlMaster auto
ControlPersist yes
Sep 2

Arch Linux 连接网络可以使用其官方开发的 netctl 系列命令行工具。要想在开机(以及从挂起/休眠状态唤醒)时自动连接到可用的无线网络,以下是设置步骤。

首先,你得告诉 Arch Linux 你知道哪些无线热点。Arch Linux 不会自动帮你破解别人的 Wi-Fi 密码的。就算 Wi-Fi 热点没有加密,你不说 Arch Linux 怎么知道它应当连接到那个热点呢,也许那是个钓鱼用的热点也说不定哦。

cd 到 /etc/netctl 目录下,可以看到 examples 目录下有一堆示例配置。复制你所需要的配置文件到上一层目录(/etc/netctl)。比如绝大多数 Wi-Fi 热点使用的是 WPA 加密,那就复制 examples/wireless-wpa 文件。目标文件名比较随意,起个方便自己的名字就行,比如 work、home 之类的。复制完成之后记得 chmod 600 禁止非 root 用户访问,因为配置文件里会包含你的 Wi-Fi 热点密码。

然后编辑配置文件,修改 ESSID 和 Key 为你的 Wi-Fi 热点 ID 和密码就可以了。之所以要先更改权限再编辑,是因为某些编辑器(如 Vim)会生成同权限的备份文件;那里有可能也会包含密码。可以放多份配置文件在这里,netctl-auto 默认会去找一个可用的连接。有多个可用的时候不太清楚它会连上哪一个,可以使用更复杂的配置文件来指定优先级(参见 examples/wireless-wpa-configsection 示例配置)。

配置文件写好之后,当然是启动相应的服务啦。Arch Linux 一贯的传统是不启动不必要的服务,除非用户说要启动之。netctl-auto 的 systemd 服务名是 netctl-auto@interface.service(当然 .service 后缀还是可以省略的)。interface 部分写你的无线网络接口的名字,可以通过 ip linkifconfigiwconfig 等命令看到。我禁用了 systemd 的可预测网络接口名称,所以我的无线网络接口名唤 wlan0。我使用如下命令启动服务:

$ sudo systemctl start netctl-auto@wlan0.service

如果一切顺利的话一小会儿之后就应该连上网了:

$ systemctl status netctl-auto@wlan0.service
● netctl-auto@wlan0.service - Automatic wireless network connection using netctl profiles
   Loaded: loaded (/usr/lib/systemd/system/netctl-auto@.service; enabled)
   Active: active (running) since 二 2014-09-02 20:23:31 CST; 2h 45min ago
     Docs: man:netctl.special(7)
  Process: 340 ExecStart=/usr/bin/netctl-auto start %I (code=exited, status=0/SUCCESS)
   CGroup: /system.slice/system-netctl\x2dauto.slice/netctl-auto@wlan0.service
           ├─402 wpa_supplicant -B -P /run/wpa_supplicant_wlan0.pid -i wlan0 -D nl80211,wext -c/run/network/wpa_supplicant_wlan0.conf -W
           ├─404 wpa_actiond -p /run/wpa_supplicant -i wlan0 -P /run/network/wpa_actiond_wlan0.pid -a /usr/lib/network/auto.action
           └─501 dhcpcd -4 -q -t 30 -K -L wlan0
...

或者通过 netctl-auto list 命令也可以看到连接上了哪个配置文件里指定的热点。

如果满意的话,就让它开机自启动啦:

$ sudo systemctl enable netctl-auto@wlan0.service

参考资料:ArchWiki 上的 netctl 条目

Aug 31

刚刚竟然梦到了高中英语老师,那个带领我们猜测试题的意图的脾气挺好的女老师。不过这里我要说的是另一位老师,一位脾气很坏的老师,一位造成我即使对很好相处的老师也感到害怕与不友好的老师,一位部分地导致我在很长的时期内与年长的人的交际障碍的老师。

她叫刘静,和中国广播电台经济之声一位主持人重名了。武汉市蔡甸区第三小学的一名教师。1997年至2000年是我所在班级的班主任,教「数学」(我不认可那时的教学方式和教学内容是数学;算术罢了)。当时她年龄大概30来岁。脾气很坏,教课无方。最常见的教学手段有二:一、拿教鞭跳起来敲讲台,敲得讲台上的粉笔头到处乱蹦,粉笔灰弄成雾霾;二、拿教鞭打手心来惩罚学生。当然那个时代,几乎所有老师都体罚学生,有些手段还更讨厌。只是体罚加上她在讲台上的「表演」,让我们在下面的学生都噤若寒蝉、惊恐不已。更重要的是,她的女儿也在这个班级,所以我们经常可以观看到一场场惊心动魄的家祖暴力。哦对了,我当初转到她这班里时没送礼,据说要不是成绩单上的数字比较大她还不会收。所以大家看,成绩好有时候并不是什么好事。

「上帝欲使其灭亡,必先使其疯狂。」不知道她现在灭亡了没有。但愿她没有继续毁人不倦。

刘静是我印象最深刻的一位老师。同时期还有一位叫张攀的男计算机老师给了留下了深刻的印象。我从那时就很喜欢计算机了,当然能力也在同学之上。但是这位张攀老师对我的提问不理不睬,因为他偏爱另一位成绩也很好的萌妹子。不仅如此,他似乎对我怀有敌意。每个孩子都会犯错。当时课上在关机的情况下练习使用鼠标,我觉得颇为无聊,把鼠标当成非电器左右推来推去地玩儿,结果招致他很严厉的一句话。具体是什么我已经忘记了。他平时脾气挺好的,经常和学生有说有笑的。那是唯一一次我见到他生气。

也就是从那时候起,我开始对各种老师敬畏有加,生怕不小心又招惹到某位疯狂或者敌意的大人。所以,尽管后来遇到过不少挺不错的老师,我也只能辜负他们的友善和帮助了。孩童时代教育中受到的伤害,和在爱情中所犯下的错误一样,要花费数十倍的时间才能慢慢弥补。

Aug 29

刚刚看到一则消息,令我很不好受。又拍云,就是我这博客现在贴图和文件下载使用的那个 CDN,也是 CloudFlare 的中国版,依旧在招开发工程师。而两个月前,我在他们那里。他们告诉我当时他们只缺一个人。十数日之后,他们 HR 终于在数倍于承诺的时间之后告诉我,他们不缺人了

福无双至,祸不单行。这种令人很不好受的事情也并不是第一次发生了。大约两年前,我找朋友内推红帽(中国)的工作。结果被拒了,理由是,他们不缺人。大约两三个月之后,那位朋友又告诉我,他们又开始招人了。

更早一些,我毕业那年,搜狐来武汉了。我去面试,结果认为我能力不够。等我确定工作之后,他们却又有人在 Gtalk 上问我要不要去那里。

其实阿里巴巴那次也有类似的情形出现,虽然被拒的主要原因在于我严重休息不好、自由太少导致的状态欠佳。哦对了,没有预料到他们有人自己不懂 TCP 基础就问我网络相关问题也应该是原因之一。当时有人给我推荐工作的时候他们很缺人。但是因为我刚回到老家,很累,还没准备好应聘下一份工作,所以迟了几天。结果那时候他们就不怎么缺人了。

一直想不明白,为什么事情会一直是这个样子,总是错过想要的工作。也许自从走进武汉大学信息学部的那一刻就注定了如此命运吧。也许这种事情再三出现,是因为那次冲动的初恋告白。好像我总是第一次做不好呢。第一次初恋。第一次找工作,没有任何经验,根本不懂得表达自己。第一次选学校,由于信息严重不足导致选择了不适合的学校-专业组合。第一次正式学习编程语言,也是因为资源有限,学了 C++。第一次选择文本编辑器,还是由于信息不足,而选择了私有且不再支持 Linux 的 EditPad Pro。第一次选择命令行 shell,既没有选择大众的 bash,也没有选择功能强大的 zsh,却是挺随意地用上了(当时有不少问题的)fish。第一次自己买电脑,CPU 竟然不支持虚拟化。第一次租房,也是很令人不悦的方寸之地。

当然,人总是会从过去的不满和失意中吸取教训,在反复不断地练习之中臻于熟稔。可是,现在,在这里,这个社会似乎并不认同这一点。我应聘阿里巴巴的时候,他们几乎不问任何具体的技术问题,而是纠结于你之前的工作内容是什么、有没有管理服务器经验(我应聘的是运维开发)、做过的项目能多少并发(你倒是给我找来足够压垮我的项目的用户呀)。也问过我根本没有接触过的东西,比如如何管理上百万的服务器。这其实是很容易学习的事情。他们不承认人的学习能力,总希望别的公司给他们的员工培训,让他们的员工在别处赚取经验。当然,这样规避了风险,使得招进去的人的能力的标准差比较小。没办法,他们招人的人的能力不行(不懂 TCP 就别给我谈网络),所以只能招到能力不是很行的人(连重要的密码都敢往 GitHub 公开项目里扔)。(其实历史已经证明了招个王垠进去也不会出大事的说。)

曾经有人跟我说,我在群里帮忙解答问题的那些人,工资都是我的几倍。很无语。但是也没什么好说的。他们在专业技能上花的时间少,但是在交际、演讲方面花的精力多。招聘通常没办法直接测量应聘者的专业技能,于是只能通过一些表象来推测。就像让你只通过观看来比较两个物体的质量,你只能从体积、色泽、纹理等方面去揣测。然而,这其中没有一项有比较高的证明力度。经验在大多数情况下有效,仅此而已。

我还听我曾经的 boss 评价一个自称对多个方向均有兴趣的应聘者,认为一个人不可能对如此多的方向均有兴趣。言外之意是认为简历有吹嘘之嫌。可是我比那人的兴趣点还要丰富得多啊:Python、C、Linux 系统管理、网页开发、自动化运维、Lua、函数式编程,还有更远一些的,数学、物理、化学、天文、地理、语言学、经济、历史、生物、文学,这些也都是我所喜欢的。另一些人(很正确地)注意到,「喜欢」、「有兴趣」与「擅长」是两码事。于是,「喜欢XYZ」没用。你已经会了什么就继续干什么吧。和第一份工作的公司一样,开始决定结局

我注意到现在很多人喜欢「撞大运编程」。根本不理解程序在干什么。它不工作了,那么就这样试试,再那样试试。有错误消息?太晦涩难懂了,当作不存在好了。很好奇为什么我能很快看出问题来?答案很简单:因为我在尝试理解已有的信息表达了什么。在这里这个对象本应该有的方法却不存在。咦这个对象根本不是预期的对象。哦,这个对象是在初始化那里通过注入的方法弄进来的。咦你声明要注入的对象列表里怎么比初始化的参数列表少了这个出问题的对象?OK,问题解决。

这个事例也很好地说明了我为什么要用 Vim,我为什么不喜欢重复的代码,以及在必要的时候我为什么喜欢复制粘贴已有的相似代码片断而不是重新打一遍。使用 Vim 当然是因为搜索和复制以及对复制之后的代码的处理很方便。除了弱到爆的文件编辑功能(你一直在切换键鼠、一直在找需要的文件所在的那个标签、一直在输入已经输入过的单词和代码行,你没意识到么),另一个 IDE 令我吃惊的是,它们竟然鲜有支持多栏布局的!就是同时显示多个文件,通常是竖直并列,这样方便对照查看。不然用那么大的显示器多浪费啊。

我突然有点想什么时候给 CloudFlare 投简历了。虽然我从没有出过国。本来我想去又拍云就是受到了 CloudFlare 那博客的影响,而他们又一直在博客末尾说在招人。不管怎样,那会是一条非常不确定的旅程。我还没准备好。还是努力让生活慢慢变好吧。现在已经比之前好了很多了呢。虽然与我携手走过生命中剩下的旅程的那个人还没有出现,不过带我来到一个不适合我的环境、并一直阻碍着我追寻自己的人已经远去了。

又,我本来不习惯在这边写自己对非技术的想法的。但似乎只有写在这边才会有人看。那么妥协好了。坚持太累,放弃一些给自己放飞。


2014年9月5日更新:阿里巴巴又在招 Pythoner 了,而且这次是非常符合我目前技术方向的职位…………

Aug 5

这里有人给出了自己的解法,但是我不喜,所以有了本文。注意,本文中的 shell 代码均为 zsh。如果你在用 Windows,那建议还是不要玩了,那个对付这种事情太难用了。

可惜知道这个题晚了两天,没能进前X名 :-(

0. Fuck your brain

机器上没有 brainfuck 编译器,于是 Google「brainfuck online」,得到这个。贴进去运行即可。

1. Multiply

一个值是 42。另一个要观察数列。直接把数列贴到数列百科全书即可。然后乘起来。

2. Keyboard

这个也很容易,不是把 Dvorak 键盘当成 Qwerty,那就是把 Qwerty 当成 Dvorak 了。对照着 Dvorak 的键位输入下边那串字符串,得到一 C 源码。编译、运行之即可。

3. QR Code

扫码,得到:

[abcdefghijklmnopqrstuvwxyz] <=> [pvwdgazxubqfsnrhocitlkeymj]

是个字符映射关系。Python 有现成的函数来处理这个。也是有正反两种可能,都试试就可以了。

>>> T = str.maketrans('pvwdgazxubqfsnrhocitlkeymj', 'abcdefghijklmnopqrstuvwxyz')
>>> s = 'Wxgcg txgcg ui p ixgff, txgcg ui p epm. I gyhgwt mrl lig txg ixgff wrsspnd tr irfkg txui hcrvfgs, nre, hfgpig tcm liunz txg crt13 ra "ixgff" t
r gntgc ngyt fgkgf.'
>>> s.translate(T)
'Where there is a shell, there is a way. I expect you use the shell command to solve this problem, now, please try using the rot13 of "shell" to enter next level.'

按照提示执行命令:

rot13 <<< shell

Arch 上,rot13 命令位于 bsd-games 包。或者在 Vim 里把光标移动到「shell」单词上按g?aw也能得到结果。

题目开始有趣起来了~

4. cat

这个题目更有趣了。源码里一堆乱七八糟的数据。先把它们弄到一个单独的 Vim 缓冲区,然后找到所有的五字符回文字符串:

%!grep -oP '(.)(.)(.)\2\1'

不是所有回文都被接受。仔细观察示例可以发现,中间一定是个小写字母,左边一定是一字母一数字。但是过滤后还是有太多结果。限制左边的字母为大写字母之后可以得结果。删掉不符合条件的,然后把中间的字符连起来即可。

v/\v^([A-Z][0-9]|[0-9][A-Z])\l/d

5. variables

初看,提示莫名其妙。后来注意到图片链接到了有意思的地方。访问得到另一个数字「32722」。显然是要用这个数字放在 URL 上继续访问了。直接拿 shell 访问:

$ n=1024
$ while true; do n=$(curl -sS http://fun.coolshell.cn/n/$n); echo $n; done

访问上百次之后出现一句话,给出了下一关的地址。

6. tree

这关要求从一棵二叉树的中序和后序遍历中还原其最深的路径。不知道怎么做,直接 Google「reconstruct a binary tree from in-order and post-order」,看来有不少人都在做类似的东西啊。我看的是 LeetCode 上的这篇文章。有代码,但我懒得写程序把树画出来或者是找最深的了。反正这树也不大,懂得了方法,直接在 dia 里手工构建出来了。当然,我只构建了最深的那部分。SVG 导出图片

然后就是拿密码解那个字符串了。要注意的是,不要自己去解 base64,不然 openssl 报错的……

7. N Queens

八皇后问题的变种。我直接使用了 Rosetta Code 上的代码。当然要小改一下,直接输出结果而不是打印出图案:

main = mapM_ print $ queens 9

然后找到符合那个 SHA1 值的解就可以了:

$ ./queens | tr -d ',[]' | while read code; do [[ $(sha1sum <<<zWp8LGn01wxJ7$code | awk '{print $1}') == e48d316ed573d3273931e19f9ac9f9e6039a4242 ]] && echo $code; done

8. Excel Column

26 进制转十进制:

>>> def debase26(x):
...   return sum(26 ** i * (ord(d) - ord('A') + 1) for i, d in enumerate(x[::-1]))
...
>>> debase26('COOLSHELL') // base26('SHELL')
85165

结果得到的页面说要转回 26 进制。好吧:(可惜没能在一行内搞定)

>>> def base64(x):
...   L = []
...   while True:
...     x, d = divmod(x, 26)
...     if d == 0: break
...     L.append(d)
...   return ''.join(chr(x + ord('A') - 1) for x in L[::-1])
...
>>> base64(85165)
'DUYO'

9. Fraternal Organisation

这个我没能解出来 QAQ 这两个图片看起来有些莫名其妙。我没注意到图片的名字和鼠标放上去的小提示。最后是看前边那个链接里的答案才知道原来还有个「猪圈密码」-_-|||

PS: 最近博客访问和评论速度都挺慢的,请见谅。

Jul 18

缘起

在一群文件里搜索特定的文本,第一个想到的工具是经典的 grep。自从知道 ag——The silver searcher 之后,我就只在命令行管道里用 grep 啦。

ag 的优势:

  • 命令短
  • 和 ack 以及 git grep 一样,默认会忽略掉你通常不想看的文本(二进制文件、被版本控制系统忽略掉的文件)
  • C 编写的,比 ack 更快!

作者挺在乎程序的执行效率。这也很重要,因为文件多啊,几十上百兆的源码找起来可费时了。

不过在我这里,ag 取代掉的既不是 grep 也不是 ack。因为我之前用的是 cgvg,和 ack 一样也是 Perl 写的,但不一样的是,它包含两个命令:一个(cg)用来搜索,另一个(vg)用来在编辑器里打开!

不知道为什么其它工具的作者都没有想到这一点。匹配的地方找到了,大部分情况都需要用编辑器打开看看,也许再改改吧?cgvg 免去了复制路径到编辑器里并跳转到特定位置这一烦琐的步骤。

所以换用 ag 之后,我自然也希望能够很便利地使用编辑器查看匹配的地方了。为此,我做了三个工具。好吧,其实有一个是在 cgvg 时代就已经有了的。

搜索

实践一下。假设我要寻找 Vim 源码中涉及到p_enc变量的地方。使用 agg 命令来搜索并使用翻页器 less 显示结果(截图时没有显示出 less 的存在):

agg result

agg 脚本更改了 ag 的显示样式,每一项开头都有一个序号,就和 cg 命令一样。同样地,agg 也把这个结果保存在主目录下的一个文件,以供 agv 命令使用。

注意,agg 并不能用来按文件名搜索。这种情况还是用 ag -g pattern

使用编辑器打开

agv 命令不给定参数,会显示上一次搜索的结果。如果给出一个编号,就会将结果在AGV_EDITOR环境变量指定的编辑器里打开,并跳转到对应的地方。和 vg 不同的是,agg/agv 为每一个终端维护了一个结果列表,这样就可以在不同的终端里搜索不同的内容而不会相互干扰了。

比如我们要去第 14 号结果所在的地方,只要执行agv 14,然后就到了:

agv result

我这里是在已有的 gVim 里打开的哦。因为我设置的AGV_EDITOR的值是vv $file:$line:$col。这是一句包含占位符的 shell 命令。$file$line$col分别是结果所在的文件、行号、列号。而vv,则是我写的另一个工具,用来在已经打开的 gVim 里打开文件,并且跳转到特定的地方

vv 需要 Python easygui 库,以及 gVim(或者 Vim)的 +clientserver 支持。vv 不仅支持上述格式的参数,作为一名 Pythonista 所编写的工具,它也支持从 Python 报错时打印的 Traceback 的行中提取文件名和行号。比如:

vv 'File "/usr/lib/python3.4/sre_parse.py", line 358, in _parse_sub'

这里,不一定要复制一整行。包含必要的信息(文件名和行号)就可以了。

当然,手动复制粘贴比较累。所以配合我的 zsh 全局别名:

alias -g XS='"$(xsel)"'

我只需要选中那一行,然后执行

vv XS

就可以了。zsh 会帮我把XS展开成我刚刚选择的文本。

ag.vim

ag.vim 是一个 ack.vim 的修改版,用于在 Vim 中调用 ag,就像 ack.vim 在 Vim 中调用 ack、grep.vim 在 Vim 中调用 grep 一样。

因为经常搜索在当前光标下的内容,我写了这么一条自定义命令:

command Agg exe 'Ag -Q ' . expand('<cword>')

仓库地址

https://github.com/lilydjwg/search-and-view

Jul 15

准备

首先检查 CPU 支持。需要 CPU 支持虚拟化的。

grep -E "(vmx|svm|0xc0f)" --color=always /proc/cpuinfo

没输出就没戏了。现在的 CPU 一般都支持的。

然后是内核支持。

zgrep CONFIG_KVM /proc/config.gz
zgrep CONFIG_VIRTIO /proc/config.gz

官方内核是支持的。

最后是用户态软件。Arch Linux 一向不怎么分包,安装 qemu 这个包就可以了。

哦对了,要安装 Arch 的话,还要准备它的安装镜像。

开始啦

一切就绪。

先创建虚拟机所用的磁盘文件。

qemu-img create -f qcow2 ArchVM.img 15G

这样就创建了一个 15G 容量的 qcow2 格式虚拟磁盘文件。之所以选用 qcow2,是因为它支持「母镜像」功能,对应于 Virtual Box 的差分存储。

然后就可以启动系统了。为了避免老是输入一长串命令,遵循 Gentoo Wiki 的建议,我们创建一个脚本:

#!/bin/sh
exec qemu-system-x86_64 -enable-kvm \
       -cpu host \
       -drive file=$HOME/ArchVM.img,if=virtio \
       -netdev user,id=vmnic,hostname=archvm,hostfwd=tcp:127.0.0.1:2222-:22 \
       -device virtio-net,netdev=vmnic \
       -m 256M \
       -curses \
       -name "Arch VM" \
       "$@"

注意到这里我已经加上了hostfwd参数,将虚拟机的 22 端口映射到 host 的 2222 端口上,方便以后通过 ssh 连接。

我这里指定了-curses参数,它将虚拟机的显示器直接使用 curses 库显示在当前终端上。当然能显示的只有显示器处于文本模式的时候,图形模式就只能知晓当前分辨率了。因为我是在服务器上使用,所以加上这个参数。当然你也可以使用 VNC 去连。

然后执行命令:

./startvm -boot once=d -cdrom path_to_file.iso

首先从光驱启动一次(once=d),重启之后恢复到默认的从硬盘启动。

系统启动啦~然后就会发现引导器 isolinux 把显示器切换到图形模式了……

终端无法显示图形模式的内容

不过还好。Arch 的引导界面我们知道。按Tab,然后输入<Space>nomodeset并回车。不过待会进系统里,KMS 之后一直是图形模式就什么也看不到了。

然后进入系统安装啦。注意硬盘设备是/dev/vda。当然也要注意安装并让 sshd 在开机时启动,虽然说有 curses 模式的「显示器」也可以用。

装好之后、重启之前还要注意一点,把/boot/grub/grub.cfg包含gfxload_video之类的地方都去掉,不然会进图形模式的。

装好后就 reboot 吧。如果一切顺利的话就能看到已经安装好的 Arch 登录提示符了。

好不容易装好了系统,当然要把它作为母镜像,所有后续的修改放子镜像上啦:

qemu-img create -f qcow2 -b ArchVM.img ArchTest.img

然后修改一下启动脚本。以后就可以用./startvm脚本启动这个虚拟机啦。

参考文章

Jul 3

muttils 是一系列用于 mutt 等终端邮件客户端的小工具,其中 viewhtmlmsg 脚本用于在浏览器中阅读 HTML 邮件:

macro pager \eh "<pipe-entry>viewhtmlmsg<enter>" 在网页浏览器中查看

但一直以来有个问题:需要等好几秒脚本才会返回,因为它要给浏览器足够的时间来读取 HTML 等文件,然后在退出前清理掉它们。如果是 Linux 系统并且安装了 inotifyx 包会好一些,它检测到有程序读取了 HTML 文件之后就立即退出了。

不过我希望更快一些。到后台去处理,到后台去等待。我要继续阅读下一封邮件。

所以就有了这么个小脚本:读取标准输入传过来的数据之后立即 fork,父进程退出,子进程去把数据交给 viewhtmlmsg 来处理:

#!/usr/bin/env python3

import sys
import os
import subprocess

def main():
  content = sys.stdin.buffer.read()
  if os.fork() > 0:
    return
  p = subprocess.Popen(
    'viewhtmlmsg',
    stdin = subprocess.PIPE,
  )
  p.communicate(content)
  p.wait()

if __name__ == '__main__':
  main()