这个博客从我高中起用到现在, 记录了我成长的点点滴滴, 已然成为我记忆的一部分, 中途试过很多博客, 最终还是回到这里. 今天起将会是一个全新的开始, 地址虽已改变, 生活依然美丽, 生命依旧灿烂.
新博客地址: http://blog.xiaogaozi.org/
淡泊以明志, 宁静以致远.
这个博客从我高中起用到现在, 记录了我成长的点点滴滴, 已然成为我记忆的一部分, 中途试过很多博客, 最终还是回到这里. 今天起将会是一个全新的开始, 地址虽已改变, 生活依然美丽, 生命依旧灿烂.
新博客地址: http://blog.xiaogaozi.org/
跋: 这篇短文写于今年上半年, 被我积压着一直没有发表, 今日重读, 发现有些观点还是略显稚嫩, 或许也跟当时的写作情绪有关, 但还是决定贴出来供众人评判. 这里顺便广告一下, 鄙部门, 也就是腾讯IM部, 现在已经为所有员工提供了20%时间, 目的就是让员工自己想出创新的点子, 并积极实现它, 希望这是一个新的开始, 也希望这样能够带来更出色、更好用的QQ.
Google有一个著名的 20%时间, 意思是员工可以每天利用这20%的闲功夫鼓捣一些感兴趣或者有创造性的东西, Gmail就是这么被鼓捣出来的. 国内貌似罕有公司对员工这样的鼓励, 国内和国外程序员对比会有一个有趣的现象, 每当我们听说国外某某某开发出什么不错的东西时, 就会有人补充说要是咱们国家的福利待遇好, 可以不愁吃穿, 可以不用关心房价, 我也能开发出来. 看到了吧, 我们给自己找了一长串借口, 敢情资本主义国家就是好, 啥都不用愁. 可以这样说, 国内程序员更喜欢被动, 而不愿意主动, 这里面有长期教育的成分, 也有个人修养的成分. 这跟国内IT界人士喜欢自称 “码农” 很是相似.
20%的意义在于让程序员主动去发掘, 主动去探索. 还记得去年我实习那会儿, 公司搞了一个竞猜活动, 然后让大家跟帖写结果, 由于奖品丰厚, 很快就吸引了大量回帖. 作为新人的我当然也想凑个热闹, 不过既然我是一名程序员, 就得有点新意才行, 于是我想到了通过写程序分析回帖来得到竞猜结果的概率分布, 然后我再根据概率来选择一个可能性较高的结果. 当天下午下班之后我就留在公司开始实施我的计划, 用着半熟的Python在那又是分析网页, 又是分析Cookies什么的. 在晚上离开公司之前我用Google Docs画出了那张我想要的概率分布图, 并选了一个比较靠谱的答案提交上去. 虽然最后我还是没有中奖 (要不然哥现在就有iPad了…), 但我不会认为那天晚上我是在浪费时间. 20%在某些人看来肯定是吃饱了撑的, 有那闲功夫炒炒股, 赚赚钱多好. 这样看来Google的程序员都是一群傻子, 放着大好的青春和出众的技术, 到外面赚点外快也行嘛, 干嘛非得要 “帮” 公司去发明这, 发明那的. 在我看来, 这代表着截然不同的两种人, 一种人利用自己的技术去赚钱, 另一种利用自己的技术去实现理想.
就在前段时间, 三叶草要组织春游骑车比赛, 在某同学的怂恿下, 我 “浪费” 了一点睡觉时间写了一个随机分组程序, 然后发到了论坛里. 因为有了这么一个东西, 第二天大家在群里讨论得很是活跃, 到了分组的时间, 大家似乎都屏住呼吸等待我运行程序, 那一刻, 一个程序将我们联系在了一起. 我觉得这是一件很有意义的事情, 就像我在简历中所写的 “坚信可以用程序让人们的生活更加美好”. 不过某些专业人士恐怕是看不起这些 “小打小闹” 的, 在他们的眼中, 似乎只有利益. 当我们进入这个社会, 有时我们会迷惘, 看着那些人像上战场一样你争我夺. 他们似乎忘了自己曾经拥有的那份单纯与美好, 似乎忘了自己也很纯粹地干过一些事情, 而不考虑任何利益. 他们说这就是 “成熟” 的代价, 但我得说这是你们 “堕落” 的开始. 在想这篇文章的标题时, 一直想不到什么东西跟20%对应比较合适, 最后决定使用 “商业机器”. 20%代表着自由、随性, 而 “商业机器” 则像猛兽一样吞噬着人类美好的心灵. 就像Raymond的著名文章 《大教堂与市集》 中的比喻一样. Linux教会我的最好的东西就是学会奉献, 它让我更加深刻地明白这个世界是由一群乐于奉献、善于付出的人所构建. 在很多场合, 我都会推荐人们去使用Linux, 一旦你脱离了微软的世界, 你会发现你以前所了解的只是沧海一粟, 拥抱开源, 拥抱整个世界将会是一片海阔天空.
现在只要我有时间, 我都会时不时地给一些开源软件提交补丁或者发表建议. 这些事情除了能换来一点点微不足道的名誉之外, 并没有多大的利益, 但我还是非常乐意. 我时常在想, 我们学习编程、学习技术的意义到底是什么, 每次都会坚定地认为是改善人们的生活, 为这个世界做出一点微薄的贡献. 曾经跟兰姐讨论改变世界这个话题时, 兰姐相当质疑我的看法, 她认为仅仅依靠几个人的力量不足已改变这个世界, 但在我看来人类历史就是通过不断进步来推进的, 一个人能做到的虽然有限, 也必定会对历史、对社会造成影响.
昨天沧之声同学在「桃源」上发表了 一篇文章, 歌词大意是对腾讯忽视Linux用户的声讨. 这让我相当惭愧, 细想去年半年的实习时间, 我其实并没有花时间去考虑怎样利用公司资源来推进Linux上的一些发展, 更多的只是依照导师的要求去完成一些任务. 很多人其实也仅仅把工作当成必须完成的任务, 而没有倾注自己的热情与理想. 某种机缘巧合, 让我来到了Client中心, 得让自己更加主动, 我的下一个20%已经决定, 无论结果如何.
升级到Lion之后, MacPorts开始抽风, 更新软件出现一大堆错误, 排查了很久, 总结了下面的从Snow Leopard升级到Lion后, MacPorts的过渡方法. 官方也有一篇 指导, 我总结的方法大同小异, 只是不需要手动重新安装所有软件, 那样会累死的…
第4步是关键, 保证了在升级时所有安装包都会重新下载.
原文发表于「桃源」: http://linux.cuit.edu.cn/?p=1362
一直以来, 我不太习惯于使用自动补全, 一是没有发现合适的 (这方面VS和Xcode这些IDE明显做得更好), 二来也觉得自己敲还来得快一点 (=_,=). 前几天发现 Gocode, 试用下来感觉还不错, 配置也不算繁琐, 推荐给使用Go语言的同学.
Gocode现在支持的编辑器有Vim和Emacs, 分别利用Vim的omni completion和Emacs的 Auto Complete Mode. 想看实际演示效果的同学可以点 这里. 这里主要讲一下安装和Emacs的配置, Vim的配置由于比较简单, 可以参考 官方文档.
很多同学在安装Gocode的时候都会出现编译错误, 这主要是因为Go语言发展比较迅速, 这个星期的版本和上个星期的版本之间都可能存在不兼容, 默认 clone 下来的Gocode代码都是兼容最新版Go语言的 (这作者真勤劳), 因此如果你的Go编译器版本太陈旧的话就可能编译失败. 比如我电脑上的Go版本是 r57.1 (weekly.2011-05-03), 是一个release版, 而最新的Gocode是针对weekly.2011-06-16的, 这是一个开发者版. 理所当然的, 我在编译的时候遇到了编译错误, 这时我有两种选择, 要么升级Go, 要么降级Gocode. 升级Go对于我来说不太方便, 因为我的Go版本都是随着软件仓库一起更新的, 因此我选择了降级Gocode. 还好Gocode的作者给那些存在兼容问题的版本都打上了tag, 我们只需找到兼容自己电脑上当前Go版本的tag, 再回滚就行了.
$ git log --decorate $ git checkout compatible-with-go-weekly.2011-04-27 |
接下来是编译和安装.
$ gomake $ sudo gomake install |
编译完成之后会生成 gocode 命令, 默认情况下 gomake install 会将 gocode 放到 GOBIN 环境变量指定的目录, 如果你没有设置 GOBIN, 那就会放到 ${GOROOT}/bin 里. 必须确保 gocode 命令可以在 PATH 环境变量中找到.
最后是配置Emacs. 首先你需要安装 Auto Complete Mode 扩展, 安装步骤可以参考 这里. 然后将Gocode提供的Emacs Lisp文件放到Emacs的 load-path 里即可. 注意这里使用到的el文件需要是最新版的 (非回滚版), 否则补全将失败.
现在用Emacs打开一个Go源文件试试看吧~
上周末跟几个朋友一起参加了 DEF CON 19 CTF Quals, DEF CON是一年一度的黑客大会, 期间会举办一些比赛, 最有名的就是CTF (Capture the Flag). 要进入CTF世界总决赛, 必须先参加CTF Quals, 也就是预选赛, 这是一个在线的竞速赛, 前12名就可以免费去Las Vegas. 当然我们是去不了了, 虽然比赛前还各种畅想…
今年的题型和往年不同, 新增了两类题. 当然这是从题型的名字上来说的, 其实考查的知识点还是大同小异, 只是今年的题要变态很多. 你可以在 这里 看到今年的所有题目, 一共有5种题型:
每种题型都有5道题, 分值分别是100, 200, 300, 400, 500. 我主要负责Forensics, 有点类似于数字取证, 也同时涉及一些与文件格式相关的知识. 网络安全领域我接触不多, 但是由于CTF Quals的大部分题都是在Unix/Linux下进行, 所以还是可以帮上一些忙. 比赛前做了一些准备, 把前三年的题都看了一遍, 当时感觉题目难度还行, 搞定100、200应该可以, 后面的如果下些功夫再加上运气也许也能做出来, 不过今年的题确实把我打击得不小.
比赛开始时间是6月4日凌晨3点, 我们几个人在3号晚上11点左右就提前聚集到实验室等待, 期间我等得无聊的时候便从萝莉哥那里拷了一部叫 Hackers 的电影来看. 这电影实在是不咋的, 拍得有点太假了, 不过里面的男女主角居然是sick boy和Angelina Jolie, 倒是有点意外. 终于到3点了, 但官网迟迟没有公布比赛网址, 在IRC里面看到有人在说服务器停电了需要推迟6个小时, 当时就觉得这也太滑稽了, 这么大一比赛居然会出这种状况. 后来证明我们并不需要等6个小时, 那是一个假消息, 不过比赛还是推迟了1个小时左右.
接下来就是艰苦的做题过程, 由于时差问题, 我们不得不熬了两个通宵. B100是最先放出的题目, 这道题多亏源哥思路活跃, 想到了把所有字符Base64解码, 可我们还是卡在了最后一步, 找不出key在哪里, 事后看别人的write-up时真是撞墙的冲动都有, 原来key就直接写在那里, 我们又想复杂了…
RR100很搞笑, 题目只有三根横线和一个感叹号: ____ ___ ______!, 当时大叔跟我说这道题的时候我立刻就想到了 “Hack the planet!”, 为什么呢? 因为这就是 “Hackers” 电影里的一句台词… 真是巧合, 看来CTF的人很喜欢这部电影, 往年的题目也有提到这句话.
F100是一道诡异的图片题, 给了一个19025x1像素的PNG图片, 这次还是源哥想到可以把图片切成很多段, 再拼接起来说不定会有什么结果. 事实是我们的确把图片拼出来了, 但却是误入了red herring, 我们把切割的像素搞错了. 只要切割正确, key立刻就会呈现出来, 这又是一道撞墙题.
F200倒是一道很典型的Forensics题目, 给了一个NTFS文件系统的dump, 需要从中找出蛛丝马迹. 往年这类题都是要想办法提取出文件系统中被删除的文件和内容, 今年貌似不太一样, 因为这个文件系统里没有删除过文件… 里面有100个文件夹, 每个文件夹里又有220个以key打头的小文件, 这倒如何是好? 每个key文件用 hexdump 看确实是有一些规律, 其中只包含了0到9, A到F这些字符, 这很容易联想到16进制, 但提取出来再组合成二进制文件貌似也没有什么特别的地方, 这道题也就卡在这里. 不过这道题貌似也没有队伍做出来, 现在还不清楚该如何解决.
F300是一道很有意思的题, 直接给了一个iOS系统的dump, 里面包含了成千上万的小文件、图片、音乐等等内容. 题目的提示说这道题跟一个地名有关, 于是我们就在里面寻找任何跟地址有关的信息. 最开始查了短信记录, 分析出这个iPhone貌似是一个二手货. 后来又看了邮件记录, 这里面倒是有很多地名, 还有一个貌似摩斯码的东西, 让我们欣喜若狂以为找到了key, 结果提交上去一个都不正确. 后来看write-up才知道是以iPhone定时记录的GPS信息为线索, 这个是不是在讽刺苹果侵犯用户隐私呢? :) 通过GPS找到的地点居然是在南极洲… 而且从Google地图上可以神奇地发现 那里 有一个类似藏宝图上红X的标记, 这道题果然跟海盗、宝藏有关, 真是佩服出题人. 后面找到key就顺理成章了.
F400因为时间关系我们没怎么看, 但需要利用的是 JailbreakMe 发现的针对iOS的PDF漏洞, 只要知道这个这道题也就迎刃而解.
几天时间下来遗憾颇多, 也能感到与高手间的差距, CTF Quals是竞速赛, 因此不仅要会做, 还得做得快, 毕竟这么大的题量不是一般人能搞定的. 地域文化的差别有时也会导致某些题目只有美国选手比较了解. 无论怎样, 能够在毕业之前和几个朋友一起参加这个比赛, 这种经历很爽, 明年我还希望再来.
今天在看CTF write-up时发现 有人提到 SPDY这样一个东西, 貌似跟Chrome项目有关, 于是在Geek原始冲动的驱使下了解了一下.
首先SPDY是一个应用层协议, 它被创造出来的唯一目的就是让Web更快、更快,还是更快. Google这家公司似乎很喜欢 “快” 这个东西, Chrome从诞生到现在每次几乎必定宣传自己有多么得快, 搞得大家已经产生了某种心理暗示. SPDY诞生于2009年, 其实这是对外公开发布的时间, 开始研究的时间应该更早. 众所周知, 如今的Web是通过HTTP协议和TCP协议进行传输, 但种种因素导致HTTP传输变得很慢:
既然HTTP有这么多缺点, 那应该不止Google自己想要解决, 其实是有的, 本着不重复造轮子的原则Google列举了现有的一些改进方案:
但是Google同学觉得以上这些都还不够, 它要追求更大程度的性能提升. 考虑到TCP现在应用还很广泛, 想替代也不是一天两天的事情, 但HTTP就不一样了, 它是应用层的! 所以说有自家的浏览器就是好办, 发明个应用层协议马上就可以上线. SPDY在刚出来的时候Google还在说这并不是用来替代HTTP协议的, 它只是一个中间协议, 但看看 最新的协议文档 里面已经将SPDY分为了两层, 其中一层被描述为HTTP-like, 大有取代HTTP的意图. 可以想到Google已经将提议提交给IETF, 也许未来的某一天我们就不再使用HTTP协议了. SPDY主要有以下一些特性:
这些改进到底能有多大提升? Google给出的数据是39%~55%, 在丢包严重或高延迟环境下, SPDY变现更加出色.
要支持SPDY, 除了客户端必须支持外, 还要有相应的Web服务器. 现在已经有 Python、Java、Apache moudle、Ruby 等各种实现.
最后, 如果你正在使用Chrome浏览器, 并且访问Google的网站, 那你已经开始使用SPDY了, 输入 chrome://net-internals/#spdy 还可以了解更加详细的信息.
大概1年前我写过一篇叫做 "小技巧: 在C++中实现在main函数之前及之后执行代码" 的文章, 当时文中只介绍了技巧, 却没有深究技巧实现的原因. 后来 Googol Lee 同学留言 说 《程序员的自我修养》这本书有详细讲述, 近期刚好把书看完, 便琢磨着自己再总结总结, 给原来的文章续一个结尾.
下面的分析都是基于以前那篇文章里的C++代码, 假设我们编译后生成的可执行文件叫做 alist. 首先祭出反汇编利器 objdump:
$ objdump -sdC alist | less |
我们都知道程序在进入 main 函数之前会执行 .init 段里的代码 (这个可以从glibc的入口函数源码中分析得到), 那么我们就从 .init 开始看起:
Disassembly of section .init:
08048508 <_init>:
8048508: 55 push %ebp
8048509: 89 e5 mov %esp,%ebp
804850b: 53 push %ebx
804850c: 83 ec 04 sub $0x4,%esp
804850f: e8 00 00 00 00 call 8048514 <_init+0xc>
8048514: 5b pop %ebx
8048515: 81 c3 e0 1a 00 00 add $0x1ae0,%ebx
804851b: 8b 93 fc ff ff ff mov -0x4(%ebx),%edx
8048521: 85 d2 test %edx,%edx
8048523: 74 05 je 804852a <_init+0x22>
8048525: e8 2e 00 00 00 call 8048558 <__gmon_start__@plt>
804852a: e8 41 01 00 00 call 8048670 <frame_dummy>
804852f: e8 0c 03 00 00 call 8048840 <__do_global_ctors_aux>
8048534: 58 pop %eax
8048535: 5b pop %ebx
8048536: c9 leave
8048537: c3 ret
重点看红色那行, 从调用的函数名就可以看出一定是此地无银, 跟进 __do_global_ctors_aux 函数 (这个函数的源码包含在GCC中) 看看:
08048840 <__do_global_ctors_aux>: 8048840: 55 push %ebp 8048841: 89 e5 mov %esp,%ebp 8048843: 53 push %ebx 8048844: 83 ec 04 sub $0x4,%esp 8048847: a1 fc 9e 04 08 mov 0x8049efc,%eax 804884c: 83 f8 ff cmp $0xffffffff,%eax 804884f: 74 13 je 8048864 <__do_global_ctors_aux+0x 24> 8048851: bb fc 9e 04 08 mov $0x8049efc,%ebx 8048856: 66 90 xchg %ax,%ax 8048858: 83 eb 04 sub $0x4,%ebx 804885b: ff d0 call *%eax 804885d: 8b 03 mov (%ebx),%eax 804885f: 83 f8 ff cmp $0xffffffff,%eax 8048862: 75 f4 jne 8048858 <__do_global_ctors_aux+0x 18> 8048864: 83 c4 04 add $0x4,%esp 8048867: 5b pop %ebx 8048868: 5d pop %ebp 8048869: c3 ret 804886a: 90 nop 804886b: 90 nop
红色部分是一个循环, 从 call *%eax 可以看出是在循环调用函数, 且 %eax 指向的是函数指针. 究竟 %eax 中是什么内容, 我们往回看那行蓝色的代码, 一切都引向了 0x8049efc 这个地址, 我们得看看其中包含什么, 再往回在其它段中寻找, 可以发现如下部分:
Contents of section .ctors: 8049ef8 ffffffff 55870408 00000000 ....U.......
0x8049ef8 加上4正好是 0x8049efc, %eax 这个函数指针的值也就是 0x8048755 (little endian), 再来看看 0x8058755 指向的函数是什么:
08048755 <global constructors keyed to a>: 8048755: 55 push %ebp 8048756: 89 e5 mov %esp,%ebp 8048758: 83 ec 18 sub $0x18,%esp 804875b: c7 44 24 04 ff ff 00 movl $0xffff,0x4(%esp) 8048762: 00 8048763: c7 04 24 01 00 00 00 movl $0x1,(%esp) 804876a: e8 7d ff ff ff call 80486ec <__static_initialization_and_destruction_0(int, int)> 804876f: c9 leave 8048770: c3 ret 8048771: 90 nop
继续跟进 __static_initialization_and_destruction_0(int, int) 函数:
080486ec <__static_initialization_and_destruction_0(int, int)>:
80486ec: 55 push %ebp
80486ed: 89 e5 mov %esp,%ebp
80486ef: 83 ec 18 sub $0x18,%esp
80486f2: 83 7d 08 01 cmpl $0x1,0x8(%ebp)
80486f6: 75 5b jne 8048753 <__static_initialization_
and_destruction_0(int, int)+0x67>
80486f8: 81 7d 0c ff ff 00 00 cmpl $0xffff,0xc(%ebp)
80486ff: 75 52 jne 8048753 <__static_initialization_
and_destruction_0(int, int)+0x67>
8048701: c7 04 24 d5 a0 04 08 movl $0x804a0d5,(%esp)
8048708: e8 5b fe ff ff call 8048568 <std::ios_base::Init::Init()@plt>
804870d: b8 88 85 04 08 mov $0x8048588,%eax
8048712: c7 44 24 08 28 a0 04 movl $0x804a028,0x8(%esp)
8048719: 08
804871a: c7 44 24 04 d5 a0 04 movl $0x804a0d5,0x4(%esp)
8048721: 08
8048722: 89 04 24 mov %eax,(%esp)
8048725: e8 1e fe ff ff call 8048548 <__cxa_atexit@plt>
804872a: c7 04 24 d4 a0 04 08 movl $0x804a0d4,(%esp)
8048731: e8 3c 00 00 00 call 8048772 <A::A()>
8048736: b8 9e 87 04 08 mov $0x804879e,%eax
804873b: c7 44 24 08 28 a0 04 movl $0x804a028,0x8(%esp)
8048742: 08
8048743: c7 44 24 04 d4 a0 04 movl $0x804a0d4,0x4(%esp)
804874a: 08
804874b: 89 04 24 mov %eax,(%esp)
804874e: e8 f5 fd ff ff call 8048548 <__cxa_atexit@plt>
8048753: c9 leave
8048754: c3 ret
终于找到了调用类的构造函数的地方, 在调用完构造函数之后还通过 __cxa_atexit 函数 (这是glibc用于内部调用的函数, 功能和我们熟知的 atexit 函数类似) 注册了一个回调函数, 函数地址是 0x804879e, 可以猜测这个函数就是析构函数, 找到这个地址:
0804879e <A::~A()>: 804879e: 55 push %ebp 804879f: 89 e5 mov %esp,%ebp 80487a1: 83 ec 18 sub $0x18,%esp ...
果然如我们所想, 这样就保证了构造函数和析构函数的配对使用. 总结一下C++中全局变量初始化的过程:
_init -> __do_global_ctors_aux -> global constructors keyed to a -> __static_initialization_and_destruction_0(int, int) -> A::A()
这里只是从反汇编的角度进行分析, 如果需要更深入了解, 还得结合glibc和GCC的源码. 关于 .ctors 段也没有详细讲述, 这些都可以在《程序员的自我修养》中找到.
有时我们想要监视某个网页是否有更新, 但网站又没有提供RSS, 这时就只能定期去查看那个网页, 偶尔也会因此忘记. 这种枯燥、乏味的重复行为让人厌倦, 于是我实现了一个简单的小项目, 我把它叫做 Inspector. 输入你想要监视的网址, 选取你感兴趣的网页元素, 然后Inspector就会在有更新时通过Email通知你. It’s simple, huh?
下面是一些使用截图, 首先是选取网页元素:
然后是Inspector发给你的通知邮件 (最好把邮件地址先加到联系人里, 一般来说第一次都会被判为垃圾邮件, 至少Gmail是这样):
网站只在Firefox、Chrome和Safari中测试过, 访问可能需要翻墙 (由于某种众所周知的原因, 你懂的). 目前项目代码已经托管在GitHub: https://github.com/xiaogaozi/inspector, 如果你有任何问题或者建议可以到 这个页面 提出.
接下来是一些实现这个项目过程中的技术吐槽, 不感兴趣的同学可以直奔 项目主站 围观.
选取网页元素这点是借鉴的Firebug, 同时借鉴的还有它的代码. 在使用JS绑定事件时遇到了 this 关键字引用错误问题, 最后在 “JavaScript: The Definitive Guide” 中找到了答案, 具体可参考书中17.1.5小节. 呵呵, JS基础没学好. “/inspect” 页面最下面的提示要感谢兰姐提醒用 iframe, 对于固定样式很有好处. 修正网页CSS、JS链接这个需要判断是否为绝对地址, 常规的也就是http或者https打头, 不过 The World Clock 网页的CSS引用很奇怪, 是 “//” 打头, 测试大猫的博客时也让我发现一个类似的bug. 像Facebook这样的通过代码动态加载CSS的也没办法, 还有如果是用 frameset 写的页面现在还暂时不支持.
页面抓取现在对于静态页面效果要好一点. 定位网页元素现在比较常用的有CSS selector和XPath, 前者我更熟一点, 不过Python标准库不支持, libxml2可以, 但它所需要的so库不能上传到GAE, 只能作罢, 而且它只能分析那些标准的XML, 遇到稍微不标准的就会出错, 这点和标准库一个德行. 但互联网上充斥着各种不标准, 这由不得开发者挑剔, 要对付它们, Beautiful Soup 是一个不错的选择. 但问题又来了, BS即不支持CSS selector, 也不支持XPath. 后来我找到一个日本人写的 BS扩展, 可以用上XPath. 至于CSS selector, 我也找到 一份Python实现, 需要结合Tidy来处理那些非标准页面, 不过我没试过.
能这么快完成, 也得益于GAE的便捷 (虽然前前后后花了有半个来月). 用户验证、Email服务、数据存储这些都能通过很简单的API搞定, 不过自带的Django模板引擎有点弱智, 一些高级语法支持不了.
页面风格抄的GitHub, 按钮是GitHub和Twitter的结合, 表格抄的GAE, About页面用AsciiDoc写的, 看起来还算凑合. 第一次用Closure Compiler, 第一次用CSSTidy, 压缩了确实不错.
最后, 如果你坚持看到这里的话, 感谢你的支持.
原文发表于「桃源」: http://linux.cuit.edu.cn/?p=1169
我们已经习惯了各种包管理器工具, Debian、Ubuntu有 apt-get, RedHat、Fedora有 yum, Arch有 pacman, Gentoo有 emerge, BSD有 port. 包管理器的存在让我们再也不用考虑安装、删除、升级软件的细节, 也不用为了各种包之间的依赖关系而头疼, 这也是我认为UNIX/Linux相比于Window$的一个很大的优点. 今天介绍的 ELPA (Emacs Lisp Package Archive) 就是专为Emacs设计的包管理器, Emacs发展至今已经拥有了数量庞大的第三方扩展, 但安装这些扩展的方式依旧非常原始. 下载压缩包, 解压, 放到Emacs可以识别的 load-path, 修改 “.emacs”, 这一系列动作对于用户来说实在是不够友好 (虽然Emacs用户普遍喜欢折腾). ELPA可以使得安装Emacs扩展就像使用发行版的包管理器工具安装软件一样便捷, 并且Emacs官方也 决定 在下一个Emacs版本, 即Emacs 24中默认集成ELPA. 这里 是GNU的官方ELPA页面, 在那里你也可以下载到已经集成了ELPA的Emacs 24测试版.
ELPA采用同 apt-get 等包管理器工具类似的原理, 会从指定的源服务器中下载扩展以及相关依赖扩展, 然后放到特定目录 (默认为 “~/.emacs.d/elpa/”), 可能会将Lisp代码编译成字节码, 最后激活它, 当下次Emacs启动时自动加载它.
虽然Emacs 24还没有正式发布, 但是我们已经能够提前使用ELPA (实际上ELPA已经发展了好几年). 步骤很简单, 如果你使用的是Emacs 22及以上版本, 把下面的代码复制到Emacs的 *scratch* buffer中, 将光标移动到代码的最后一行, 然后按下 C-j, 一切就都搞定了, 剩下的事情就交给代码去完成吧. 如果你使用的是Emacs 21及以下版本, 请参考 这个帮助页面.
(let ((buffer (url-retrieve-synchronously "http://tromey.com/elpa/package-install.el"))) (save-excursion (set-buffer buffer) (goto-char (point-min)) (re-search-forward "^$" nil 'move) (eval-region (point) (point-max)) (kill-buffer (current-buffer)))) |
上面的代码执行完之后, ELPA就算安装完毕, 这时会多出一个 “~/.emacs.d/elpa/” 目录, 所有通过ELPA安装的扩展都会放在那里, 并且 “.emacs” 文件也被自动添加了加载ELPA的代码. 该如何通过ELPA安装扩展呢? 在Emacs中按下 M-x package-list-packages 会列出所有可供安装和已经安装的扩展, 如下图:
将光标移动到需要安装的扩展上, 按下 i 键, 就会出现如上图的 “I” 标志, 选定好之后按下 x 键便开始安装扩展. 还有其它一些按键:
按键 | 功能 |
---|---|
n |
下移一行 |
p |
上移一行 |
r |
刷新扩展列表 |
i |
标记为安装 |
d |
标记为删除 |
u |
取消标记 |
g |
还原所有标记 |
x |
执行标记 |
q |
关闭当前窗口 |
h |
帮助 |
? |
查看当前扩展的详细注释信息 |
当前ELPA的最大缺点是扩展数量还不是很多, 截至写这篇文章时仅有134个扩展. 有人说是因为ELPA只接受自由软件的缘故, 但我觉得大多数Emacs扩展开发者并不会太在意这个, 可能最大的原因还是因为大部分开发者都不知道ELPA的存在. Emacs发展这么多年, GNU也没有 像Vim那样 集中管理各种第三方扩展, 好在官方也终于决定在Emacs 24中集成ELPA, 可以预想将来也会逐渐丰富扩展仓库. 如果你是第三方扩展的开发者并且也愿意将扩展提交给ELPA, 可以参考 这个页面, YASnippet的作者pluskid也写了 一篇给需要提交给ELPA的作者的建议, 希望ELPA的扩展仓库可以越来越丰富.
最后, GNU正在寻找用于Emacs 24的合适配色主题, 有兴趣参与的同学可以访问 这个页面 自定义一份主题提交给GNU, 也许你的方案就会出现在下一个版本的Emacs中. 参与开源, 我为人人, 人人为我.
发现我现在的题目真是越来越长了, 越来越有论文开题的范了~
因为安装了MacPort, 我在 “.bashrc” 文件中自定义了 PATH 变量, 但当我打开 “Emacs.app” 时却发现 PATH 变量并没有被正确读取, 最明显的表现就是会找不到我用MacPort安装的程序. 经过一番查找才 发现, 原来是因为Mac OS X中的GUI程序在启动时并不鸟Shell初始化文件, 管你 bash, tcsh, 什么 sh. 在Mac中有一个专门给GUI程序 设置环境变量的地方: ~/.MaxOSX/environment.plist, 这是一个二进制文件, 你可以使用Xcode自带的 “Property List Editor.app” 来查看、修改其中的内容, Mac中还有一个命令 defaults 也可以对这种类型的文件进行读写. 我们可以稍微修改一下 “.bashrc” 文件以便每次自定义好 PATH 变量之后就立即修改 “environment.plist” 文件, 代码如下:
export PATH=... defaults write ~/.MacOSX/environment PATH "$PATH" |
这样当再次打开 “Emacs.app” 时查看到的 PATH 变量就是正确的值了. 同样我在使用 “MacVim.app” 时也遇到了这样的情况, 我本来以为通过上面的方法已经解决, 但… 你懂的, 再次经过一番查找, 很多人也只是推荐上面的方法. 还有 说 勾选MacVim的 “Preferences” 中的 “Launch Vim processes in a login shell.”, 但在我的MacVim中没有找到这个选项, 应该是新版本取消掉了. 我怀疑这是MacVim的bug (不然为啥 “Emacs.app” 好好的…), 不过貌似也不是所有人都有遇到. 而我现在找到的唯一解决办法是直接从Terminal中执行 “mvim” 命令来启动MacVim, 这样就会继承Shell的环境变量, 不过这也是不得已才想出的临时解决办法, 毕竟我以后就不能直接用Quicksilver来启动MacVim了, 真是遗憾.
综上, Mac的App真够折腾.