<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>年华转瞬</title>
	<atom:link href="http://blog.xiaket.org/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.xiaket.org</link>
	<description>xiaket 的网志</description>
	<lastBuildDate>Tue, 13 Jul 2010 13:42:21 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>Kindle 2初印象</title>
		<link>http://blog.xiaket.org/2010/07/11/on-kindle-2/</link>
		<comments>http://blog.xiaket.org/2010/07/11/on-kindle-2/#comments</comments>
		<pubDate>Sat, 10 Jul 2010 18:16:55 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[游记]]></category>
		<category><![CDATA[Kindle]]></category>
		<category><![CDATA[香港]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=450</guid>
		<description><![CDATA[前言

前不久Kindle 2降价至$189, 于是托在香港的西西同学购入一部以消耗我电脑中的电子书. 感谢西西同学帮我订货收货, 昨天又陪我逛了一天的街. 真乖, 发小红花表扬下~

现在默认的电量已经被我玩完了, Kindle已经送回冥王星充电了, 于是这儿只是短短几个小时的一点点初步感受.

<span class="readmore"><a href="http://blog.xiaket.org/2010/07/11/on-kindle-2/" title="Kindle 2初印象">阅读全文——共1224字</a></span>]]></description>
			<content:encoded><![CDATA[<h3>前言</h3>
<p>前不久Kindle 2降价至$189, 于是托在香港的西西同学购入一部以消耗我电脑中的电子书. 感谢西西同学帮我订货收货, 昨天又陪我逛了一天的街. 真乖, 发小红花表扬下~</p>
<p>现在默认的电量已经被我玩完了, Kindle已经送回冥王星充电了, 于是这儿只是短短几个小时的一点点初步感受.</p>
<p>免责声明: 1. 以下纯属个人意见, 文字内容仅供参考; 2. 本人相机比较渣, 所摄图片仅供参考&#8230;</p>
<h3>硬件级的观感</h3>
<ol>
<li>外观: 看起来还算大气, 满意.</li>
<li>重量: 不算皮套还是比较轻的, 比一本普通杂志稍重. 不过配上皮套就有点点分量了, 能接受.</li>
<li>响应速度: 能感觉得到延迟, 但是这方面我要求不高, 满意.</li>
<li>容量: 2G, 反正只是放文本, 这个大小显然足够了, 满意</li>
<li>充电器: 附送的电源转接头从110V到250V自适应, 小赞下. 充电时间文档说是三个小时, 有点尴尬, 不过当然是高于可接受的.</li>
<li>附属功能: 能免费3G上网看网页, 这个imba.</li>
</ol>
<p>总结: 满意.</p>
<h3>看书</h3>
</p>
<p>拿到货时里面有amazon附送的说明文档, 看起来效果不错, 能调节字的大小, 不同大小的字看起来都很清晰. 回家的大巴上到amazon下了个Holmes的sample, 看起来效果也很好. 又往里面放了一份pdf和一群txt. txt文件显示一切正常. 但是pdf文件由于原文件字体相对较小, 直接以&#8221;适合宽度&#8221;来看会很吃力, kindle提供了缩放的功能但是读书时不可能每一行去翻页的. 我后来用横屏来看发现字体大小可以接受. 除掉这一点以外, 我对Kindle的读书功能只能多说一句话了: &#8220;你看的实体书是怎样, 在Kindle上看的效果就是怎样.&#8221;</p>
<p>以下开始无良堆图:</p>
<p><img src="/wp-content/uploads/2010/07/kindle-full.png" alt="homepage of kindle" /></p>
<p>Kindle的外观.</p>
<p><img src="/wp-content/uploads/2010/07/kindle-home.png" alt="homepage of kindle" /></p>
<p>Kindle的首页, 往里面放了几本书, 还没整理.</p>
<p><img src="/wp-content/uploads/2010/07/kindle-holmes.png" alt="sample display" /></p>
<p>Holmes中两位主角第一次见面. 话说为什么我突然觉得这么遣词造句很破坏这两位的形象呢? &#8211; -</p>
<p><img src="/wp-content/uploads/2010/07/kindle-twitter.png" alt="need not say" /></p>
<p>这个, 不解释了.</p>
<p>总结: Kindle看合适格式的书是一件很爽的事情~</p>
<h3>上网</h3>
<p>我还没把中文字体放进Kindle(我还没想好要不要放进去&#8230;), 于是显示中文网页的时候汉字都是乱码. 另外, 由于这个浏览器对js/css的支持有限, 于是看网页发现结构错乱是意料中事. 但是我惊喜地发现竟然可以登录网易微博, 虽然我可耻地发推失败了.</p>
<p>好吧, Kindle实际上是一个挺不和谐的东西, 至于为什么这儿就不深入讲了. 简单的说: 没有长城~</p>
<h3>听歌</h3>
<p>好吧我在逗你玩呢. 作为一个ipod nano用户, 我近期及在可预料的未来中都没有使用Kindle来听歌的打算. Kindle本身容量只有2G, 还是省省吧.</p>
<h3>关于Kindle, 其他</h3>
<p>几个屏保都还是很有味道的.</p>
<p>反正只是偶随便写写, 于是关于这次香港之行再碎碎念几句.</p>
<ul>
<li>400多入的一个新秀丽挺值的.</li>
<li>上次(今年年初时)两人在C!tySuper围观了半天啥东西没买, 今天在里面两人买了三辆车, 当然必然显然是小模型. 是的, 我们很怀旧&#8230;</li>
<li>八点那班的中港通真不靠谱真不靠谱真不靠谱, 下午一点才到旺角!</li>
<li>香港的满记比广州要好吃一点点, was it real or just my fantasy?</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2010/07/11/on-kindle-2/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>怀念下外教</title>
		<link>http://blog.xiaket.org/2010/07/05/foreign-language-reading-room/</link>
		<comments>http://blog.xiaket.org/2010/07/05/foreign-language-reading-room/#comments</comments>
		<pubDate>Mon, 05 Jul 2010 15:08:47 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[其它]]></category>
		<category><![CDATA[南京大学]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=447</guid>
		<description><![CDATA[外教是我的母校南京大学图书馆二楼的外文教材阅览室的非正式简称. 我在南大的这么多年里, 在这儿度过了太多的岁月. 由于这儿不啻于是一个书的海洋, 所以我更愿意用语法上完全说不通的渡过而不是度过来表达我对这个阅览室的无尽的热爱.

本科三年级搬到鼓楼之后, 住宿条件和自习条件相对于浦口都有了很大程度的下降, 在图书馆自习就成了自然的选择. 相对于图书馆二楼的期刊阅览室和三楼的文理科借书处, 外教人少, 硬件条件也好, 于是我从开始便喜欢在里面自习. 大三修的物化和结构, 里面的几本相关的教材都翻看过. 记得比较清楚的是一本红色硬封皮的书是量化所研究生教材的一部分. 另外, 这儿的实验书给我的物化实验报告和后来的综合化学实验报告提供了很多素材. 后来考研时自学量子力学, 看的是Griffiths的量子力学导论, 这本硬皮书上黑色背景和烫金的薛定谔猫实在令人难忘. 入门后常用的Sakurai的Modern Quantum Mechanics就是在这儿复印的.

<span class="readmore"><a href="http://blog.xiaket.org/2010/07/05/foreign-language-reading-room/" title="怀念下外教">阅读全文——共1487字</a></span>]]></description>
			<content:encoded><![CDATA[<p>外教是我的母校南京大学图书馆二楼的外文教材阅览室的非正式简称. 我在南大的这么多年里, 在这儿度过了太多的岁月. 由于这儿不啻于是一个书的海洋, 所以我更愿意用语法上完全说不通的渡过而不是度过来表达我对这个阅览室的无尽的热爱.</p>
<p>本科三年级搬到鼓楼之后, 住宿条件和自习条件相对于浦口都有了很大程度的下降, 在图书馆自习就成了自然的选择. 相对于图书馆二楼的期刊阅览室和三楼的文理科借书处, 外教人少, 硬件条件也好, 于是我从开始便喜欢在里面自习. 大三修的物化和结构, 里面的几本相关的教材都翻看过. 记得比较清楚的是一本红色硬封皮的书是量化所研究生教材的一部分. 另外, 这儿的实验书给我的物化实验报告和后来的综合化学实验报告提供了很多素材. 后来考研时自学量子力学, 看的是Griffiths的量子力学导论, 这本硬皮书上黑色背景和烫金的薛定谔猫实在令人难忘. 入门后常用的Sakurai的Modern Quantum Mechanics就是在这儿复印的.</p>
<p>说完偏专业的书籍, 我更喜欢在这儿乱翻. 这儿的字典很全, 从大不列颠百科全书到巴掌大的小手册都有. 在这儿混查字典肯定是常事. 我常用的一本是82年版的The Concise Oxford Dictionary of Current English. 前几天在淘宝上看见一模一样的二手书还要200块, 也许哪天冲动了就会去拍下来吧. 另外, 如果这本厚厚的字典还搞不定, 我可能去查两卷本的The Shorter Oxford English Dictionary. 顺便说一句, 直到前几天我才知道这两本字典都是源自更厚的一本Oxford<br />
English Dictionary. 至于大不列颠百科全书, 我倒还真因为写论文而查过一次, 不过具体细节已经记不请了. 另外, 这儿的专业工具书还包括CRC的数据手册, 学过化学应该多少听说过这本书. 虽然手头有电子版的这本手册, 但是把一本厚重的书放在手里把玩的感觉是无可替代的.</p>
<p>说完了工具书, 本应该继续怀念下文学性的书. 但我当时在学校时这方面的书看得比较少, 而且大都看过就忘, 所以现在也列不出个一二三来了. 大致记得自己翻过一些世界历史和英美文学介绍的教科书. 另外, 这儿也有数学, 计算机的教材, 我也都把这些书当作工具书来看. 要找某个方程的解法时就会跑过去翻翻这本看看那本. 而一开始的Fortran入门也是在这儿看书学会的.</p>
<p>外教经常去的总是那么些人, 一来二去至少混个眼熟. 英语系的专业人士自然有不少来这儿学习, 我经常在那儿看到本科低年级时参加英语俱乐部认识的几个同学. 我们系除了我之外, 我周围的几个人也在我的怂恿下成为外教的常客. SCDA(一个学生求职就业组织)当时的会长经常去, 现在这个mm好像在深圳的招行总行. 本科四年级时一个生科的小个子男生经常去, 经常抱着厚厚的生化专业书籍埋着头看, 记得本科毕业后这个男生去了美国一个很好的学校.</p>
<p>铁打的营盘流水的兵, 写完图书馆里那些熟面孔, 接下来写一下这个阅览室的老师. 这些老师大都是中老年妇女, 大部分时间都是专门守在门口玩小游戏, 非常偶尔的时候也会在书架后小声的闲聊. 虽然去得够多按说能够混个脸熟了, 但到了要管人的时候这些老师仍会很不讲人情地按章办事. 但想起这些老师在你早上去图书馆时会朝着你笑, 你就无论如何不会对这些老师生气, 而会感觉到她们真是在关心你的.</p>
<p>现在我已不是南大学子, 要进入这间阅览室也已不复可能. 但是我还是希望着能有一天和以前一样, 背着书包, 到图书馆, 熟门熟路地走进外教, 放下包, 拿起书, 放在某个位置上, 然后在书架上找本喜欢的书, 躲在某个书架后面, 半躺在那个书架旁的垫有垫子的石座上, 先轻松几分钟再开始今天的学习吧.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2010/07/05/foreign-language-reading-room/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>如果</title>
		<link>http://blog.xiaket.org/2010/07/03/if/</link>
		<comments>http://blog.xiaket.org/2010/07/03/if/#comments</comments>
		<pubDate>Fri, 02 Jul 2010 17:04:22 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[文学]]></category>
		<category><![CDATA[小说]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=443</guid>
		<description><![CDATA[他坐在角落里, 想, 如果没有如果这个词, 世界会变成什么样? 或者, 有没有一种语言里是没有如果这个概念的? 使用这个语言的人都活在当下, 没有对未知的盘算, 也没有对已逝的挽留. 这样多好. 世界也会简单好多. 不过, 话说回来, 如果没有时间的概念, 没有对过去的反思和对未来的预期, 那么使用这种语言的民族也很难得以快速发展吧?

想想另一个问题, 如果如果只能用在一个时间方向上的假设, 即要么只能假设已发生的事情没有发生, 要么只能假设将要发生的事情会如何如何发生? 哪个会更甜蜜些呢? 他一边想着, 一边无意识地翻看着手机里的消息, 如果当时他没发这条消息, 事情会不会有改观? 从这个角度来说, 他是更喜欢单向的往前的时间箭头吧? 但是, 光把幻想消磨在以前的事情上真的对以后的经历有帮助吗? 而幻想本身也许就是虚妄的, 对将来没有帮助的? 想着想着, 他自己也不能确定了.

<span class="readmore"><a href="http://blog.xiaket.org/2010/07/03/if/" title="如果">阅读全文——共580字</a></span>]]></description>
			<content:encoded><![CDATA[<p>他坐在角落里, 想, 如果没有如果这个词, 世界会变成什么样? 或者, 有没有一种语言里是没有如果这个概念的? 使用这个语言的人都活在当下, 没有对未知的盘算, 也没有对已逝的挽留. 这样多好. 世界也会简单好多. 不过, 话说回来, 如果没有时间的概念, 没有对过去的反思和对未来的预期, 那么使用这种语言的民族也很难得以快速发展吧?</p>
<p>想想另一个问题, 如果如果只能用在一个时间方向上的假设, 即要么只能假设已发生的事情没有发生, 要么只能假设将要发生的事情会如何如何发生? 哪个会更甜蜜些呢? 他一边想着, 一边无意识地翻看着手机里的消息, 如果当时他没发这条消息, 事情会不会有改观? 从这个角度来说, 他是更喜欢单向的往前的时间箭头吧? 但是, 光把幻想消磨在以前的事情上真的对以后的经历有帮助吗? 而幻想本身也许就是虚妄的, 对将来没有帮助的? 想着想着, 他自己也不能确定了.</p>
<p>多想无益于事情的解决, 终究得面对现实的吧? 是这样, 如果我没有花时间在这种无聊的思考上, 也许能够做点更有意义的事情吧. 不过怎么样的事情才算是有意义的事情呢? 人生本来就是要一定程度上合理浪费的吧? 这么想着, 他直起身, 向着右边的那个漂亮女生走去.</p>
<p>&#8220;我要一份无骨鸡柳汉堡, 一份中薯条, 再来一份低咖啡因的摩卡咖啡, 大杯. 我不参加加两块钱送赠品的活动. 堂食, 麻烦等餐全齐以后一次送给我, 我坐在那边的角落. 这儿是四十二块五, 请找给我五块钱. 就这样.&#8221;</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2010/07/03/if/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>简体与繁体</title>
		<link>http://blog.xiaket.org/2010/06/22/simplified-and-traditional-chinese-characters/</link>
		<comments>http://blog.xiaket.org/2010/06/22/simplified-and-traditional-chinese-characters/#comments</comments>
		<pubDate>Tue, 22 Jun 2010 03:56:12 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[其它]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=437</guid>
		<description><![CDATA[昨天看到一个帖子, 尝试用定量的角度来比较简体汉字和繁体汉字. 这个帖子专业性比较强, 就我的理解, 其主要逻辑是用字体的几何重心偏离方块的中心的程度来衡量字体的稳定性. 为了让这个概念更明确一点, 我们来举几个例子, 我们来看&#8221;旧&#8221;这个字:



旧这个字的字形相对简单点, 方便我们后面的计算. 上面这张图里, 灰色是选中字后的高亮效果, 我们也可以大概知道这个字是在一个什么样的正方形里. 对于这个字, 我们要考虑的是这个字的几何重心相对于这个灰色的正方形的偏移程度. 为了让你明白几何重心的原理, 我们来考虑一个更简单的情形. 由于旧这个字的字形比较特殊, 我们可以在研究的开始粗略地认为这个字是&#8221;田&#8221;字切掉左边三短横的结果. 现在我们来试图计算下面这个形状的几何重心:

<span class="readmore"><a href="http://blog.xiaket.org/2010/06/22/simplified-and-traditional-chinese-characters/" title="简体与繁体">阅读全文——共2032字</a></span>]]></description>
			<content:encoded><![CDATA[<p>昨天看到<a href="http://yulewang.spaces.live.com/Blog/cns!5C815C994ABB661E!844.entry?wa=wsignin1.0&#038;sa=3640316" target="_blank">一个帖子</a>, 尝试用定量的角度来比较简体汉字和繁体汉字. 这个帖子专业性比较强, 就我的理解, 其主要逻辑是用字体的几何重心偏离方块的中心的程度来衡量字体的稳定性. 为了让这个概念更明确一点, 我们来举几个例子, 我们来看&#8221;旧&#8221;这个字:</p>
<p><img src="http://blog.xiaket.org/wp-content/uploads/2010/06/jiu.png" /></p>
<p>旧这个字的字形相对简单点, 方便我们后面的计算. 上面这张图里, 灰色是选中字后的高亮效果, 我们也可以大概知道这个字是在一个什么样的正方形里. 对于这个字, 我们要考虑的是这个字的几何重心相对于这个灰色的正方形的偏移程度. 为了让你明白几何重心的原理, 我们来考虑一个更简单的情形. 由于旧这个字的字形比较特殊, 我们可以在研究的开始粗略地认为这个字是&#8221;田&#8221;字切掉左边三短横的结果. 现在我们来试图计算下面这个形状的几何重心:</p>
<p><img src="http://blog.xiaket.org/wp-content/uploads/2010/06/tian.png" /></p>
<p>这个形状是对于中间的虚线上下对称的, 因此这个图形的重心肯定在这条虚线上, 我们来看左右这个方向上重心的位置. 我们继续简单地假设上面这张图里面每条短线的重量是1, 长度也是1. 在前面我们已经说明, 重心肯定在中间的虚线上, 所以我们可以将这些短线的重量化归为中间虚线上的点的重量, 得到下面这样的图:</p>
<p><img src="http://blog.xiaket.org/wp-content/uploads/2010/06/points.png" /></p>
<p>计算一条线上的点的重心是相对容易的:</p>
<pre>
(2*0 + 2*1 + 3*1.5+ 2*2)/(2+2+3+2) = 1.17
</pre>
<p>由于正方形的边长为2, 因此它的中心是(1, 1), 而我们计算得到的重心是(1.17, 1), 因此这个字就在x轴方向上偏移了17%.</p>
<p>上面费了不少笔墨, 只是向你解释原文中的中心及几何重心的概念, 以及偏离程度是怎么计算出来的. 当然, 虽然逻辑上每个重心都是要这么计算的, 但是原文要考虑的情形要复杂很多. 首先, 你可以回去看看前面贴出来的&#8221;旧&#8221;这个字的字形, 你首先会发现它左边那个竖和右边的距离比较短, 而且右边的&#8221;日&#8221;字下面还有些装饰性的出头. 而对于不同的字体, 面前说的距离和装饰线也许又有细微的区别, 而且可能线的宽度也会有区别(想想隶书). 因此, 原作者在计算几何重心时需要的工作量比我们这儿的粗略计算要大不少. 当然他所进行的计算都是用计算机完成的.</p>
<p>现在我来概括下原作者的工作: 原作者将道德经的某一段作为样本, 对于每个不同的字体, 计算了这一段样本的每个汉子的几何重心, 然后计算它相对于正方形中心的偏离程度, 然后对每个字体得到这样的一个平均值. 现在你可以看懂原文末尾给出的这些表了.</p>
<p>介绍完原文中的工作, 偶现在来评价下他的工作. 原贴说:</p>
<pre>
方正、中易等字体平均中心偏离正中，居左下角，标准差偏大（0.1左右）。类似中华民国教育部字标准楷书、黑
体-繁等字体，平均中心均为正中，标准差很小（0.05左右）。可见繁体字体的中心平稳度超过简体字体。

这个事实上是由于标点符号导致的。在中国大陆，标点符号比如逗号句号，都放置于左下角，而在香港、台湾等
地，标点符号位于字方中心位置。所以，港台的文字平稳性更好。这也是中国大陆书籍版面斑驳，每行有很强割裂
感的主要原因。 

所以，国家语委语言文字应用研究所《标点符号用法》课题组应该为用户的阅读困难和版面的斑驳付出责任。《国
家标准标点符号用法》起草人应该抓起来关着。
</pre>
<p>这一点实际上是比较没道理的. 首先, 港台的书籍很多都是竖排的, 因此, 繁体字中, 将标点符号放在字方中心位置是正常的, 而对于横排的字体, 将标点符号放在字方中心本身就是违背汉字从上到下从左到右的书写习惯的. 另外, 就我的个人观感, 读书这么多年从没感觉到简体字的排版有很强的割裂感(Word排出来的东西不论). 原作者在理性分析讨论后突然插入一句很没理性的话, 要把标点符号用法的起草人抓起来关着, 这让我感觉莫名突兀和不可理喻.</p>
<p>在作者去掉标点符号后, 重新计算了相关数据, 并比较了繁体汉字和简体汉字的差异, 他的结论是:</p>
<pre>
可以看到，对于相同的字体，改为繁体后，标准差亦有所减少，虽然减少的数值有限，其原因主要是因为很多汉字
的繁体和简体字形是相同。不过我们依然可以看到平均的标准差减小了 10%和15%左右。所以，说简化汉字破坏了
汉字的美观性，一点不错。当初鼓吹简化汉字的人都该拉出去砍了。
</pre>
<p>我个人认为, 这一点结论又太过主观了. 繁体字相对于简体字, 比划本来就多出不少, 在比划更多的情况下, 几何重心本身就是更偏向于中心的. 或者更宽泛一点说, 用几何中心相对于物理中心的偏离来衡量不同字体的稳定程度或许是合理的, 但是如果将繁体字拿来和简体字比较本身就是不合理的. 而将字体衬线, 间距等弃于不顾, 将字体的稳定程度等同于字体的美观程度就更没有逻辑性可言了. 至于原作者最后说的, &#8220;当初鼓吹简化汉字的人都该拉出去砍了&#8221;, 在我眼里就只是笑谈了.</p>
<p>最后扯点简体繁体的问题, 我对简体字毫无恶感, 正如我对繁体字毫无恶感一样. 如果在理性的框架下讨论简体繁体的问题, 我想我无论什么结论都会支持的. 但是我很难理解很多人都是从个人观感出发来讨论这个问题. 另外, 对于繁体字和文化传统上千丝万缕的联系, 我想这个问题更容易解决. 对于应该用繁体的场合, 例如史书, 古文, 诗词, 书法等, 当然应该使用繁体. 但是我想简体字的流行应该是很难阻挡的.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2010/06/22/simplified-and-traditional-chinese-characters/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>自定义python类方法实现加减比较操作</title>
		<link>http://blog.xiaket.org/2010/06/19/python-class-methods-customize/</link>
		<comments>http://blog.xiaket.org/2010/06/19/python-class-methods-customize/#comments</comments>
		<pubDate>Sat, 19 Jun 2010 03:34:04 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Python开发]]></category>
		<category><![CDATA[Django开发]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=429</guid>
		<description><![CDATA[最近在写一个组内项目, 需要把一群一群的类似字典的对象实现类似相加的逻辑. 为了让代码更好看逻辑性更强, 偶玩了下python的自定义类方法.

首先更详细地介绍下背景: 我们需要在Django中记录一个模型变化的历史, 我们有需求会将这个模型的某个实例的现在的状态和它的若干个变化历史相加. 为此, 我们用Django的序列化方法把这个实例导出成一个json字符串, 然后用这个字符串实例化一个类似字典的类. 变化历史的各种相加相减操作就是通过自定义这个类似字典的类的特殊的类方法而实现的.

既然这个类是用一个json字符串来实例化的, 我们简单地将这个类的名字定为Json, 其初始化方法类似:

<span class="readmore"><a href="http://blog.xiaket.org/2010/06/19/python-class-methods-customize/" title="自定义python类方法实现加减比较操作">阅读全文——共3611字</a></span>]]></description>
			<content:encoded><![CDATA[<p>最近在写一个组内项目, 需要把一群一群的类似字典的对象实现类似相加的逻辑. 为了让代码更好看逻辑性更强, 偶玩了下python的自定义类方法.</p>
<p>首先更详细地介绍下背景: 我们需要在Django中记录一个模型变化的历史, 我们有需求会将这个模型的某个实例的现在的状态和它的若干个变化历史相加. 为此, 我们用Django的序列化方法把这个实例导出成一个json字符串, 然后用这个字符串实例化一个类似字典的类. 变化历史的各种相加相减操作就是通过自定义这个类似字典的类的特殊的类方法而实现的.</p>
<p>既然这个类是用一个json字符串来实例化的, 我们简单地将这个类的名字定为Json, 其初始化方法类似:</p>
<pre class="brush: python;">
class Json(object):
    &quot;&quot;&quot;
    This class is used to hold a model instance's information.
    &quot;&quot;&quot;
    def __init__(self, _json_string):
        &quot;&quot;&quot;
        This class is initialized with a json formatted string.

        The json formatted string is provided using django's serialization
        tool, which accept a QuerySet or a list as an object. So here we have
        to retrieve the first(and the only) object in the list.
        &quot;&quot;&quot;
        self.init_string = _json_string
        _dictionary = json.loads(_json_string)[0]
        self.model = _dictionary['model']
        self.object_id = self.pk = _dictionary['pk']
        self.fields = _dictionary['fields']
</pre>
<p>看起来很长一段代码, 实际上只是定义了若干基本类属性而已. 这些属性包括初始化字符串, 被序列化的实例的模型和主键, 以及一个存放原实例的属性的self.fields字典. 如果对这些名字不太熟悉可以自行去围观下django的序列化工具的json输出, <a href="http://docs.djangoproject.com/en/dev/topics/serialization/#topics-serialization">这个页面</a>上有例子.</p>
<p>Json这个类只是存放了类的序列化的内容, 为了记录历史, 我们需要将两个这样的类相减以得出变化. 在定义相减这个操作之前, 我们需要定义一个用来存放历史变化的类:</p>
<pre class="brush: python;">
class Jsondiff(object):
    &quot;&quot;&quot;
    This object is used to save substraction results from two Json objects.
    &quot;&quot;&quot;
    def __init__(self, _diffdict):
        &quot;&quot;&quot;
        Input format specification
        --------------------

        the input json string represents a dictionary of differences. Like:
            {
                'fieldname': (newvalue, oldvalue),
            }
        It also contains a special field named '_model', containing the name of
        the model and the id of the object.
        A realworld example is provided below:
            {
                u'_object': (u'gsdb.server', 1),
                u'tag': (u'12535', u'12537'),
            }
        &quot;&quot;&quot;
        self.fields = _diffdict
        self.model = self.fields['_object'][0]
        self.object_id = self.fields['_object'][1]
        del self.fields['_object']
</pre>
<p>同样的, 看上去很长的代码只是干了很有限的几件事情: 从类初始化参数中拿到实例的模型和主键, 作为类属性保存起来, 并从初始化参数中删除这个用于存放元信息的特殊键值, 这样我们再后面进行各种操作时就不需要再讨论这个特殊的键值了.</p>
<p>OK, 两个类已经定义完了, 我们开始定义一些特殊的类方法. 首先, 我们需要定义一个方法能够比较两个Json实例的区别以得到一个Jsondiff实例. 我们需要实现下面的代码:</p>
<pre class="brush: python;">
jsondiff0 = json1 - json2
</pre>
<p>为此, 我们需要自定义Json的__sub__方法:</p>
<pre class="brush: python;">
    def __sub__(self, _json):
        &quot;&quot;&quot;
        This method would substract two Json objects and give a Jsondiff
        object.

        The object represented by _json is the old object, 'new - old' would
        make more sense than 'old - new'.
        &quot;&quot;&quot;
        _diffdict = {
            '_object': (self.model, self.object_id),
        }
        for _field in self.fields:
            if self.fields[_field] != _json.fields[_field]:
                _diffdict[_field] = (self.fields[_field], _json.fields[_field])
        return Jsondiff(_diffdict)
</pre>
<p>这段代码很容易理解. 你不是要比较两个Json对象吗? 既然所有相关的值都在self.fields里面, 那我们就一个个地进行比较, 如果有不同的地方就按照一个预设的格式记录下来. 我们这儿将这些记录放到一个字典里面, 处理完每个键后就直接将这个字典作为初始化Jsondiff类的参数实例化一个Jsondiff对象, 然后就可以直接返回这个对象了. 到这儿, 我们已经实现了前面写的json对象相减的代码. 下一步, 既然已经写了减法, 不写加法就不合理了:</p>
<pre class="brush: python;">
# Since we can do:
jsondiff0 = json1 - json2
# we wanna have this, too:
json1 = json2 + jsondiff0
</pre>
<p>好吧, 我们现在来定义加法:</p>
<pre class="brush: python;">
    def __add__(self, _jsondiff):
        &quot;&quot;&quot;
        This method would add a Json to a Jsondiff object.
        &quot;&quot;&quot;
        _sum = copy.deepcopy(self)
        for _field in _jsondiff.fields:
            _new, _old = _jsondiff.fields[_field]
            if _sum.fields[_field] != _old:
                raise RuntimeError
            else:
                _sum.fields[_field] = _new
        return _sum
</pre>
<p>此处, 为了避免执行加法后改变已有实例的属性, 我们用deepcopy将原来的Json对象复制了一份. 然后从Jsondiff里面读键值, 对于每个键值看看diff里的内容和当前的实例的属性有没有冲突, 以确定我们要进行的操作是不是合法的. 如果一切都没问题, 返回一个Json对象.</p>
<p>为了让这一切更和谐, 我们还定义了一个Jsondiff的__add__方法, 这样我们哪天把前后顺序弄反了也不会有多大的事情:</p>
<pre class="brush: python;">
    def __add__(self, _json):
        &quot;&quot;&quot;
        This method would add a Jsondiff to a Json object.
        &quot;&quot;&quot;
        return _json + self
</pre>
<p>有了这段代码, 我们在进行Json对象和Jsondiff对象的加法时可以肆无忌惮地将Jsondiff放在前面或者后面了. 而如果没有这个对象, 我们只能老老实实地将Jsondiff放在后面.</p>
<p>后来在使用中, 我发现我经常会需要比较两个Json对象的创建时间来比较哪个更新一点. 为了简化这些操作, 我给Json对象添加了一个date属性, 记录原模型的修改时间. 这样我每次只需要比较Json对象的date属性即可. 既然已经有了这个属性, 我们可以更进一步的定义__cmp__方法来使Json对象的比较更美观更和谐:</p>
<pre class="brush: python;">
    def __cmp__(self, _json):
        &quot;&quot;&quot;
        This method would compare two Json object's date.
        &quot;&quot;&quot;
        return cmp(self.date, _json.date)
</pre>
<p>定义了这个方法后, 我们能做类型下面的操作:</p>
<pre class="brush: python;">
&gt;&gt;&gt; json1 &gt; json2
True
</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2010/06/19/python-class-methods-customize/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>张悟本</title>
		<link>http://blog.xiaket.org/2010/06/03/zhang-wuben/</link>
		<comments>http://blog.xiaket.org/2010/06/03/zhang-wuben/#comments</comments>
		<pubDate>Thu, 03 Jun 2010 02:22:46 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[其它]]></category>
		<category><![CDATA[电视]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=423</guid>
		<description><![CDATA[事件回放下: 



春节在家时, 家里的若干个阿姨和偶妈都追着让我给她们下载张悟本的视频. 我这个几年没看电视的人围观了下, 觉得这丫的基本就是在扯. 不过反正不是我要看, 也就顺手下载了一把.

<span class="readmore"><a href="http://blog.xiaket.org/2010/06/03/zhang-wuben/" title="张悟本">阅读全文——共1735字</a></span>]]></description>
			<content:encoded><![CDATA[<p>事件回放下: </p>
<ol>
<li>春节在家时, 家里的若干个阿姨和偶妈都追着让我给她们下载张悟本的视频. 我这个几年没看电视的人围观了下, 觉得这丫的基本就是在扯. 不过反正不是我要看, 也就顺手下载了一把.</li>
<li>前几天打电话回家, 说起绿豆涨价, 偶妈跟我说张悟本是骗子, 说他导致了绿豆涨价. 我囧&#8230;</li>
<li>今天网上看到<a href="http://blog.ifeng.com/article/5642086.html">一篇文章</a>说是电视台编导的错, 为了经济利益而纵容了这种骗子上台.</li>
</ol>
<p>关于这个话题, 随便扯几句吧.</p>
<p>中医我一向不信的, 这种没有理论的验方虽然可能是对99%的人有效, 但是你没法保证那1%的不幸不会发生在你的身上. 相比之下, 我更愿意相信我可以理解的西医. 所以, 当一个中医的专家站上台讲座的时候, 下面坐着的我给他的起评分就已经不及格了.</p>
<p>话说这个张某某扯的话比较多, 我现在也懒得去找他的视频来玩找错误的游戏了, 说两个我印象比较深的例子吧.</p>
<p>张某某说某金属(好像是锌吧?)是抗癌的. 国内一直不同意, 直到十几年后, 才有美国科学家发表学术论文说锌是抗癌的. 这句话我当时看了就觉得很不爽. 首先, 锌的存在方式就没说清楚, 具体是耦合在蛋白质里的锌还是呈离子状态的锌? 耦合在蛋白质里的锌是和哪种或者哪类蛋白质耦合的? 耦合后的蛋白质的构型是什么样子的? 这种蛋白质以什么机理阻止癌细胞的增生或者杀灭癌细胞? 如果是离子状态的锌, 这些锌是什么价态的? 以什么方式参与了哪些生化反应? 这些都没有回答. 接下来, 我本也不指望你这个骗子能够答出我之前说过的问题, 那么, 您能从中医的角度解释一下为什么这些锌能够抗癌吗? 哦, 您一笔带过了, 难怪难怪. 我们继续来研讨一下所谓的抗癌的问题. 癌症种类那么多, 这玩意能够包治百病? 不是吧? 那锌岂不是要变成贵金属? 而抗, 又是一个太过笼统的提法, 锌是能够阻止癌细胞增生呢, 还是能够防止它扩散啊? 是能够治愈病人呢还是能够延长其生存时间啊? 如果这些都能说是抗. 那么我们可以把话说得极端一点, 你每天多吃点肉都能够增强体制, 那是不是说脂肪和蛋白质也是抗癌的? 再往后, 您十几年前说锌能抗癌, 之后十几年间都没有科学家说锌能抗癌了. 这个我还真不信. 那么多为了毕业可以舍身取义的硕士生博士生, 为了科研基金可以寝食不休的讲师教授都在孜孜不倦的堆论文, 这么多年来都没有一个人能够说锌能够在某种环境下对某种癌细胞在实验用小鼠体内的增生产生抑制作用? 这个几率我相信比中五百万要小太多了. 你说最近才有美国科学家发表论文说锌能抗癌. 那是你见得太少了吧!</p>
<p>张某某说, 自然界有白天和黑夜的原因是, 白天是给你工作的, 晚上就该睡觉. 这句话让我想起了Sheldon的一句台词, &#8220;This party is a scathing indictment of the American education system.&#8221; 从张某某的这句话, 我可以批评他不懂简单的物理学和天文学, 对生物多样性和生物进化没有基本的认识, 甚至从文科的角度, 在逻辑学上这句话也是说不通的. 不过我不打算细说了. 如果我给了这么多线索还不知道我想说什么的话, 那么这篇文章也不是写给你看的.</p>
<p>关于张某某的就扯这么多, 在这个人身上浪费笔墨时间实在不值得. 接下来说绿豆涨价. 绿豆涨价只是一个典型的例子, 用来说明最近普遍出现的粮食作物价格上涨. 而要说如果有一个节目的播出能够导致物价的普遍上涨, 那么这个节目只能是新闻联播. 至于这些乱七八糟的养生杂谈连资格证都拿不到. 至于到底谁开始说张某某导致了绿豆价格上涨, 我不知道, 也不高兴去了解. 虽然对他来说是冤枉的, 但是偶从不可怜骗子.</p>
<p>接下来是媒体在这件事中所起的作用. 前面说的我刚看的那篇博客说这是电视台编导的错. 那我还真不开心了. 电视台编导有啥错? 就因为没对这个骗子加以审核? 搞笑! 电视台编导当然要拉这种人上电视了! 有亲和力, 有感染力, 说起话来头头是道. 吸引中老年妇女提高收视率最合适了. 这种人不上电视那是犯罪. 你说张某某是骗子, 不能这样上电视, 这样不好? 笑话, 每天我们遇到的骗子还少? 随随便便走在路上有, 不出门宅在家里还有人给你发中奖短信, 关了手机上网又会看到一堆, 看报纸? 别逗了, 报纸上的骗子更多了. 要彻底杜绝骗子, 要么你从此长闭双眼, 两耳不闻窗外事, 一心只在周公; 要么你学会了解事物的本质, 睁大眼睛. 而首先, 就是要受教育水平的提高. 至于这一点是谁的责任, 我也就不多说了.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2010/06/03/zhang-wuben/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>python实现本地邮件发送</title>
		<link>http://blog.xiaket.org/2010/05/20/python-send-local-mail/</link>
		<comments>http://blog.xiaket.org/2010/05/20/python-send-local-mail/#comments</comments>
		<pubDate>Thu, 20 May 2010 13:48:00 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Linux相关]]></category>
		<category><![CDATA[Python开发]]></category>
		<category><![CDATA[slackware]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=409</guid>
		<description><![CDATA[之前一直忍受Thunderbird的荼毒, 换上KMail后一身轻, 感觉很开心. 于是有了将本地报警和通知转成邮件的冲动. 作为第一步, 就是要用KMail收取本地的邮件. 今天就整了整这个.

要用KMail收取本地邮件, 你需要一个本地邮件服务器(更具体的说是MTA). 这个我用的是exim, 因为Slackware默认的sendmail比我的岁数都大, 而且实际使用中也发现有些小问题. exim的安装和配置比较容易, 我基本上就只是改了默认端口而已.

接下来是要写一个程序能够发送本地邮件. 这个功能是smtplib这个库实现的. 基本的框架是这样的(继续无耻抄袭python官方文档):

<span class="readmore"><a href="http://blog.xiaket.org/2010/05/20/python-send-local-mail/" title="python实现本地邮件发送">阅读全文——共1681字</a></span>]]></description>
			<content:encoded><![CDATA[<p>之前一直忍受Thunderbird的荼毒, 换上KMail后一身轻, 感觉很开心. 于是有了将本地报警和通知转成邮件的冲动. 作为第一步, 就是要用KMail收取本地的邮件. 今天就整了整这个.</p>
<p>要用KMail收取本地邮件, 你需要一个本地邮件服务器(更具体的说是MTA). 这个我用的是exim, 因为Slackware默认的sendmail比我的岁数都大, 而且实际使用中也发现有些小问题. exim的安装和配置比较容易, 我基本上就只是改了默认端口而已.</p>
<p>接下来是要写一个程序能够发送本地邮件. 这个功能是smtplib这个库实现的. 基本的框架是这样的(继续无耻抄袭python官方文档):</p>
<pre class="brush: python;">
import sys
import smtplib

msg = sys.argv[1]
fromaddr = &quot;&lt;xiaket@bolt&gt;&quot;
toaddrs = &quot;&lt;xiaket@bolt&gt;&quot;
server = smtplib.SMTP('localhost')
server.set_debuglevel(1)
server.sendmail(fromaddr, toaddrs, msg)
server.quit()
</pre>
<p>这段代码里的bolt是我的主机名.</p>
<p>一切正常的话, 执行这个脚本应该能够给自己的用户发一封邮件, 你可以从debug信息里面看到发送过程中你的python脚本和服务器的通讯. 邮件发送完成后, 你可以在本地邮箱(例如: /var/mail/xiaket)里找到它的内容. KMail的配置也很简单, 给出到本地邮箱的路径即可.</p>
<p>接下来要做的事情就是完善我们的python脚本了. 我们要实现的目标是, 既能够当作本地命令执行又能够作为python模块被导入. 于是大概的框架应该是这样的:</p>
<pre class="brush: python;">
import smtplib
import sys

def send(message):
    fromaddr = &quot;&lt;xiaket@bolt&gt;&quot;
    toaddrs = &quot;&lt;xiaket@bolt&gt;&quot;

    # I used a bizzare port to receive mail.
    server = smtplib.SMTP('localhost', '9025')
    server.sendmail(fromaddr, toaddrs, message)
    server.quit()

if __name__ == &quot;__main__&quot;:
    if len(sys.argv) != 2:
        print &quot;Usage: mailer message&quot;
        exit(1)
    send(sys.argv[1])
</pre>
<p>不过现在还有一个问题是这样发出来的邮件没有标题, 编码和From/To信息. 找了找文档, 发现应该用email这个库, 用类似字典键值的方式给MIMEText这个类的实例添加属性, 最后用一个as_string转成字符串传给sendmail即可.</p>
<pre class="brush: python;">
from email.mime.text import MIMEText

mail = MIMEText(content, 'plain', 'utf-8')
mail['Subject'] = subject
mail['From'] = &quot;%s &lt;xiaket@localhost&gt;&quot; % category
mail['To'] = 'me &lt;xiaket@localhost&gt;'
server.sendmail(fromaddr, toaddrs, mail.as_string())
</pre>
<p>这样, <a href="http://blog.xiaket.org/wp-content/uploads/2010/05/mailer.py" target="_blank">代码</a>基本就完成了, 把这段代码命名为mailer.py, 放到我自己定义的PYTHONPATH里, 然后做一个软链接放到PATH下, 测试完成以后就大功告成了~ 使用起来还是挺方便的, 在命令行下:</p>
<pre class="brush: bash;">
mailer 测试类别 测试主题 测试内容
</pre>
<p>在python里面调用函数:</p>
<pre class="brush: python;">
&gt;&gt;&gt; from mailer import send
&gt;&gt;&gt; send(category=&quot;测试类别&quot;, subject=&quot;测试主题&quot;, content=&quot;测试内容&quot;)
</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2010/05/20/python-send-local-mail/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>翻译: scp协议原理</title>
		<link>http://blog.xiaket.org/2010/05/18/how-scp-protocol-works/</link>
		<comments>http://blog.xiaket.org/2010/05/18/how-scp-protocol-works/#comments</comments>
		<pubDate>Tue, 18 May 2010 04:21:10 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Linux相关]]></category>
		<category><![CDATA[读书笔记]]></category>
		<category><![CDATA[scp]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=406</guid>
		<description><![CDATA[译自: http://blogs.sun.com/janp/entry/how_the_scp_protocol_works. 原作者为Jan Pechanec. 这篇文章主要讲solaris中的scp协议实现. 本人对原文做了适当的润色, 希望能更易理解而不易产生误会. 水平有限, 有错误请不吝指出.

rcp协议简史

rcp命令1982年第一次出现在4.2版的BSD里面(man页面链接). 近30年的岁月让这个命令有了一些变化, 所以最初的rcp和现代的rcp会有些不一致的地方. rcp的基本协议在ssh-1.2.x版本里有实现, 而ssh-1.2.x又是OpenSSH的基础. 由于Solaris用的SSH工具(SunSSH)是OpenSSH的分支, 因此前面所讲的rcp协议到现在都还是在起作用. 说了这么多, 我也许应该把这篇文章的标题改成&#60;rcp协议原理&#62;, 不过这样看起来就不够酷了, 你懂的.(译者注: 这样偶就看不到这篇文章了&#8230;)

<span class="readmore"><a href="http://blog.xiaket.org/2010/05/18/how-scp-protocol-works/" title="翻译: scp协议原理">阅读全文——共6587字</a></span>]]></description>
			<content:encoded><![CDATA[<p>译自: <a href="http://blogs.sun.com/janp/entry/how_the_scp_protocol_works" target="_blank">http://blogs.sun.com/janp/entry/how_the_scp_protocol_works</a>. 原作者为<a href="http://blogs.sun.com/janp/" target="_blank">Jan Pechanec</a>. 这篇文章主要讲solaris中的scp协议实现. 本人对原文做了适当的润色, 希望能更易理解而不易产生误会. 水平有限, 有错误请不吝指出.</p>
<h3>rcp协议简史</h3>
<p>rcp命令1982年第一次出现在4.2版的BSD里面(<a href="http://blogs.sun.com/janp/resource/scp/rcp.txt" target="_blank">man页面链接</a>). 近30年的岁月让这个命令有了一些变化, 所以最初的rcp和现代的rcp会有些不一致的地方. rcp的基本协议在ssh-1.2.x版本里有实现, 而ssh-1.2.x又是OpenSSH的基础. 由于Solaris用的SSH工具(SunSSH)是OpenSSH的分支, 因此前面所讲的rcp协议到现在都还是在起作用. 说了这么多, 我也许应该把这篇文章的标题改成&lt;rcp协议原理&gt;, 不过这样看起来就不够酷了, 你懂的.(译者注: 这样偶就看不到这篇文章了&#8230;)</p>
<h3>协议工作方式</h3>
<p>前面已经说过, rcp和scp在协议层面上没有区别, 不同在于传输时使用了rlogin. 从现在开始, 我就只讲scp了. 其使用摘要如下:</p>
<pre>scp [options] [user@]host1:]file1 []... [ [user@]host2:]file2</pre>
<p>除开远程服务器之间的文件复制这个特殊情况, scp会先解析命令行参数, 然后打开一个到远程服务器的连接. 再通过这个连接起另一个scp进程, 这个进程的运行方式可以是源模式(source)也可以是宿模式(sink). (译者注: 前者是数据提供者, 源头, 以源模式运行的scp进程后面会被称作是源端. 后者是数据的目的地, 归宿, 以宿模式运行的scp进程后面会被称作是宿端)前者读取文件并通过SSH连接发送到另一端, 后者通过SSH连接接收文件. 源模式和宿模式是通过-f (from)和 -t (to)这两个隐藏选项来启动的. 这两个参数仅供命令内部使用, 因此没写进文档. (译者注: 你执行scp -t不会给出非法参数的报错提示, 而scp -s就会, 因为没有-s这个选项.) 除了这两个隐藏参数外, 还有另一个隐藏参数-d, 表示复制的对象是一个目录而不是文件.</p>
<p>下图给出了一个简化后的scp源/宿模式工作示意图:</p>
<pre>

+-----------+   remote command: scp -t file2    +------+
| ssh hostB |----------------------------------&gt;| sshd |
+-----------+                                   +---+--+
     ^                                              |
     |                                              |
     |fork()                                  fork()|
     |                                              |
+----+-----------------+                +-----------V--+
| scp file hostB:file2 |                | scp -t file2 |
+----------------------+                +--------------+
</pre>
<h3>协议</h3>
<p>下面介绍传输协议是如何工作的. 你不如先暂时忘了ssh, sshd以及两台机器之间的连接这些东东. 如果我们只关注以源宿两种模式工作的scp命令的话, 上图可以简化成:</p>
<pre>
                      data transfer
+------------------+   ___________   +--------------+
| scp fileX hostY: | -&gt;___________-&gt; | scp -t fileX |
+--------.---------+                 +-------.------+
         |                                   |
         |read()                             |write()
   __....|....__                       __....|....__
  =__  fileX  __:'                    =__  fileX  __:'
     `''''''''                           `''''''''
</pre>
<p>需要注意的是, 永远不会有两个工作模式一样的scp协同工作. (译者注: 你可以想象下两个源端互相期待对方给自己传文件会是啥情况&#8230;) 远程服务器上的scp进程选定一种模式后, 本地的scp进程(就是本地用户命令行起的这个进程)会自动选定另一种模式, 因为这个本地进程会于用户交互.</p>
<h4>源端</h4>
<p>协议信息是由文本和二进制数据混合构成的. 例如, 当我们要传出一个普通文件时, 协议消息的类型, 文件的权限位, 长度及文件名都会以文本的方式发送, 接着在一个换行符后发送文件的内容. 我们在后面会更详细地讨论这一点. 协议消息内容可能类似:</p>
<pre>
C0644 299 group
</pre>
<p>二进制数据传输前需要传输的文本信息可能更多. 源端会一直等宿端的回应, 直到等到回应才会传输下一条协议文本. 在送出最后一条协议文本后, 源端会传出一个ASCII字符&#8217;\0&#8242;来表示真正文件传输的开始. 当文件接收完成后, 宿端会给源端发送一个&#8217;\0&#8242;.</p>
<h4>宿端</h4>
<p>来自源端的每条消息和每个传输完毕的文件都需要宿端的确认和响应. 宿端会返回三种确认消息: 0(正常), 1(警告)或2(严重错误, 将中断连接). 消息1和2可以跟一个字符串和一个换行符, 这个字符串将显示在scp的源端. 无论这个字符串是否为空, 换行符都是不可缺少的.</p>
<h3>协议消息类型列表</h3>
<pre>Cmmmm <length> <filename></filename></length></pre>
<p>表示传输单个文件, mmmm是文件的权限位. 实例: C0644 299 group</p>
<pre>Dmmmm <length> <dirname></dirname></length></pre>
<p>表示开始整个目录的递归复制. 此处文件长度将会忽略, 但是不可缺少. 实例: D0755 0 docs</p>
<pre>E</pre>
<p>表示目录的结束(D-E这一对可以嵌套使用, 这也是我们能正常递归复制目录树的原因.)</p>
<pre>T<mtime> 0 <atime> 0</atime></mtime></pre>
<p>当命令行给出-p选项时, 这一类协议消息用来传输所传递的文件的修改时间和访问时间(我猜你应该知道为啥我们不把文件创建时间传到宿端吧?). 时间记录了从UTC 1970.01.01 00:00:00到现在所经历的秒数. 这一类协议消息在最初的rcp实现中并未出现. 实例: T1183828267 0 1183828267 0</p>
<p>传完了这些消息后就开始传文件数据了. 宿端从数据流中读取之前协议消息中指定的文件长度. D和T需要在其他消息之前指定. 这是因为如果这两类消息放在其他消息之后, 这两类消息的内容具体是消息还是数据就不清楚了. 我们可以总结如下: </p>
<ul>
<li>传完了C类消息后开始传输文件数据.</li>
<li>在传完了D类消息后, 要么出现C类消息, 要么出现E类消息.</li>
</ul>
<h3>最大文件大小和文件完整性</h3>
<p>scp所能传输的最大文件大小是由scp协议, scp软件, 操作系统以及文件系统综合决定的. 由于OpenSSH用long long int来放文件大小, 因此理论上可以传输的最大文件大小是2^63 Byte. 给一个参考值, 2^40 Byte的大小是1T. 这意味着我们可以认为协议本身没有文件大小的限制.</p>
<p>scp本身不提供对文件完整性的保护, 这一特性是在ssh协议那一层完成的. 你可以参考<a href="http://blogs.sun.com/janp/entry/ssh_messages_code_bad_packet" target="_blank">我之前写的博客文章</a>, 也可以直接去围观<a href="http://www.ietf.org/rfc/rfc4253.txt" target="_blank">RFC43253</a>.</p>
<h3>例子</h3>
<p>讲协议是扯不清楚的, 直接看例子更直观更形象.</p>
<h4>1. 本地文件复制到另一位置</h4>
<pre>
$ rm -f /tmp/test
$ { echo C0644 6 test; printf "hello\n"; } | scp -t /tmp
test                 100% |***************************| 6       00:00
$ cat /tmp/test
hello
</pre>
<p>好玩吧? 我用了printf命令, 这样我们能够很清楚地看见为什么文件长度为6. 接下来我们试试复制一个目录.</p>
<h4>2. 本地目录复制到另一位置</h4>
<p>我们准备将一个名为testdir的目录, 内含一个名为test的文件, 递归地复制到/tmp下去.</p>
<pre>
$ rm -rf /tmp/testdir
$ { echo D0755 0 testdir; echo C0644 6 test;
    printf "hello\n"; echo E; } | scp -rt /tmp
test                 100% |****************************| 6       00:00
$ cat /tmp/testdir/test
hello
</pre>
<p>请注意, 我们在此处用了-r参数, 因为我们要复制整个目录.</p>
<h4>3. 将另一位置的目录复制到本地</h4>
<p>之前的例子中, 管道里的scp都是充当宿端. 这个例子里面, scp进程的角色是源端. 就像前面说的那样, 我们必须要对每个成功的消息和文件传输加以应答. 另外, 这个例子只是模拟应答的过程, 而没有真正去创建文件和文件夹. 因为要创建的东西都已经在你的终端里打印出来了.</p>
<pre>
$ cd /tmp
$ rm -rf testdir
$ mkdir testdir
$ echo hello &gt; testdir/test
$ printf '\000\000\000\000\000\000' | scp -qprf testdir
T1183832947 0 1183833773 0
D0700 0 testdir
T1183833773 0 1183833762 0
C0600 6 test
hello
E
</pre>
<p>解释下, 这次没有进度条了, 这是因为我们用了-q选项. 你可以看到传输了文件时间信息, 这是因为我们是用了-p选项. 另外, -f表示这一次scp进程是源端. 你可以发现我们丢了六个&#8217;\000&#8242;给scp, 这是我们模拟的传输过程中的应答. 第一个来开始传输过程, 四个响应消息, 一个响应文件传输结束. 对了吗? 不对, 我们还没响应最后的E呢. 此时看看退出状态:</p>
<pre>
$ echo $?
1
</pre>
<p>如果我们用七个&#8217;\000&#8242;, 就不会有问题了:</p>
<pre>
$ printf '\000\000\000\000\000\000\000' | scp -qprf testdir
T1183832947 0 1183833956 0
D0700 0 testdir
T1183833773 0 1183833956 0
C0600 6 test
hello
E
$ echo $?
0
</pre>
<h4>4. 发送错误消息</h4>
<p>下面这个例子中, 我们将会返回2给scp, 你可以看到即使我们在这个2后面又发送了几个&#8217;\000&#8242;, scp命令也不接受后面的这些确认信息了.</p>
<pre>
$ printf '\000\000\002\n\000\000' | scp -qprf testdir

T1183895689 0 1183899084 0
D0700 0 testdir
</pre>
<h3>远程服务器shell配置文件有输出</h3>
<p>有时候, 会有scp不能正常工作而ssh却一切正常的情况发生. 这通常是由于远程服务器的配置文件里有echo/printf而造成的. 下面展示几个例子:</p>
<h4>输入密码后, scp就卡住不动了</h4>
<p>要重现这一症状, 在远程服务器的配置文件里面添加一行:</p>
<pre>echo ""</pre>
<p>为什么会有这种情况发生呢? 这是因为在源端的scp进程会等待第一个协议消息的确认信息. 如果拿到的不是0, 它会认为这是远程服务器错误提示的一部分, 并会接着无限期地等待标志消息结束的换行符. 由于你在第一个换行符后没有打印新的内容, 你本地的scp就卡住不动了. 一直处于read状态. 另一方面, 远程服务器在处理完配置文件后, 以宿端scp进程也就开始了它也会卡在read状态, 等待一个0来表示文件传输的开始. 好吧, 现在两边的scp都卡住不动了. 总结下, 这种情况下, 问题的起因是远程服务器的shell配置文件的输出参与了scp协议的对话.</p>
<h4>如果我将文件复制到远程服务器, scp执行完我的shell配置就退出了</h4>
<p>这句话的意思是, scp只是将用户的shell配置文件打印出来的第一句话打印出来就退出了. 要重现这个问题, 可以执行下面的操作:</p>
<p>译者注: 这个实验会清空你的.bashrc&#8230; 慎用.</p>
<pre>
$ echo 'echo "hi there!"' &gt;&gt; .bashrc
</pre>
<p>然后执行scp命令:</p>
<pre>
$ scp /etc/passwd localhost:/tmp
hi there!
$ echo $?
1
</pre>
<p>这个问题和第一个问题很类似. 由于接收到的第一个字符不是0(这个例子中是&#8217;h'), 它会认为有问题, 一直读到下一个换行符. 将读到的东西打印出来, 然后退出.</p>
<p>这一类问题都比较容易解决, 用下面的命令, 当你真正是通过终端登录时才把你想要的东西打印出来即可.</p>
<pre>
tty -s &#038;&#038; echo "hi there!"
</pre>
<h4>屏幕提示&#8221;协议错误: 未预料的&lt;换行符&gt;&#8221;然后scp退出了</h4>
<p>同样和第一个问题类似, 但是你是从远程将文件复制到本地时会出现这种情况. 为什么呢? 你本地的scp是宿端, 等待源端传来的协议消息. 但是, 它拿到的是一个空行, 紧接着又拿到一个换行符. 这显然是违背协议的, 因此你本地的scp就退出了. 如果你在远程服务器配置文件里面还打印了额外的东西, 这些就会被当作是错误提示(除非这条消息是由一个有效的消息头标识符所引导的, 而如果这种纠结的事情真的发生了, 那么打印出来的错误提示会更难懂.) , 直到下一个换行符之前的输出都会被认为是错误提示的内容. 打印完这些内容后, scp就退出了. 例如我将下面这行加到我的shell配置里去:</p>
<pre>printf "XXXX"</pre>
<p>(printf不会自动打印换行符的, 记得吧?)第一条协议消息之前的所有输出都会被认为是错误提示:</p>
<pre>
$ scp localhost:/etc/passwd .
Password:
XXXXC0644 1135 passwd
$ echo $?
1
</pre>
<p>另外, 如果你恰好很纠结地在你的配置文件中指定了输出D之类的有效字符, 你拿到的消息就更纠结了.</p>
<pre>
$ scp localhost:/etc/passwd .
Password:
protocol error: bad mode
$ echo $?
1
</pre>
<p>知道教训了吧? 记得检查scp的退出状态!</p>
<h3>协议的扩展性</h3>
<p>rcp协议很简单, 我们现在想研究下它的可拓展性. 例如, 我们怎么样才能传输文件的ACL信息? 问题在于, 如何拓展这个协议, 让其具有向后的兼容性. 也许这个地方有些很简单的办法但是我没想到的, 不过我很怀疑这一点. 现在的问题是, 你不能拓展已有的消息. 比如, 看看我们往T消息结尾处添加一个字符串&#8221;123&#8243;会怎么样:</p>
<pre>
$ { echo T1183832947 0 1183833773 0 123;
    echo D0755 0 testdir; echo E; } | scp -rt /tmp
scp: protocol error: atime.usec not delimited
</pre>
<p>C类消息也是一样:</p>
<pre>
$ { echo D0755 0 testdir; echo C0644 6 test 123;
    printf "hello\n"; echo E; } | scp -rqt /tmp
$ ls -1 /tmp/testdir/
test 123
</pre>
<p>而且你又不能添加一类新消息, 因为scp命令不能识别:</p>
<pre>
$ { echo X 1 a; echo D0755 0 testdir; echo C0644 6 test;
    printf "hello\n"; echo E; } | scp -rt /tmp
scp: X 1 a
$ echo $?
1
</pre>
<p>可能的办法:(有其他的办法咩?) 一个比较明显能解决问题的办法是给scp命令添加一个选项, 指明可以是用一些拓展协议消息. 如果运行失败的话可能远程服务器上的scp版本和本地的scp版本不一样, 然后就可以退到普通模式下运行了. 不过我不确定是不是真要搞得这么纠结. 有些scp软件的开发者已经在用sftp协议传输文件了, 而这也是我们想要做的事情. 我想也许可能在非交互方式下执行exec sftp, 再转换下参数就可以了.</p>
<h3>远程服务器之间的复制</h3>
<p>一个通常会问到的问题是, 为啥远程服务器之间的复制不能有密码输入之类的认证方式. 这不是bug, 这是特性. 代码上虽然可以实现有密码输入的认证, 但是由于实现的机理是建立hostA和hostB之间的直接连接, 而有人不希望把他在hostB的密码暴露给hostA, 所以代码上没有实现这一点. 远程服务器之间的文件复制是由本地的scp命令建立一个到hostA的连接, 然后执行&#8221;scp fileX hostB:&#8230;&#8221;来实现的.</p>
<p>我们最近更新了scp的手册页, 添加了这样一段:</p>
<pre>
一般来说, 通过密码或键盘交互来用scp实现远程服务器之间的文件传输不能正常
工作. 而用公钥, 基于主机或者GSSAPI的认证则是可行的. 对于公钥认证, 要么
不能有非空的加密短语, 要么需要使用ssh认证转发功能. GSSAPI认证能够在
kerberos_v5 GSS_API机制下使用, 但是必须启用GSSAPIDelegateCredentials
选项.
</pre>
<h3>效率</h3>
<p>现在你对scp的工作机理应该比较清楚了, 也应该比较容易理解为什么在一个延时比较高的网络环境下复制大量小文件会比将文件夹打包后传输需要长得多的时间. 每条协议信息以及传输结束后的确认信息的开销很大. 所以下一次, 你应该用类似下面的命令来传输大量小文件:</p>
<pre>
tar cfv - testdir | ssh user@host 'cd /tmp; tar xfv -'
</pre>
<h3>结论</h3>
<p>就这样吧, 总结下: rcp/scp协议是一个很简单的文件传输协议, 第一次在4.2BSD里面出现. 这个协议的可扩展性不强, 以后scp实现中可能会用sftp协议取代它.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2010/05/18/how-scp-protocol-works/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>用python的单元测试工具写网易微博API的测试样例</title>
		<link>http://blog.xiaket.org/2010/05/16/python-unittest-on-t163-api/</link>
		<comments>http://blog.xiaket.org/2010/05/16/python-unittest-on-t163-api/#comments</comments>
		<pubDate>Sun, 16 May 2010 14:59:45 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Python开发]]></category>
		<category><![CDATA[decorator]]></category>
		<category><![CDATA[网易微博]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=403</guid>
		<description><![CDATA[虽然之前在Django项目中也不完整地用过单元测试工具, 但是今天是第一次用python的单元测试工具完整写出一个项目的测试样例, 于是小记录下~

需求: 测试网易微博API的python实现.

源代码可见: http://code.google.com/p/163microblog/source/browse/trunk/tests.py

<span class="readmore"><a href="http://blog.xiaket.org/2010/05/16/python-unittest-on-t163-api/" title="用python的单元测试工具写网易微博API的测试样例">阅读全文——共2639字</a></span>]]></description>
			<content:encoded><![CDATA[<p>虽然之前在Django项目中也不完整地用过单元测试工具, 但是今天是第一次用python的单元测试工具完整写出一个项目的测试样例, 于是小记录下~</p>
<p>需求: 测试网易微博API的python实现.</p>
<p>源代码可见: <a href="http://code.google.com/p/163microblog/source/browse/trunk/tests.py" target="_blank">http://code.google.com/p/163microblog/source/browse/trunk/tests.py</a></p>
<p>话说对于偶这种初级民工, 写代码喜欢不需要动脑子地先搭个架子出来然后往里面填空, 不过偶连这个架子也是从python文档里面抄出来的:</p>
<pre class="brush: python;">
import unittest

from session import T163Session as Session

class SessionTests(unittest.TestCase):

    def testlogin(self):
        self.session = Session(username=&quot;xiaket@163.com&quot;, password=&quot;notmypass&quot;)
        self.assertEqual(self.session.screen_name, &quot;xiaket&quot;)

if __name__ == &quot;__main__&quot;:
    unittest.main()
</pre>
<p>这个东西貌似是能够跑, 但是有个比较不爽的地方是密码必须放到测试样例里面去, 这个让偶很不爽. 于是网上搜了搜, 看到一个继承unittest.TestCase这个基类的__init__方法的办法:</p>
<pre class="brush: python;">
import unittest

from session import T163Session as Session

class SessionTests(unittest.TestCase):

    def __init__(self, testname, username=None, password=None):
        super(SessionTests, self).__init__(testname)
        self.username = username
        self.password = password

    def testlogin(self):
        session = Session(username=self.username, password=self.password)
        self.assertEqual(session.screen_name, &quot;xiaket&quot;)
</pre>
<p>这样你在另外一个文件里面就能够对这个类进行测试了:</p>
<pre class="brush: python;">
import unittest

from t163.tests import SessionTests

if __name__ == &quot;__main__&quot;:
    username = &quot;xiaket@163.com&quot;
    password = &quot;notmypass&quot;
    suite = unittest.TestSuite()
    suite.addTest(SessionTests(&quot;testlogin&quot;, username, password))
    unittest.TextTestRunner().run(suite)
</pre>
<p>架子搭好了, 剩下的就是苦力活, 往里面填测试样例了. 这个过程比较纠结比较繁复, 而且伴随着测试样例的逐渐完成, 我还fix了一群以前代码中的bug. 另外, 由于现在网易微博还比较不成熟, 我还不得不为一群server的错误擦屁股, 写了几个装饰器来修正API的行为. 例如, 有个微博的API能够给出follow某用户的用户的信息.当给出的用户的screen_name参数不正确时, 我们理应期待服务器返回错误代码. 但是服务器比较出人意料地返回了follow当前登录用户的用户的信息. 这导致所有查询都不是完全可靠的, 除非你在每次查询前确定你查询所使用的screen_name是有效的. 于是, 我写了这样一个装饰器加到已有函数的前面:</p>
<pre class="brush: python;">
def check_screen_name(func):
    &quot;&quot;&quot;
    This decorator would check the screen_name in the parameter of the original
    function.

    It is to be noted that the screen must be the first argument if we are
    using a positional parameter.
    &quot;&quot;&quot;
    def morewrapped(func):
        def wrapped(kls, *args, **kwargs):
            if 'screen_name' in kwargs:
                _screen_name = kwargs['screen_name']
            elif len(args):
                _screen_name = args[0]
            else:
                _screen_name = None
            if _screen_name:
                # If the screen_name is set, we shall check if it is a valid
                # screen_name. We do this by visiting the homepage of this
                # screen_name:
                _url = &quot;/%s&quot; % _screen_name
                _message = &quot;Specified user does not exist.&quot;
                _err_dict = {
                    404: (UserNotFound, _message),
                }
                kls.request(_url, errors=_err_dict)
            return func(kls, *args, **kwargs)
        return wrapped
    return morewrapped(func)
</pre>
<p>我们通过能否访问这个用户的主页来判断这个用户是否存在. 如果这个主页不能够被正常访问, 我们就抛出一个404, 这样就能相信followers这个API的结果了. 另外, 使用装饰器的好处在于, 哪天服务器端的程序更新了, 我们的代码不用做太大的修改就可以继续使用.</p>
<p>好吧, 话题扯回来, 总结下写了这么长时间的测试样例, 有啥经验:</p>
<ul>
<li>将测试代码分类, 这样什么时候改动了一个地方, 不需要测试所有的样例.</li>
<li>这个项目主要测试的内容是异常的处理, 于是可以先写好所有的会抛异常的代码, 然后一边测试一边添加assert语句.</li>
<li>考虑问题要全面, 当然这个比较废话了, 每个人都希望如此.</li>
<li>如果测试需要网络交互, 或者速度较慢的话, 用&#8221;"&#8221;将测过的不影响逻辑的代码注释起来, 这样能有效地提高速度.</li>
<li>注意添加注释, 这样你能够更有效地重用你自己的代码.</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2010/05/16/python-unittest-on-t163-api/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>整了个自己喜欢的WordPress主题</title>
		<link>http://blog.xiaket.org/2010/05/14/my-wordpress-theme/</link>
		<comments>http://blog.xiaket.org/2010/05/14/my-wordpress-theme/#comments</comments>
		<pubDate>Fri, 14 May 2010 15:57:32 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Web开发]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[wp]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=401</guid>
		<description><![CDATA[之前那个主题名为monochrome, 当时觉得挺好看的, 这么长时间没有审美疲劳, 也不容易了&#8230; 那么, 无论如何, 你也已经看到了, 现在偶更新了页面的主题, 希望大家看起来更清爽一点. 夏天到了, 大家都要淡定, 主题也要淡定&#8230;

现在的主题基于wlsy的simpleG, 主题包里给的链接是

http://www.g9net.com/2009/02/05/wordpress-theme-simpleg.html

<span class="readmore"><a href="http://blog.xiaket.org/2010/05/14/my-wordpress-theme/" title="整了个自己喜欢的WordPress主题">阅读全文——共2476字</a></span>]]></description>
			<content:encoded><![CDATA[<p>之前那个主题名为monochrome, 当时觉得挺好看的, 这么长时间没有审美疲劳, 也不容易了&#8230; 那么, 无论如何, 你也已经看到了, 现在偶更新了页面的主题, 希望大家看起来更清爽一点. 夏天到了, 大家都要淡定, 主题也要淡定&#8230;</p>
<p>现在的主题基于<a href="http://wlsy.me/" target="_blank">wlsy</a>的simpleG, 主题包里给的链接是
<pre>http://www.g9net.com/2009/02/05/wordpress-theme-simpleg.html</pre>
<p>, 不过貌似已经失效了&#8230; 偶把这个主题改了改, 以更符合偶被广泛批评的审美观.</p>
<p>说明下, 当你看到这篇文章的时候, 偶的施工已经基本完毕了. 所以很多东西你是没法具象地理解的, 所以这篇文章对你来说没有太大作用, 是的, 现在你就可以停下来了. 偶的文笔很一般, 技术就更烂了. 下面这些乱七八糟的你本来就不感兴趣, 指不定里面还有很幼稚的错误. 读下去对你有百害而无一利. 你还想继续围观? 好吧, 随你, 偶已经提醒过你了&#8230;</p>
<h3>TODO</h3>
<p>开工, 首先明确需求, 有哪些不太爽的地方:</p>
<ul>
<li>首页标题的字体确定是什么, 比较奇怪的显示.</li>
<li>超链接的颜色和语义强调的颜色混淆, 要修改.</li>
<li>导航条的悬停颜色需要修改, 偏深了.</li>
<li>将页面放到导航条上面去.</li>
<li>博文标题的字体需要调大些或者加粗.</li>
<li>pre的背景需要再浅一点.</li>
<li>提交评论的按钮是英文的.</li>
<li>右边边栏适当增加文字间距.</li>
<li>超链接的样式不统一.</li>
<li>页脚太高了, 弄简单点.</li>
<li>调查下页面宽度, 方便的话弄宽点.</li>
<li>围观下其他页面, 看有没有i18n问题</li>
<li>发文日期希望能具体到分钟.</li>
</ul>
<h3>步骤</h3>
<p>工欲善其事, 必先利其器. 首先要做的事情是把以前懒得没去装的web开发插件装上, Colorzilla, Firebug, Measureit, Web Developer. 还算比较大路货吧.</p>
<p>接下来的没有绝对的步骤之分吧, 偶是想到哪个就做哪个&#8230;</p>
<p>首先看到字体, 原主题作者给出的字体替换顺序是:</p>
<pre>"Lucida Grande",Verdana,helvetica,Arial,Tahoma,Sans-Serif;</pre>
<p>为了让偶自己觉得好看点, 我在最前面加上了苹果的圆体:</p>
<pre>"Vera Sans Yuanti", "Lucida Grande", Verdana, helvetica, Arial, Tahoma, sans-serif</pre>
<p>据说有句俗话是一人吃饱全家不饿, 本着类似的精神, 剩下的字体替换顺序偶就懒得变了&#8230;</p>
<p>接下来处理的是pre, 这个tag相对来说比较独立, 比较容易处理, 找到这一行:</p>
<pre class="brush: css;">
pre {background:#ccc;border:1px solid #DDD;overflow:auto;padding:10px;}
</pre>
<p>这一段我有几点不太喜欢: 首先是背景太深, 其次是上下的padding有点太大了, 另外, 这段里面border加了之后基本没感觉, 于是拿过来改了改:</p>
<pre class="brush: css;">
pre {background:#ddd; border:1px dashed #aaa; overflow:auto;padding:5px 20px;}
</pre>
<p>现在的border比较明显, 但是也没到影响读者注意力的程度. 至于留空, 上下的留空我改小了, 左右的留空我改大了.</p>
<p>写到这儿, 偶发现页面宽度要早点改为好, 后面发现问题也容易补救. 拿Firebug看了看, 貌似正文宽度是在content这个div里被指定的. 跑过去一看, 发现已经给到960像素了, 哎哎, 再加大那么一点点也没啥实际意义了, 宽度这一点就算了吧.</p>
<p>接下来偶处理的是导航条, 原主题作者估计是和他的博客有相应的整合, 所以有这种导航条的配置. 导航条的内容在menu.php里面定义, 偶把这个文件改成了:</p>
<pre class="brush: php;">
&lt;li&gt; &lt;a href=&quot;/&quot;&gt;首页&lt;/a&gt;&lt;/li&gt;
&lt;li&gt; &lt;a href=&quot;/about/&quot;&gt;夏恺&lt;/a&gt;&lt;/li&gt;
&lt;li&gt; &lt;a href=&quot;/books/&quot;&gt;书单&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;rss&quot;&gt;&lt;a href=&quot;/feed/rss/&quot;&gt;Feed&lt;/a&gt;&lt;/li&gt;
</pre>
<p>对, 原来的动态内容全部静态化了, 这样的问题是如果我需要在首页上添加一个主页面时, 我会需要编辑menu.php这个文件, 不过我想这个我还能接受. 另外, 这样的好处是理论上能够更快那么一点点&#8230; 现在的问题是, 这儿的空间浪费比较严重, 不符合我的习惯. 于是我想把这个导航条放到上面去. menu.php这个文件是在header.php这个文件里被导入的. 打开这个文件后我又发现可以静态化的东西了, 顺手修了一把, 然后发现工作量就开始变大一点点了. 偶要把logo, 描述以及导航条放到适合的地方, 不过还好这边的定位还不算太困难. 在把网页拿到本地编辑了十分钟后, head的问题就搞定了~</p>
<p>博文标题字体大小也是比较容易调的, 找到那一行, 原始大小是18像素, 调成20就可以了.</p>
<p>页脚要弄简单点? 还有什么能比没有更简单呢? 反正本来也不太需要页脚来着&#8230;</p>
<p>下一个问题是将发文日期精确到分钟, 这个日期是由the_date函数给出的, 稍微搜索了wp的源代码, 发现这个函数是能够接受一个时间格式做参数的, 于是也是速度搞定~ 改完后, 这儿是酱紫的:</p>
<pre class="brush: php;">
&lt;?php the_date(&quot;Y-m-d H:i&quot;); ?&gt;
</pre>
<p>右边边栏的文字间距, 实际上也就是最近评论这一项的间距有点不正常, 这个也是比较容易搞定的, 找到对应的内容修改就可以了. 我在widget_recent_comments这个class里面增加一点点上下的margin就搞定了.</p>
<p>写到这儿, 基本上只剩下超链接风格统一和i18n的问题了, 后面那个虽然比较碎, 但是不算纠结, 于是先搞定i18n吧. 在几个页面上找了找, 改了一群php文件的内容就搞定了~ 剩下一个日历的月份貌似还没改, 也懒得看了&#8230;</p>
<p>配色&#8230;  现在的黄瓜色我还是蛮喜欢的, 不过稍稍有点深了, 于是改亮了一点~ 原来是8aab46, 现在统统改成了7d7. 改完之后发现和header的颜色有点小冲突, 于是把header的背景色改掉.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2010/05/14/my-wordpress-theme/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
