<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>Andor Chen</title>
 <link href="http://about.ac/atom.xml" rel="self"/>
 <link href="http://about.ac"/>
 <updated>2015-02-12T15:02:53+08:00</updated>
 <id>http://about.ac</id>
 <author>
   <name>Andor</name>
   <email>Andor.Chen.27@gmail.com</email>
 </author>

 
 <entry>
   <title>翻译流程</title>
   <link href="http://about.ac/2015/02/translation-process.html"/>
   <updated>2015-02-12T00:00:00+08:00</updated>
   <id>http://about.ac/2015/02/translation-process</id>
   <content type="html">&lt;p&gt;我翻译了&lt;a href=&quot;/books/&quot;&gt;一些书&lt;/a&gt;，积累了一些经验。写这篇文章，给对翻译感兴趣、想从事翻译的人看一下，希望对新人有所帮助。其实，我也还是个“新人”。&lt;/p&gt;

&lt;p&gt;我要说的是和出版社合作翻译，倘若你是和客户直接联系，自然不能用本文所述的“流程”。而且，这里所述的流程仅限部分出版社、部分图书类别（主要是计算机科学类）。&lt;/p&gt;

&lt;h3&gt;找书&lt;/h3&gt;

&lt;p&gt;国外的出版社很多，每年出版的书数量众多，国内会引进的却不多。国内从事计算机科学类图书引进的出版社（或旗下专门的公司）主要有：人邮信息技术分社，&lt;a href=&quot;http://www.ituring.com.cn/&quot;&gt;图灵&lt;/a&gt;（人民邮电出版社），&lt;a href=&quot;http://www.broadview.com.cn/&quot;&gt;博文视点&lt;/a&gt;（电子工业出版社），&lt;a href=&quot;http://www.hzbook.com/&quot;&gt;华章图文&lt;/a&gt;（机械工业出版社）。&lt;/p&gt;

&lt;p&gt;如果想寻找已经引进待译的书，可以访问各出版社或公司的网站，联系相应的编辑，看他们手头上有哪些书在“诚征译者”。&lt;/p&gt;

&lt;p&gt;在这方面，我觉得图灵做得不错，&lt;a href=&quot;http://www.ituring.com.cn/minibook/921&quot;&gt;所有待译的书都能在网站上找到&lt;/a&gt;。&lt;/p&gt;

&lt;h3&gt;调研&lt;/h3&gt;

&lt;p&gt;找到“想”翻译的书后，你要做些调研，问自己几个问题，最终决定要不要翻译：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;我对这本书所讲的内容（领域）是否熟悉（不一定要精通）？&lt;/li&gt;
  &lt;li&gt;这本书在原出版地的影响如何？亚马逊等网站上的评价如何？&lt;/li&gt;
  &lt;li&gt;我能为这本书投入多少时间？投入产出比是否令我满意？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;关于最后一个问题，我可以给你提供一个参考。如果你是业余翻译，一般一天能翻译 3-5 页；出版社能支付你的翻译酬劳，一般是 ￥30-80 每千字。&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;当然，这些只是参考，不排除有超级赛亚人和资金雄厚的出版社。&lt;/p&gt;

&lt;h3&gt;试译&lt;/h3&gt;

&lt;p&gt;确定要翻译的书之后，联系出版社，索要试译章节，开始试译。试译内容的长度一般在 3 千字左右。出版社通过试译确认译者的翻译能力以及对书中所讲的知识是否了解。&lt;/p&gt;

&lt;h3&gt;签合同&lt;/h3&gt;

&lt;p&gt;试译通过后，出版社会和你签订“翻译委托协议”，确认翻译的具体细节，例如酬劳，交稿期限和方式等。&lt;/p&gt;

&lt;h3&gt;翻译&lt;/h3&gt;

&lt;p&gt;接下来就是“漫长而痛苦”的翻译了。你要制定一个计划，比如每天翻译多少页，确保在约定的交稿期限之前能翻译完。一般情况下，如果没按期译完，出版社会扣除一定的稿酬（在“翻译委托协议”中应有说明）。&lt;/p&gt;

&lt;h3&gt;交稿，审核&lt;/h3&gt;

&lt;p&gt;如果你挺过来了，完成了整本书的翻译，这时候该把译稿交给出版社审查了。&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;出版社的编辑会仔细认真地审核你的译稿，如果发现问题会及时和你联系，共同解决。这个过程可能很长，有时可能长达半年。&lt;/p&gt;

&lt;h3&gt;等待&lt;/h3&gt;

&lt;p&gt;出版社确认你的译稿没问题之后，会走些“内部流程”，比如排版、专家审读等。这个时候你只能等待，如果想继续翻译其他书，现在就可以回到第一步了。&lt;/p&gt;

&lt;h3&gt;确认样书&lt;/h3&gt;

&lt;p&gt;出版社的那些流程走完之后，应该会寄送你几本样书。收到样书后，你要再次确认。如果发现问题，及时向出版社反馈。&lt;/p&gt;

&lt;h3&gt;出版&lt;/h3&gt;

&lt;p&gt;一切都顺利完成了，现在书就出版了。接下来是铺天盖地的宣传期。&lt;/p&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;税前，起征额 ￥800。字数统计方式：一般以 Word 统计的字数为准。 &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;有时出版社会要求译者分批提交译稿，即每隔一段时间就要提交一次。具体的提交方式以“翻译委托协议”为准。 &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>2014 年</title>
   <link href="http://about.ac/2014/12/2014.html"/>
   <updated>2014-12-31T00:00:00+08:00</updated>
   <id>http://about.ac/2014/12/2014</id>
   <content type="html">&lt;p&gt;每年着手写年终总结时，总会感叹时间过得好快。是啊，什么都没做，一年就过完了。小家伙也长大了。&lt;/p&gt;

&lt;p&gt;这一年应该是我大学毕业五年来最“潦倒”也最快活的日子，尤其是下半年，无拘无束，不用看任何人脸色，不用被使唤来使唤去。正因为这样，虽然“潦倒”，但我一直拒绝再次进入“牢笼”。&lt;/p&gt;

&lt;p&gt;4 月底，和领导做了次谈判，结果是双方对加班问题无法达成共识，我选择辞职。五一开始，正式成为“无业游民”。肆意妄为的玩了一个多月，巴西世界杯开始了。&lt;/p&gt;

&lt;p&gt;5 月底到 6 月初，翻译《&lt;a href=&quot;http://about.ac/books/flask-web-development.html&quot;&gt;Flask Web 开发&lt;/a&gt;》。6 月底到 7 月底，翻译《&lt;a href=&quot;http://about.ac/books/python-network-programming-cookbook.html&quot;&gt;Python 网络编程攻略&lt;/a&gt;》。7 月底到 10 月中，翻译另一本 Python 相关的书（尚未出版）。11 月初到 12 月初，翻译《&lt;a href=&quot;railstutorial-china.org&quot;&gt;Ruby on Rails 教程&lt;/a&gt;》（原书第 3 版）。12 月初，开始翻译一本 Java 相关的书，到今天已完成 40% 左右。&lt;/p&gt;

&lt;p&gt;7 月底，为了提升码字的效率和“逼格”，买了把机械键盘（&lt;a href=&quot;http://www.filcochina.com/Products.asp?Bigclassname=ProductsJ&amp;amp;productname=FFKB67MCEB&quot;&gt;Filco Minila 青轴&lt;/a&gt;）。11 月初，为了升级到 Mavericks，把笔记本的 2x2G 内存换成了 2x8G。&lt;/p&gt;

&lt;p&gt;7 月筹划翻译 Rails 教程第 3 版时，我决定弃用 Markdown，转用 &lt;a href=&quot;http://asciidoc.org/&quot;&gt;AsciiDoc&lt;/a&gt;。所以开发了制作电子书的工具 &lt;a href=&quot;https://github.com/AndorChen/persie&quot;&gt;persie&lt;/a&gt;，放弃 &lt;a href=&quot;https://github.com/AndorChen/burr&quot;&gt;burr&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;这一年基本上都窝在屋里翻译。当我拿到第一本书的翻译费用时，比刚毕业拿到第一个月的工资还兴奋。虽然钱不多，但毕竟是自己一个字一个字码出来的。拿到实体书时，我傻乐了起来。这是自己的劳动成果，看得见摸得着。&lt;/p&gt;

&lt;p&gt;至于来年的计划嘛，我从不做计划。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>升级到 Mavericks</title>
   <link href="http://about.ac/2014/11/mavericks.html"/>
   <updated>2014-11-15T00:00:00+08:00</updated>
   <id>http://about.ac/2014/11/mavericks</id>
   <content type="html">&lt;p&gt;最近把系统升级到了 Mavericks 10.9.5。观察了好久，最终评估我的电脑应该无法顺畅运行 Yosemite，所以决定安装前一代 OS X。&lt;/p&gt;

&lt;p&gt;安装的过程尚算顺利，用时一个半小时左右。这一次我选择的是直接升级，不是全新安装。升级后应用都兼容（我安装的应用实在少的可怜），不过也遇到一些问题。&lt;/p&gt;

&lt;h2&gt;内存&lt;/h2&gt;

&lt;p&gt;不升级 10.10，最大的担忧就是内存不够。看样子，10.9 也很吃内存，开机后就用掉了 3GB。所以终决定升级内存，把 2x2GB 换成 2x8GB。事后证明这个决策是明智的，因为开 XCode 后内存用量超过了 9GB。&lt;/p&gt;

&lt;p&gt;我用的是英睿达的内存条，按照英睿达的系统推荐，最终选定的型号是 CT102464BF160B。因为这个型号的频率是 1600MHz，而电脑原装的是 1333MHz，所以买之前有些犹豫。随后咨询了一些推友，大家的说法不一，不过我决定买回来试试。&lt;/p&gt;

&lt;p&gt;装上之后系统能正确识别，目前来看没有什么问题。&lt;/p&gt;

&lt;h2&gt;Git&lt;/h2&gt;

&lt;p&gt;不知什么原因，升级后识别不了 brew 安装的 git，直接使用系统自带的版本。所以，重新使用 brew 安装了 git。&lt;/p&gt;

&lt;p&gt;另外，bash prompt 中的 git 设置也失效了，所以&lt;a href=&quot;https://github.com/AndorChen/dotfiles/commit/179b51897ff8eaa454778e4191cd4821de17f9ca&quot;&gt;升级了 dotfiles&lt;/a&gt;。顺便也把 git 的推送规则设为了 &lt;code&gt;matching&lt;/code&gt;（&lt;a href=&quot;https://github.com/AndorChen/dotfiles/commit/d1af6aad079ccd953ef100eb4324cc7a0508d728&quot;&gt;ref&lt;/a&gt;）。&lt;/p&gt;

&lt;h2&gt;Ruby&lt;/h2&gt;

&lt;h3&gt;OpenSSL&lt;/h3&gt;

&lt;p&gt;在使用 Ruby 的过程中遇到“Library not loaded: /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib (LoadError)”错误，使用如下方法解决（&lt;a href=&quot;http://mithun.co/hacks/library-not-loaded-libcrypto-1-0-0-dylib-issue-in-mac/&quot;&gt;via&lt;/a&gt;）：&lt;/p&gt;

&lt;div class=&quot;codeblock sh&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;&lt;span class=&quot;c&quot;&gt;# install openssl via brew&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;brew install openssl

&lt;span class=&quot;c&quot;&gt;# copy files&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /usr/local/Cellar/openssl/1.0.1i/lib
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;../1.0.1i/lib] &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;sudo cp libssl.1.0.0.dylib libcrypto.1.0.0.dylib /usr/lib/

&lt;span class=&quot;c&quot;&gt;# re-create symbolic links&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;../1.0.1i/lib] &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;sudo rm libssl.dylib libcrypto.dylib
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;../1.0.1i/lib] &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;sudo ln -s libssl.1.0.0.dylib libssl.dylib
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;../1.0.1i/lib] &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;sudo ln -s libcrypto.1.0.0.dylib libcrypto.dylib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;readline&lt;/h3&gt;

&lt;p&gt;使用 Rails 的过程中遇到“Library not loaded: /usr/local/opt/readline/lib/libreadline.6.2.dylib”错误，使用如下方法解决（&lt;a href=&quot;http://qiita.com/hoisjp/items/61b5e016f51bc1ac8ed8&quot;&gt;via&lt;/a&gt;）：&lt;/p&gt;

&lt;div class=&quot;codeblock sh&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;&lt;span class=&quot;c&quot;&gt;# uninstall old readline&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;brew uninstall readline

&lt;span class=&quot;c&quot;&gt;# re-install readline&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;brew install readline

&lt;span class=&quot;c&quot;&gt;# link dylibs&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;brew link --force readline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>安装 DNSMasq</title>
   <link href="http://about.ac/2014/07/dnsmasq.html"/>
   <updated>2014-07-29T00:00:00+08:00</updated>
   <id>http://about.ac/2014/07/dnsmasq</id>
   <content type="html">&lt;p&gt;连接 VPN 之后，经常打不开国内的某些网站。经高人指点，这是 DNS 的问题，推荐使用 &lt;a href=&quot;http://www.thekelleys.org.uk/dnsmasq/doc.html&quot;&gt;DNSMasq&lt;/a&gt;，在本机架设简易的 DNS 服务器，让 VPN 使用本地服务器解析域名。以下对安装的过程做一记录，以备不时之需。&lt;/p&gt;

&lt;h2&gt;安装 DNSMasq&lt;/h2&gt;

&lt;p&gt;我使用的系统是 Mac OS X，因此使用 homebrew 安装。安装方式如下：&lt;/p&gt;

&lt;div class=&quot;codeblock sh&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;brew up &amp;amp; brew install dnsmasq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;DNSMasq 比较小，下载过程很快。&lt;/p&gt;

&lt;p&gt;然后，根据 homebrew 的提示，执行几个命令：&lt;/p&gt;

&lt;div class=&quot;codeblock bash&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;cp /usr/local/opt/dnsmasq/dnsmasq.conf.example /usr/local/etc/dnsmasq.conf
sudo cp -fv /usr/local/opt/dnsmasq/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.plist /Library/LaunchDaemons
sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;这三个命令的作用分别是：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;把配置示例复制到指定位置；&lt;/li&gt;
  &lt;li&gt;把 DNSMasq 加入启动列表，开机自启动；&lt;/li&gt;
  &lt;li&gt;手动启动 DNSMasq。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;为了操作方便，我还定义了两个命令别名，用于手动启动和停止 DNSMasq：&lt;/p&gt;

&lt;div class=&quot;codeblock bash&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;start-dnsmasq&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sudo launchctl start homebrew.mxcl.dnsmasq&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;stop-dnsmasq&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sudo launchctl stop homebrew.mxcl.dnsmasq&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;配置 DNSMasq&lt;/h2&gt;

&lt;p&gt;DNSMasq 的配置文件是 &lt;code&gt;/usr/local/etc/dnsmasq.conf&lt;/code&gt;。在文本编辑器中打开，在文件末尾写入如下设置（&lt;a href=&quot;https://ruby-china.org/topics/20270&quot;&gt;via&lt;/a&gt;）：&lt;/p&gt;

&lt;div class=&quot;codeblock conf&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=&lt;span class=&quot;m&quot;&gt;8&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;8&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;8&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;8&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=&lt;span class=&quot;m&quot;&gt;8&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;8&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;4&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;4&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#Bilibili
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;bilibili&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#fm
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;jing&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;fm&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;xiami&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#Baidu
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;baidu&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;bdstatic&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#QQ
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;qq&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;qcloud&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#Taobao
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;taobao&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;tmall&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;alipay&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;alibaba&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;taobaocdn&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#Sina
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;sina&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;cn&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;weibo&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#163
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;m&quot;&gt;163&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;youdao&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;netease&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#Zhihu
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;zhihu&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;zhimg&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#Apple
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;apple&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;autonavi&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#Sohu
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;sohu&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;sogou&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#Ifeng
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;ifeng&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;m&quot;&gt;360&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;cn&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;


&lt;span class=&quot;c&quot;&gt;#Youku
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;youku&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;ykimg&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;tudou&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;soku&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#douban
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;douban&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#TV
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;pptv&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;ppstream&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;pps&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;tv&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;letv&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;m&quot;&gt;56&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;m&quot;&gt;58&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;iqiyi&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;cntv&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;cn&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#ShopMall
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;m&quot;&gt;51&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buy&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;yixun&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;yihaodian&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;dangdang&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;qunar&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;meituan&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;jd&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;m&quot;&gt;360&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buyimg&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#Xunlei
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;xunlei&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;kankan&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#Ruby China
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;ruby&lt;/span&gt;-&lt;span class=&quot;n&quot;&gt;china&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#CDN
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;tanx&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;aliyun&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;upaiyun&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#Map
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;amap&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;hvvc&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;us&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#cn
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;cn&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#guokr
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;=/&lt;span class=&quot;n&quot;&gt;guokr&lt;/span&gt;.&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;/&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;114&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;如果之前已经启动 DNSMasq，现在重启。&lt;/p&gt;

&lt;h2&gt;配置 VPN&lt;/h2&gt;

&lt;p&gt;打开 VPN 的高级设置，在 DNS 选项卡中填入 &lt;code&gt;127.0.0.1&lt;/code&gt;，让 VPN 使用本地 DNS 服务器解析域名。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>多看对 XHTML 实体的支持</title>
   <link href="http://about.ac/2014/06/duokan-xhtml-entities.html"/>
   <updated>2014-06-22T00:00:00+08:00</updated>
   <id>http://about.ac/2014/06/duokan-xhtml-entities</id>
   <content type="html">&lt;p&gt;多看是我在 iOS 设备商阅读 ePub 格式电子书的主力应用，因此在制作 ePub 格式电子书时我也会特别有针对性的测试在多看中的效果。在最近的一本书中，我发现有一个 XHTML 实体无法显示出来，因此做了一个测试，看一下多看到底不支持哪些实体。测试结果如下表所示。&lt;/p&gt;

&lt;p&gt;在这个表格中，只列出了多看不支持的实体，不包含浏览器不支持的实体。同时为了对比，列出了 iBooks 的支持情况。测试的实体来自 &lt;a href=&quot;http://www.w3.org/2000/07/8378/xhtml/entities/entities.xml&quot;&gt;http://www.w3.org/2000/07/8378/xhtml/entities/entities.xml&lt;/a&gt;。&lt;/p&gt;

&lt;table class=&quot;table table-bordered&quot;&gt;
    &lt;thead&gt;
        &lt;tr&gt;
            &lt;th&gt;实体&lt;/th&gt;
            &lt;th&gt;16 进制表示&lt;/th&gt;
            &lt;th&gt;多看&lt;/th&gt;
            &lt;th&gt;iBooks&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x3D2;&lt;/td&gt;
            &lt;td&gt;3D2&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2118;&lt;/td&gt;
            &lt;td&gt;2118&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2111;&lt;/td&gt;
            &lt;td&gt;2111&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x211C;&lt;/td&gt;
            &lt;td&gt;211C&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x21B5;&lt;/td&gt;
            &lt;td&gt;21B5&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x21D0;&lt;/td&gt;
            &lt;td&gt;21D0&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x21D1;&lt;/td&gt;
            &lt;td&gt;21D1&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x21D2;&lt;/td&gt;
            &lt;td&gt;21D2&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x21D3;&lt;/td&gt;
            &lt;td&gt;21D3&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x21D4;&lt;/td&gt;
            &lt;td&gt;21D4&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2200;&lt;/td&gt;
            &lt;td&gt;2200&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2203;&lt;/td&gt;
            &lt;td&gt;2203&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2205;&lt;/td&gt;
            &lt;td&gt;2205&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2207;&lt;/td&gt;
            &lt;td&gt;2207&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2209;&lt;/td&gt;
            &lt;td&gt;2209&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x220B;&lt;/td&gt;
            &lt;td&gt;220B&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2217;&lt;/td&gt;
            &lt;td&gt;2217&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x223C;&lt;/td&gt;
            &lt;td&gt;223C&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2282;&lt;/td&gt;
            &lt;td&gt;2282&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2283;&lt;/td&gt;
            &lt;td&gt;2283&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2284;&lt;/td&gt;
            &lt;td&gt;2284&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2286;&lt;/td&gt;
            &lt;td&gt;2286&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2287;&lt;/td&gt;
            &lt;td&gt;2287;&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2297;&lt;/td&gt;
            &lt;td&gt;2297&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x22C5;&lt;/td&gt;
            &lt;td&gt;22C5&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2308;&lt;/td&gt;
            &lt;td&gt;2308&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2309;&lt;/td&gt;
            &lt;td&gt;2309&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x230A;&lt;/td&gt;
            &lt;td&gt;230A&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x230B;&lt;/td&gt;
            &lt;td&gt;230B&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2329;&lt;/td&gt;
            &lt;td&gt;2329&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x232A;&lt;/td&gt;
            &lt;td&gt;232A&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2660;&lt;/td&gt;
            &lt;td&gt;2660&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2663;&lt;/td&gt;
            &lt;td&gt;2663&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2665;&lt;/td&gt;
            &lt;td&gt;2665&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;&amp;#x2666;&lt;/td&gt;
            &lt;td&gt;2666&lt;/td&gt;
            &lt;td&gt;N&lt;/td&gt;
            &lt;td&gt;Y&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;由上表可以看出：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;多看不支持的实体都属于“符号”（Symbol）类别；&lt;/li&gt;
  &lt;li&gt;多看不支持的实体，iBooks 都支持；&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;另外，在 ePub 中使用实体时要注意一点。ePub 规范基于 XHTML，而 XHTML 实体比 &lt;a href=&quot;http://dev.w3.org/html5/html-author/charref&quot;&gt;HTML 实体&lt;/a&gt;少得多。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>2013 年</title>
   <link href="http://about.ac/2013/12/2013.html"/>
   <updated>2013-12-31T00:00:00+08:00</updated>
   <id>http://about.ac/2013/12/2013</id>
   <content type="html">&lt;p&gt;转眼又到年底了，回过头去看，这一年真的没做什么，大部分时间都用在鼓噪乏味的工作上。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;2 月底，《Ruby on Rails Tutorial》中文版（&lt;a href=&quot;http://railstutorial-china.org&quot;&gt;网站&lt;/a&gt;）初译完毕；&lt;/li&gt;
  &lt;li&gt;3 月初，到老婆的老家转了一圈；&lt;/li&gt;
  &lt;li&gt;5 月初，哥哥结婚，回家。小家伙太惹人爱了；&lt;/li&gt;
  &lt;li&gt;6.1 结婚两周年；&lt;/li&gt;
  &lt;li&gt;7 月，做了一个教学系列视频的中文字幕（&lt;a href=&quot;http://www.youku.com/playlist_show/id_19492591.html&quot;&gt;Youku&lt;/a&gt;）；&lt;/li&gt;
  &lt;li&gt;8 月底 - 9 月初，翻译《Everyday Rails Testing with RSpec》（&lt;a href=&quot;https://leanpub.com/everydayrailsrspec-cn&quot;&gt;购买地址&lt;/a&gt;）；&lt;/li&gt;
  &lt;li&gt;9 月底 - 11 月中，为一家国外科技公司翻译产品文档；&lt;/li&gt;
  &lt;li&gt;11 月 29 日：从上家公司离职；&lt;/li&gt;
  &lt;li&gt;12 月初，到惠州；月底，入职新公司；&lt;/li&gt;
  &lt;li&gt;12 月 30 日，开始翻译《Learning Laravel》（&lt;a href=&quot;https://leanpub.com/everydayrailsrspec-cn&quot;&gt;购买地址&lt;/a&gt;）；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这一年除了工作，之外的时间都用来翻译了。至今也有 7 个多月没见到小家伙了。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>新建 Rails 程序</title>
   <link href="http://about.ac/2013/05/rails-new.html"/>
   <updated>2013-05-12T00:00:00+08:00</updated>
   <id>http://about.ac/2013/05/rails-new</id>
   <content type="html">&lt;p&gt;本文针对 Rails 4。&lt;/p&gt;

&lt;p&gt;新建 Rails 程序的第一步是，在命令行中执行 &lt;code&gt;rails new&lt;/code&gt; 命令。执行这个命令后到底发生了什么？&lt;/p&gt;

&lt;p&gt;当我们在命令行中输入 &lt;code&gt;rails new [PATH]&lt;/code&gt; 后，命令行先要找到 &lt;code&gt;rails&lt;/code&gt; 这个可执行文件，可以使用 &lt;code&gt;which&lt;/code&gt; 命令查看，具体的路径视安装方式而不同。我使用 &lt;code&gt;rbenv&lt;/code&gt; 管理 Ruby，所以在我的电脑中，&lt;code&gt;rails&lt;/code&gt; 可执行文件的位置在 &lt;code&gt;~/username/.rbenv/shims/rails&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rails&lt;/code&gt; 这个可执行文件就是 &lt;code&gt;railties/bin/rails&lt;/code&gt;，内容很简单：&lt;/p&gt;

&lt;div class=&quot;codeblock ruby&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;&lt;span class=&quot;c1&quot;&gt;#!/usr/bin/env ruby&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;git_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;expand_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;../../..&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;__FILE__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;.git&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exists?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;git_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;railties_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;expand_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;../../lib&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;__FILE__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;vg&quot;&gt;$:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;unshift&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;railties_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;rails/cli&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;第一行是 shebang 声明，其他几行暂且不提，最后一行才是关键，把命令的实现交由 &lt;code&gt;railties/lib/rails/cli.rb&lt;/code&gt;（&lt;a href=&quot;https://github.com/rails/rails/blob/4-0-0/railties/lib/rails/cli.rb&quot;&gt;source&lt;/a&gt;）。&lt;code&gt;railties/lib/rails/cli.rb&lt;/code&gt; 第 6 行，调用 &lt;code&gt;Rails::AppRailsLoader.exec_app_rails&lt;/code&gt; 方法，在 &lt;code&gt;railties/lib/rails/app_rails_loader.rb&lt;/code&gt; 中定义（&lt;a href=&quot;https://github.com/rails/rails/blob/4-0-0/railties/lib/rails/app_rails_loader.rb&quot;&gt;source&lt;/a&gt;）。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;exec_app_rails&lt;/code&gt; 这个类方法执行一个 &lt;code&gt;loop&lt;/code&gt; 无限循环，直到在当前工作目录或上层目录中找到 &lt;code&gt;bin/rails&lt;/code&gt; 这个可执行文件为止。这个方法的作用是判断 pwd 或上层目录是否为 Rails 程序目录，如果是一个现有的 Rails 程序，则终止执行 &lt;code&gt;railties/lib/rails/cli.rb&lt;/code&gt; 的后续内容。&lt;/p&gt;

&lt;p&gt;现在，假设我们的 pwd 不是既有 Rails 程序目录，执行 &lt;code&gt;rails new [PATH]&lt;/code&gt; 命令，则会跳到 &lt;code&gt;railties/lib/rails/cli.rb&lt;/code&gt; 的第 8 行，，判断 Ruby 的版本，如果小于 1.9.3，便终止命令。Rails 4 推荐使用 Ruby 2.0。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;railties/lib/rails/cli.rb&lt;/code&gt; 第 11-16 行，是 &lt;code&gt;if-else&lt;/code&gt; 分支语句，跳过 &lt;code&gt;if&lt;/code&gt; 分支，直接看 &lt;code&gt;else&lt;/code&gt; 分支，加载 &lt;code&gt;railties/lib/rails/commands/application.rb&lt;/code&gt;（&lt;a href=&quot;https://github.com/rails/rails/blob/4-0-0/railties/lib/rails/commands/application.rb&quot;&gt;souece&lt;/a&gt;）。这个文件第 3-6 行不会在执行 &lt;code&gt;rails new&lt;/code&gt; 命令是运行，暂且不提。接下来又是一个 &lt;code&gt;if-else&lt;/code&gt; 分支，直接看 &lt;code&gt;else&lt;/code&gt; 分支。&lt;/p&gt;

&lt;p&gt;如果在执行 &lt;code&gt;rails new&lt;/code&gt; 命令时没有指定 &lt;code&gt;--no-rc&lt;/code&gt; 选项的话，会执行第 12-25 行，读取 &lt;code&gt;~/.railsrc&lt;/code&gt; 文件。&lt;/p&gt;

&lt;p&gt;然后，加载所需的生成器文件：&lt;/p&gt;

&lt;div class=&quot;codeblock ruby&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;rails/generators&#39;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;rails/generators/rails/app/app_generator&#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;第 43 行，才是关键，&lt;code&gt;Rails::Generators::AppGenerator.start&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;Rails 的生成器是通过 &lt;a href=&quot;https://github.com/wycats/thor&quot;&gt;Thor&lt;/a&gt; 实现的。&lt;code&gt;Rails::Generators::AppGenerator&lt;/code&gt; 的父类如下：&lt;code&gt;Rails::Generators::AppBase &amp;lt; Rails::Generators::Base &amp;lt; ::Thor::Group&lt;/code&gt;。&lt;code&gt;Thor::Group&lt;/code&gt; 的用法可以参考 &lt;a href=&quot;https://github.com/wycats/thor/wiki/Groups&quot;&gt;Thor 的 Wiki&lt;/a&gt;，简单来说，Group 中定义的实例方法，会按照顺序执行，特别适合用来生成文件。&lt;/p&gt;

&lt;p&gt;调用 &lt;code&gt;Rails::Generators::AppGenerator.start&lt;/code&gt; 方法后，会按照顺序调用如下方法： &lt;code&gt;create_root&lt;/code&gt;，&lt;code&gt;create_root_files&lt;/code&gt;，&lt;code&gt;create_app_files&lt;/code&gt;，&lt;code&gt;create_bin_files&lt;/code&gt;，&lt;code&gt;create_config_files&lt;/code&gt;，&lt;code&gt;create_boot_file&lt;/code&gt;，&lt;code&gt;create_active_record_files&lt;/code&gt;，&lt;code&gt;create_db_files&lt;/code&gt;，&lt;code&gt;create_lib_files&lt;/code&gt;，&lt;code&gt;create_log_files&lt;/code&gt;，&lt;code&gt;create_public_files&lt;/code&gt;，&lt;code&gt;create_test_files&lt;/code&gt;，&lt;code&gt;create_tmp_files&lt;/code&gt;，&lt;code&gt;create_vendor_files&lt;/code&gt;，&lt;code&gt;finish_template&lt;/code&gt;。文件的模板位于 &lt;code&gt;railties/lib/rails/generators/rails/app/templates/&lt;/code&gt; 目录下。&lt;/p&gt;

&lt;p&gt;然后，我们在命令行中就看到了一串说明：&lt;/p&gt;

&lt;div class=&quot;codeblock sh&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;rails new demo -B
      create
      create  README.rdoc
      create  Rakefile
      create  config.ru
      create  .gitignore
      create  Gemfile
      create  app
      create  app/assets/javascripts/application.js
      create  app/assets/stylesheets/application.css
      create  app/controllers/application_controller.rb
      create  app/helpers/application_helper.rb
      create  app/views/layouts/application.html.erb
      create  app/mailers/.keep
      create  app/models/.keep
      create  app/controllers/concerns/.keep
      create  app/models/concerns/.keep
      create  bin
      create  bin/bundle
      create  bin/rails
      create  bin/rake
      create  config
      create  config/routes.rb
      create  config/application.rb
      create  config/environment.rb
      create  config/environments
      create  config/environments/development.rb
      create  config/environments/production.rb
      create  config/environments/test.rb
      create  config/initializers
      create  config/initializers/backtrace_silencers.rb
      create  config/initializers/filter_parameter_logging.rb
      create  config/initializers/inflections.rb
      create  config/initializers/mime_types.rb
      create  config/initializers/secret_token.rb
      create  config/initializers/session_store.rb
      create  config/initializers/wrap_parameters.rb
      create  config/locales
      create  config/locales/en.yml
      create  config/boot.rb
      create  config/database.yml
      create  db
      create  db/seeds.rb
      create  lib
      create  lib/tasks
      create  lib/tasks/.keep
      create  lib/assets
      create  lib/assets/.keep
      create  log
      create  log/.keep
      create  public
      create  public/404.html
      create  public/422.html
      create  public/500.html
      create  public/favicon.ico
      create  public/robots.txt
      create  &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;/fixtures
      create  &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;/fixtures/.keep
      create  &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;/controllers
      create  &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;/controllers/.keep
      create  &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;/mailers
      create  &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;/mailers/.keep
      create  &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;/models
      create  &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;/models/.keep
      create  &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;/helpers
      create  &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;/helpers/.keep
      create  &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;/integration
      create  &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;/integration/.keep
      create  &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;/test_helper.rb
      create  tmp/cache
      create  tmp/cache/assets
      create  vendor/assets/javascripts
      create  vendor/assets/javascripts/.keep
      create  vendor/assets/stylesheets
      create  vendor/assets/stylesheets/.keep
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;最后，我们得到了一个 Rails 程序骨架。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>制作电子书的工具集</title>
   <link href="http://about.ac/2013/03/ebook-toolset.html"/>
   <updated>2013-03-14T00:00:00+08:00</updated>
   <id>http://about.ac/2013/03/ebook-toolset</id>
   <content type="html">&lt;p&gt;在制作&lt;a href=&quot;http://about.ac/rails-tutorial-2nd-cn&quot;&gt;《Ruby on Rails Tutorial》中文版&lt;/a&gt;电子书的过程中积累了一些经验，记录如下。&lt;/p&gt;

&lt;p&gt;这本书我用了 5 个多月的时间才翻译完，其中大概三分之一的时间都用在编写生成电子书所需的工具上了。期间上了两个月晚班，有很多空闲时间，所以我在网上到处搜索可用的工具，用来生成电子书。我的要求很简单：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;使用 Markdown 撰写文本；&lt;/li&gt;
  &lt;li&gt;能够生成 PDF，Epub 和 Mobi 格式的电子书；&lt;/li&gt;
  &lt;li&gt;可以自动生成目录；&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;就这三个简单的要求，我还是没能找到称心的工具。首先被我否定的是 &lt;a href=&quot;http://sphinx.pocoo.org/&quot;&gt;Sphinx&lt;/a&gt; 和 &lt;a href=&quot;http://johnmacfarlane.net/pandoc/&quot;&gt;Pandoc&lt;/a&gt;。这两个工具用的人比较多，功能也比较完善，但 Sphinx 只支持 reStructuredText 格式，而且是用 Python 开发的，我不太熟悉这种编程语言，而且我觉得这个工具太复杂。而 Pandoc 很强大，似乎用起来也不复杂，但是还是被否了，原因很简单，Haskell？这是哪个星球的语言？&lt;/p&gt;

&lt;p&gt;否了这两个之后，我又接着寻找，这一次集中经历寻找使用 Ruby 开发的工具，结果找到两个，似乎用的人比较多：&lt;a href=&quot;https://github.com/fnando/kitabu&quot;&gt;kitabu&lt;/a&gt; 和 &lt;a href=&quot;https://github.com/blueheadpublishing/bookshop&quot;&gt;bookshop&lt;/a&gt;。kitabu 还不错，基本的要求都能满足我的要求，但是看了代码后我才发现自动生成目录大代码对中文完全无用，而且这个 gem 功能又太简单了。bookshop 完全就不支持 Markdown。&lt;/p&gt;

&lt;p&gt;又是一阵搜索，最后找到了 &lt;a href=&quot;http://easybook-project.org/&quot;&gt;easybook&lt;/a&gt;，完全能满足我的要求，只有一点，是用 PHP 开发的。本来我想 PHP 就 PHP，反正代码也能看懂，想定制的话也可以，但是当我使用了十几分钟后就决定放弃了，PHP 毕竟还不能胜任 CLI。&lt;/p&gt;

&lt;p&gt;找了很多工具，相互比较，也觉得累了，干脆就直接把生成电子书的任务交给 &lt;a href=&quot;http://leanpub.com/&quot;&gt;Leanpub&lt;/a&gt; 吧。动了这个念头之后，我花了点时间熟悉了 Leanpub 的工作流程，也看了生成的文件，但觉得可定制的内容太少了，我是个外观控，如果不能自己定制生成的文件格式，我宁愿不用。所以最后我还是放弃使用 Leanpub。但是，Leanpub 对电子书做的一些处理被我借鉴了，具体内容参见下面的说明。&lt;/p&gt;

&lt;p&gt;我决定不在继续找了，所以就总结了一下这几个工具各自的优缺点。最终我决定以 easybook 为蓝本，参照其他几个工具可取的部分，自己编写一个程序，名字嘛就叫做 bookstrap。&lt;/p&gt;

&lt;p&gt;其实 bookstrap 只是个 wrapper，使用 Ruby 编写，是个命令行工具，先把 Markdown 转换成 HTML，然后生成 PDF、Epub 和 Mobi 格式电子书。详情如下。&lt;/p&gt;

&lt;p&gt;更新（2013-6-3）：bookstrap 更名为 burr，并开源：&lt;a href=&quot;https://github.com/AndorChen/burr&quot;&gt;https://github.com/AndorChen/burr&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;文本&lt;/h2&gt;

&lt;p&gt;文本肯定要用 Markdown，这是我最熟悉的文本书写语言。决定使用 Markdown 之后就要考虑使用那个工具将其生成 HTML 了，可选的工具太多了，我第一个想到的是 &lt;a href=&quot;https://github.com/vmg/redcarpet&quot;&gt;redcarpet&lt;/a&gt;，我之前使用 Jekyll 开发的网站用的就是这个 gem，而且它还对基本的 Markdown 语法做了一些扩展。不过用起来还是觉得句法太少了，而且这个 gem 是用 C 语言编写的，很难扩展，所以我放弃了。&lt;/p&gt;

&lt;p&gt;接下来又找到了 &lt;a href=&quot;http://fletcherpenney.net/multimarkdown/&quot;&gt;multimarkdown&lt;/a&gt;，这是 Markdown 基本语法的一个扩展集，增加了很多实用的句法。但是似乎还是无法满足我的要求，而且相应的转换程序 &lt;a href=&quot;https://github.com/fletcher/peg-multimarkdown&quot;&gt;peg-multimarkdown&lt;/a&gt; 也是用 C 语言开发的，不易扩展。&lt;/p&gt;

&lt;p&gt;最后，我想到了 Leanpub。Leanpub 使用的是 &lt;a href=&quot;https://github.com/gettalong/kramdown&quot;&gt;kramdown&lt;/a&gt;，所以我决定试一下。kramdown 是使用 Ruby 开发的，很容易扩展语法，试用了一段时间之后，我决定就使用它了。我还开发了 &lt;a href=&quot;https://leanpub.com/help/manual&quot;&gt;Leanpub 对 kramdown 语法的扩展&lt;/a&gt;，这样 Markdown 转换工具就有了。&lt;/p&gt;

&lt;h2&gt;生成 PDF&lt;/h2&gt;

&lt;p&gt;PDF 制作工具我第一个想到的就是 &lt;a href=&quot;http://www.latex-project.org/&quot;&gt;LATEX&lt;/a&gt;，但是它有自己的一套书写语言，而且学习曲线太高。LATEX 其实更适合学术文章，也适合组织或公司使用，个人折腾起来代价太大了。&lt;/p&gt;

&lt;p&gt;随后又看了几个生成 PDF 的工具，都不符合我的要求。最后还是选择了 kitabu 等使用的工具：&lt;a href=&quot;http://www.princexml.com/&quot;&gt;PrinceXML&lt;/a&gt;。PrinceXML 是闭源软件，Java 开发，但是可以免费试用，生成的 PDF 首页会加上一个 LOGO。PrinceXML 真正吸引我的是 HTML+CSS 的组合，学过前端开发的人看到这个组合太亲切了。&lt;/p&gt;

&lt;p&gt;决定使用 PrinceXML 后，我以 &lt;a href=&quot;http://alistapart.com/article/boom&quot;&gt;boom&lt;/a&gt; 的样式为基础，编写了所需的样式表，加之上一步生成的 HTML，就可以生成一个很漂亮的 PDF 了。&lt;/p&gt;

&lt;h3&gt;注意事项&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;在 PDF 中一定要嵌入中文字体；为了减少文件大小，最好嵌入 TrueType 格式的字体；&lt;/li&gt;
  &lt;li&gt;不要使用 Mac OS X 自带的预览程序优化 PDF 文件；&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;生成 ePub&lt;/h2&gt;

&lt;p&gt;ePub 格式的生成我没有选择 GUI 程序，或者其他命令行工具，而是选择了 &lt;a href=&quot;https://github.com/jugyo/eeepub&quot;&gt;Eeepub&lt;/a&gt; 这个 gem，因为使用 EeePub 我可以自由控制很多东西。&lt;/p&gt;

&lt;p&gt;在生成 ePub 格式时我产生了错觉，仿佛又进入 IE、Chrome、Firefox、Safari 这些浏览器兼容问题的深渊。市面上 ePub 阅读器太多了，而它们对规范的支持很有限，甚或各自有不同的实现方式及扩展，你就要考虑如何最大限度的兼容这些阅读器。其实这里我们可以采用的措施和前端开发一样：优雅降级。对主流阅读器（多看，iBooks）一定要保证所有功能都正确表现，而在其他阅读器中，只要内容能正常显示即可。&lt;/p&gt;

&lt;p&gt;生成 ePub 文件后，一定要使用 &lt;a href=&quot;http://code.google.com/p/epubcheck/&quot;&gt;EpubCheck&lt;/a&gt; 检查文件是否有错误。如果有错误一定要按照提示修正，否则可能有些阅读器无法正常显示，或者会导致下一步无法生成 Mobi 格式。&lt;/p&gt;

&lt;h2&gt;生成 Mobi&lt;/h2&gt;

&lt;p&gt;Mobi 格式其实和 ePub 差不多，只是 Kindle 还没提供对 ePub 格式的原生支持，所以必须要为 Kindle 用户考虑，生成 Mobi 格式。生成 Mobi 文件的工具是由 Amazon 开发的 &lt;a href=&quot;http://www.amazon.com/gp/feature.html?ie=UTF8&amp;amp;docId=1000765211&quot;&gt;kindlegen&lt;/a&gt;。这是一个命令行工具，可以直接使用上一步生成的 ePub 文件作为输入。在转换过程中，可能有很多错误提示，一定要及时修正这些错误，否则可能导致 Mobi 文件无法打开。&lt;/p&gt;

&lt;p&gt;生成 Mobi 文件后，还可以在 &lt;a href=&quot;http://www.amazon.com/gp/feature.html?ie=UTF8&amp;amp;docId=1000765261&quot;&gt;Kindle Previewer&lt;/a&gt; 中预览，看一下在各种 Kindle设备中的表现如何。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>2012 年</title>
   <link href="http://about.ac/2012/12/2012.html"/>
   <updated>2012-12-30T00:00:00+08:00</updated>
   <id>http://about.ac/2012/12/2012</id>
   <content type="html">&lt;p&gt;放假了，正好回顾一下即将过去的 2012 年。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;1 月 30 日，大年初八，儿子出生了；&lt;/li&gt;
  &lt;li&gt;冬末春初，基本上都在开发 &lt;a href=&quot;https://github.com/AndorChen/V2Press&quot;&gt;V2Press&lt;/a&gt;；&lt;/li&gt;
  &lt;li&gt;5.1 假期到惠州，和之前的同事玩了几天；&lt;/li&gt;
  &lt;li&gt;从惠州回来后和家人出了点矛盾，我“离家出走了”，一个月后厚着脸又回家了；&lt;/li&gt;
  &lt;li&gt;6.1 结婚一周年；&lt;/li&gt;
  &lt;li&gt;之后，找工作，在合肥待了一个月，面试了好多公司都没有结果，有点绝望了；&lt;/li&gt;
  &lt;li&gt;7月初，又到合肥面了一个公司，顺利拿到 offer；&lt;/li&gt;
  &lt;li&gt;然后，到公司上班，至今五个月了；&lt;/li&gt;
  &lt;li&gt;儿子渐渐长大了，第一次听到叫爸爸时，感觉挺奇怪的；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;一年快过完了，才想起来有很多之前雄心勃勃的计划根本就没完成，甚至都没开始。但愿 2013 年能做完手头上的几个 projects。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Ruby 的路径分隔符</title>
   <link href="http://about.ac/2012/06/separator.html"/>
   <updated>2012-06-22T00:00:00+08:00</updated>
   <id>http://about.ac/2012/06/separator</id>
   <content type="html">&lt;p&gt;当我看到 Ruby 的 File 有 &lt;code&gt;SEPARATOR&lt;/code&gt; 这个常量时，我的第一反应，这个常量是设置文件路径的分隔符的，而且这个分隔符在不同的系统实现上是不同的，比如 Unix 和 Windows 就应该是不同的。&lt;/p&gt;

&lt;p&gt;但事实并非如此。在《The Ruby Programming Language》一书 §9.6.1 第一段有这样一句话：“The constant &lt;code&gt;File::SEPARATOR&lt;/code&gt; should be ‘/’ in all implementations.”（&lt;a href=&quot;http://rubyforge.org/tracker/index.php?func=detail&amp;amp;aid=13026&amp;amp;group_id=426&amp;amp;atid=1698&quot;&gt;为什么要这样做&lt;/a&gt;）也就是说这个常量在所有的系统中的值是不变的，是 &lt;code&gt;/&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;为了提供对不同系统的支持，在生成路径时，通常会使用 &lt;code&gt;File.join&lt;/code&gt; 将路径的各部分连接起来。文档中说该方法会“Returns a new string formed by joining the strings using File::SEPARATOR”。但是通过实验就可以证明这句话是错误的（&lt;a href=&quot;http://objectmix.com/ruby/185491-wrong-file-separator.html#post655633&quot;&gt;via&lt;/a&gt;）：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;&amp;gt;&amp;gt; File::SEPARATOR = &quot;\\&quot;
(irb):3: warning: already initialized constant SEPARATOR
=&amp;gt; &quot;\\&quot;
&amp;gt;&amp;gt; File.join(&quot;c:&quot;, &quot;My Documents&quot;)
=&amp;gt; &quot;c:/My Documents&quot;
&amp;gt;&amp;gt; File::SEPARATOR
=&amp;gt; &quot;\\&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;也就是说不管 &lt;code&gt;SEPARATOR&lt;/code&gt; 的值是什么，&lt;code&gt;join()&lt;/code&gt; 是直接使用 &lt;code&gt;/&lt;/code&gt; 进行连接操作的。基于 &lt;code&gt;join()&lt;/code&gt; 的这种操作方式，很多人就倾向于 hard code 路径，其实还是使用 &lt;code&gt;join()&lt;/code&gt; 方法好一些，因为这个方法会做一些额外的工作，比如去除额外的 ‘/’：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;File.join(&#39;/foo/&#39;, &#39;bar&#39;) # =&amp;gt; /foo/bar 而非 /foo//bar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;现在的问题是：&lt;code&gt;SEPARATOR&lt;/code&gt; 有没有用？答案是，基本没什么用，而且现在看来也没有什么其他的特殊用途（&lt;a href=&quot;http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/58085&quot;&gt;via&lt;/a&gt;）。&lt;/p&gt;

&lt;p&gt;这个常量是没用的，那么还有一个问题需要解决：Windows 或其他平台能够识别 ‘/’ 作为分隔符的路径吗？答案是肯定的，Windows 本身可以识别 ‘/’ 作为路径分隔符，只是在某些特殊的环境中无法识别，比如 DOS 命令行（&lt;a href=&quot;http://objectmix.com/ruby/185491-wrong-file-separator.html#post655635&quot;&gt;via&lt;/a&gt;）。&lt;/p&gt;

&lt;p&gt;Ruby 还提供了另外一个常量 &lt;code&gt;ALT_SEPARATOR&lt;/code&gt;，这个常量更符合我们的期待，它在 Windows 中的值是 &lt;code&gt;\&lt;/code&gt;，在其他平台中则是 &lt;code&gt;nil&lt;/code&gt;。如果确实需要 Windows 风格的路径字符串，可以通过下面的代码实现：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;File.join(&#39;path&#39;, &#39;to&#39;, &#39;file&#39;).gsub(/\//, &#39;\\&#39;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;</content>
 </entry>
 
 <entry>
   <title>语义性版本号</title>
   <link href="http://about.ac/2012/06/semantic-versioning.html"/>
   <updated>2012-06-14T00:00:00+08:00</updated>
   <id>http://about.ac/2012/06/semantic-versioning</id>
   <content type="html">
&lt;p&gt;版本号对一个程序来说是很重要的，我以前是根据自己对其他程序的观察和自我理解为程序制定版本号的，因为似乎没有这方面的规范可以依据。如果程序只是自己或者小范围内使用这也没有大碍，不过如果你的程序被作为其他程序的依赖库，那么你就要重视这个问题了，含糊不清的版本号可能会给其他程序带来不必要的麻烦。&lt;/p&gt;

&lt;p&gt;很巧的是，昨天我发现了“&lt;a href=&quot;http://semver.org/&quot;&gt;语义性版本号&lt;/a&gt;”（Semantic Versioning）这个项目，这个项目旨在为你提供一个参考规范，可以更好的为你的程序制定版本号，而且可以让版本号更具语义性。使用 Jekyll 或者对 Github 有所了解的人对这个项目的发起人可能很熟悉，他就是 Github 的联合创始人之一 Tom Preston-Werner（Gravatar 居然也是他创建的，卖给 WordPress 应该赚了不少钱），所以这个参考规范的可行性还是很高的。&lt;/p&gt;

&lt;p&gt;下面是我对“语义性版本号”的 12 条参考规范的简单翻译，以后我的程序会严格按照这些规范来制定版本号。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;使用“语义性版本号”的软件&lt;strong&gt;必须&lt;/strong&gt;声明一个公共 API。这个 API 可以在代码中声明，也可以在文档中详细说明。不论采用哪种方式，都要保证准确性和完整性。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;常规的版本号&lt;strong&gt;必须&lt;/strong&gt;使用 X.Y.Z 的格式，其中 X、Y 和 Z 是非负整数。X 是主版本号，Y 是次版本号，Z 是补丁版本号。每部分&lt;strong&gt;必须&lt;/strong&gt;按照数字顺序递增 1。例如：1.9.0 -&amp;gt; 1.10.0 -&amp;gt; 1.11.0&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;在提升主版本号的时候，次版本号和补丁版本号&lt;strong&gt;必须&lt;/strong&gt;归零。在提升次版本号的时候，补丁版本号&lt;strong&gt;必须&lt;/strong&gt;归零。例如：1.1.3 -&amp;gt; 2.0.0 -&amp;gt; 2.2.0&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;一旦某个版本的程序已经打包发布，这个版本下的内容就&lt;strong&gt;一定不&lt;/strong&gt;可以改动。如需改动则必须发布新版本。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;主版本号为零（0.y.z）意味着处在初期开发阶段。所有内容随时都可能改变。公共 API 不应该被认为是稳定的。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;版本到达 1.0.0 说明公共 API 已经定义完成。在此之后的版本号提升取决于公共 API 的变化。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;只有在引入了向后兼容的错误修正时才&lt;strong&gt;必须&lt;/strong&gt;提升补丁版本号 Z（x.y.Z，其中 x &amp;gt; 0）。错误修正的定义是：为了修正不正确的表现而对内部代码做出的改动。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;在为公共 API 引入新的或向后兼容的功能时&lt;strong&gt;必须&lt;/strong&gt;提升次版本号 Y（x.Y.z，其中 x &amp;gt; 0）。在弃用任何公共 API 后&lt;strong&gt;必须&lt;/strong&gt;提升次版本号。如果为私有代码引入了大量的新功能或是改进，&lt;strong&gt;可以&lt;/strong&gt;提升次版本号。次版本号的提升可能会包含补丁等级的变化。次版本号提升后，补丁版本号&lt;strong&gt;必须&lt;/strong&gt;归零。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;如果为公共 API 引入了对向后不兼容的改变，&lt;strong&gt;必须&lt;/strong&gt;提升主版本号 X（X.y.z，其中 X &amp;gt; 0）。这可能会包含次版本和补丁等级的变化。主版本号提升后，次版本号和补丁版本号&lt;strong&gt;必须&lt;/strong&gt;归零。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;预览版可以通过在补丁版本号后直接添加一个连字符和点分标识符串进行注明。这个点分标识符串只能包含 ASCII 表中的数字、字母和连字符（[0-9A-Za-z-]）。预览版版本号是一种常用的形式，但是它比相应的常规版本号优先级要低。例如：1.0.0-alpha，1.0.0-alpha.1，1.0.0-0.3.7，1.0.0-x.7.z.92&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;构建版可以通过在补丁版本号后或预览版版本号后直接添加一个加号和点分标识符串进行注明。这个标识符串只能包含 ASCII 表中的数字、字母和连字符（[0-9A-Za-z-]）。构建版版本号是一种常用的形式，但是它比相应的常规版本号优先级要高。例如：1.0.0+build.1，1.3.7+build.11.e0f985a&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;优先级的计算&lt;strong&gt;必须&lt;/strong&gt;先将版本号拆分成主版本号、次版本号、补丁版本号、预览版版本号和构建版版本号，然后按照这个顺序进行比较。主版本号、次版本号和补丁版本号按照数字顺序比较。预览版和构建版的优先级&lt;strong&gt;必须&lt;/strong&gt;将各自的点分标识符串分开后按照以下方法比较：只包含数字的标识符按照数字顺序比较；包含字母或连字符的标识符按照 ASCII 表的顺序比较。数字标识符都比非数字标识符的优先级低。例如：1.0.0-alpha &amp;lt; 1.0.0-alpha.1 &amp;lt; 1.0.0-beta.2 &amp;lt; 1.0.0-beta.11 &amp;lt; 1.0.0-rc.1 &amp;lt; 1.0.0-rc.1+build.1 &amp;lt; 1.0.0 &amp;lt; 1.0.0+0.3.7 &amp;lt; 1.3.7+build &amp;lt; 1.3.7+build.2.b8f12d7 &amp;lt; 1.3.7+build.11.e0f985a&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>Ruby 对多语言的支持</title>
   <link href="http://about.ac/2012/06/understanding-m17n.html"/>
   <updated>2012-06-10T00:00:00+08:00</updated>
   <id>http://about.ac/2012/06/understanding-m17n</id>
   <content type="html">&lt;p&gt;这是一篇翻译文章，原文链接 &lt;a href=&quot;http://blog.grayproductions.net/articles/understanding_m17n&quot;&gt;http://blog.grayproductions.net/articles/understanding_m17n&lt;/a&gt;。原文是一个系列，翻译过来整合成了一篇文章，对文章内容做了适当的变化。因为原文是三年前写的，其中某些代码片段的执行结果和最新版本的 Ruby 相比可能会有所不同。&lt;/p&gt;

&lt;p&gt;Ruby 在进入 1.9 版本时发生了重大的变化，以前 Ruby 堪称是对字符编码支持最差的语言之一，而现在变成了支持最好的语言之一，可以处理不同的字符编码。我们都在成长。&lt;/p&gt;

&lt;p&gt;而这一变化带来的一个影响就是增加了学习的难度。之所以知道难度有所增加是因为我最近在尝试为标准库中的 CSV 库添加对所有的字符编码支持，我发现实现的过程就是一场战斗。这是一个全新的领域，没有太多的资料可以帮助理解 Ruby 的这个新特性。&lt;/p&gt;

&lt;p&gt;我希望改变这种状况。&lt;/p&gt;

&lt;p&gt;这篇文章的用意就是说明 Ruby 1.9 对字符编码的支持情况。我假设你对字符编码一无所知，从什么是字符编码、为什么会出现字符编码开始讲起。&lt;/p&gt;

&lt;p&gt;然后探索 Ruby 1.8 对字符编码的支持情况，其实没有很多值得探索的东西，但是我希望在对细节探索的过程中可以帮助你理解为什么 Ruby 1.9 做出了改变，以及这个改变是如何实现的。&lt;/p&gt;

&lt;p&gt;最后，我们会尽量深入而全面地探索 Ruby 1.9 中引入的字符编码的新特性。我会在一定的理论高度上说明，同时也会介绍我在处理字符编码方面的成功经验，兼具一般性和对 Ruby 的针对性。&lt;/p&gt;

&lt;p&gt;下面是本文的目录：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#um-h-1&quot;&gt;什么是字符编码&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#um-h-2&quot;&gt;Unicode 字符集和 Unicode 编码&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#um-h-3&quot;&gt;通用编码策略&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#um-h-4&quot;&gt;Ruby 1.8 中的字节和字符&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#um-h-5&quot;&gt;$KCODE 变量和 jcode 库&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#um-h-6&quot;&gt;使用 iconv 转换字符编码&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#um-h-7&quot;&gt;Ruby 1.8 中字符编码的缺点&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#um-h-8&quot;&gt;Ruby 1.9 中的字符串&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#um-h-9&quot;&gt;Ruby 1.9 中的三种默认编码类型&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#um-h-10&quot;&gt;多语言的其他细节&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#um-h-11&quot;&gt;Ruby 1.9 为我们提供了什么&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a id=&quot;um-h-1&quot;&gt;&lt;/a&gt;
## 1. 什么是字符编码？&lt;/p&gt;

&lt;p&gt;为了理解字符编码，首先需要讨论电脑是如何储存字符数据的。我们都知道，当我们在键盘上按下 a 键后，电脑会在某个地方记录一个 a 字符符号，这是个很神奇的过程。&lt;/p&gt;

&lt;p&gt;我认为大多数人都熟知电脑的核心基本上就是一堆数字 0 和 1。那么 a 也就应该是以一个数字的形式存储的。事实确实如此。我们可以在 Ruby 1.8 中查看是哪个数字：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -ve &#39;p ?a&#39;
ruby 1.8.6 (2008-08-11 patchlevel 287) [i686-darwin9.4.0]
97
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;?a&lt;/code&gt; 这个不常用的句法给出的是一个字符，而不是一个完整的字符串。在 Ruby 1.8 中，&lt;code&gt;?a&lt;/code&gt; 返回的是字符编码所对应的编码值。通过索引访问字符串中的一个字符也可以得到相同的结果：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -ve &#39;p &quot;a&quot;[0]&#39;
ruby 1.8.6 (2008-08-11 patchlevel 287) [i686-darwin9.4.0]
97
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;字符串的这种表现使 Ruby 核心开发团队很困扰，所以在 Ruby 1.9 中已经做了改变：返回字符串中的一个字符。如果想在 Ruby 1.9 中查看编码字符可以使用 &lt;code&gt;getbyte()&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby_dev -ve &#39;p &quot;a&quot;.getbyte(0)&#39;
ruby 1.9.0 (2008-10-10 revision 0) [i386-darwin9.5.0]
97
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;上面的代码虽然告诉了我们怎样得到这个神奇的数字，但是却没有告诉我们这个数字的意义是什么。当决定将字符以数字的形式存储后，人们制定了一个简单的图表，将一些数字映射到特定的字符上。这个映射图表就是著名的 US-ASCII 或者直接叫 ASCII。&lt;/p&gt;

&lt;p&gt;现今的 ASCII 表覆盖了你能在英式键盘上找到的所有字符：大、小写字母，数字和一些常见的符号；这个 128 个字符容量的 ASCII 映射表甚至还包括了一些控制字符序列。&lt;/p&gt;

&lt;p&gt;很和谐的生活，不是吗？但是⋯⋯&lt;/p&gt;

&lt;p&gt;这导致了两个不太和谐的事实：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;全世界的字符不可能仅仅包括这些字符&lt;/li&gt;
  &lt;li&gt;1 个字节只用了其中 7 位，还有 1 位没有使用（所以只产生了 128 个字符）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;太好了，还有 1 位空着，这 1 位可以产生另外的 128 个字符，我们也需要更多的字符。这真是个意外的发现啊！几乎每个人都对如何使用这额外的 128 个字符有着很好的想法，所以每个人都按照自己的想法在使用。至此，字符编码就诞生了。&lt;/p&gt;

&lt;p&gt;这额外的 128 个字符具体是哪些字符要根据使用者所实现的方式来决定，所以我们说这些字符数据使用了某种编码方式。如要正确的读出数据的内容就必须要知道字符所用的是哪种编码。&lt;/p&gt;

&lt;p&gt;举例说明一下，&lt;a href=&quot;http://en.wikipedia.org/wiki/Latin-1&quot;&gt;ISO-8859-1（也叫 Latin-1）&lt;/a&gt; 编码是一些操作系统、程序甚至是编程语言的默认编码方式，它主要使用欧洲语言中常用的重音符号来填充这额外的 128 个字符。&lt;/p&gt;

&lt;p&gt;如果问题只是这额外的 128 个字符的话，可能也不会如此麻烦。很不幸的是，还一个问题：256 个字符仍然无法满足某些语言。既然 1 字节只能产生 256 个字符，那么这些语言就需要编码多个字节，使用多个字节来实现单一的字符。&lt;/p&gt;

&lt;p&gt;多字节编码处理起来是很麻烦的，你需要很小心的处理数据的分隔，避免将一个字符的两个字节分开。&lt;/p&gt;

&lt;p&gt;日本语就是一个很好的例子。日本语的大多数符号就是文字本身，而不是用来合成文字的，所以常用的就有上千种符号。日本语常用的字符编码之一是 &lt;a href=&quot;http://en.wikipedia.org/wiki/Shift_JIS&quot;&gt;Shift JIS&lt;/a&gt;，需要两个字节来实现其中一些字符。&lt;/p&gt;

&lt;p&gt;这里只是举了一些很特殊的例子，实际上&lt;a href=&quot;http://en.wikipedia.org/wiki/Character_encoding#Popular_character_encodings&quot;&gt;现在有大量的编码在使用着&lt;/a&gt;。你不用在每一个程序中都支持所有的编码方式，实际上也有很好的理由不去这么做。很重要的一点就是要知道世间存在着不同的编码方式，不同的人群使用不同的方式存储数据。这是当今的程序员无法避免而需要面对的问题。&lt;/p&gt;

&lt;p&gt;如果你想一下的话应该可以想出一些没有正确使用编码的例子。你是不是怎经在你的邮件客户端或者 shell 中看到一些问号或者很奇怪的框框？通常这都意味着数据没有按照程序期望的方式进行编码，所以导致程序无法正确显示内容。这些就是我们极力避免发生的事情。&lt;/p&gt;

&lt;p&gt;本节要点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;不同的人群使用不同的方式存储数据&lt;/li&gt;
  &lt;li&gt;所有的字符数据都有特定的编码方式告诉你如何处理这些数据&lt;/li&gt;
  &lt;li&gt;为了正确的处理数据你必须知道数据所采用的编码方式&lt;/li&gt;
  &lt;li&gt;有些编码方式处理起来很麻烦，比如多字节编码&lt;/li&gt;
  &lt;li&gt;当程序无法得知字符编码方式时就会显示一大堆很混乱的输出（问号和框框）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a id=&quot;um-h-2&quot;&gt;&lt;/a&gt;
## 2. Unicode 字符集和 Unicode 编码&lt;/p&gt;

&lt;p&gt;字符编码方式越来越多，所以需要找到一种大家都可以用的方式。很难保证所有人都认可，不过 &lt;a href=&quot;http://unicode.org/&quot;&gt;Unicode&lt;/a&gt; 算是最接近完美的解决方案了。&lt;/p&gt;

&lt;p&gt;Unicode 的目标简单的说就是提供一个字符集，包含现在使用的所有字符，也就是所有的字母和数字、所有的象形文字图形和所有的符号。如你所想的，这是一项很艰巨的任务，不过完成的不错。花点时间&lt;a href=&quot;http://www.unicode.org/charts/&quot;&gt;查看一下 Unicode 标准现在包含的字符&lt;/a&gt;吧。Unicode 协会经常提醒我们，这个字符集还有空间可以放下更多的字符，所以如果发现了外星种族文字只需直接添加进去就行了。&lt;/p&gt;

&lt;p&gt;为了真正的理解 Unicode 是什么，我需要澄清一个容易混乱的概念：字符集和字符编码不是指同一个事物。Unicode 是一个字符集，但是有不同的字符编码方式。下面解释一下。&lt;/p&gt;

&lt;p&gt;字符集是将一堆的符号映射到对应的数字上，这些神奇的数字是电脑用来显示字符的。Unicode 把这些数字叫做 码位，通常使用 &lt;code&gt;U+0061&lt;/code&gt; 的形式书写，其中 &lt;code&gt;U+&lt;/code&gt; 表示这是 Unicode 字符，后面的四个数字是“码位”的十六进制表现形式。所以 0061 就是 97，恰好就是 a 的 Unicode 码位，如果你还记得第一节中的内容，你会发现这和 US-ASCII 是一致的。这一点后续会详细说明。有一点值得注意，Ruby  1.8 和 1.9 都可以显示码位：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -vKUe &#39;p &quot;aé…&quot;.unpack(&quot;U*&quot;)&#39;
ruby 1.8.6 (2008-08-11 patchlevel 287) [i686-darwin9.4.0]
[97, 233, 8230]

$ ruby_dev -ve &#39;p &quot;aé…&quot;.unpack(&quot;U*&quot;)&#39;
ruby 1.9.0 (2008-10-10 revision 0) [i386-darwin9.5.0]
[97, 233, 8230]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;unpack()&lt;/code&gt; 参数中的 &lt;code&gt;U&lt;/code&gt; 指明需要的是 Unicode 码位，&lt;code&gt;*&lt;/code&gt; 指明后续的字符都照此处理。请注意 我使用了 &lt;code&gt;-KU&lt;/code&gt; 让 Ruby 1.8 运行在 UTF-8 模式下。Ruby 1.9 基于我的环境设置，默认使用 UTF-8。当我们开始将语言细节的时候会详细说明这一点。&lt;/p&gt;

&lt;p&gt;码位并不是文件中记录的内容，它们只是各个字符的抽象表现。字符如何写入真正的数据流就是“编码”了。针对 Unicode 有很多种编码方式，或者说有不同的方式将这些抽象的数字写入文件中。&lt;/p&gt;

&lt;p&gt;不同的编码方式有不同的优点。例如，Unicode 的其中一种编码方式是 UTF-32，每 32 位（4字节）呈现一个码位。这种编码的好处是字符总是以四个字节的长度出现（不同于下面讨论的可变长度的编码方式）。而很明显的一个缺点就是空间的浪费，我的意思是说如果你的数据全是 ASCII 中的符号，你真正需要的只是一个字节，但是 UTF-32 却总是会使用四个字节。&lt;/p&gt;

&lt;p&gt;在处理多字节编码时一定要小心谨慎。UTF-32 就是一个很好的例证来说明这其中的麻烦，因为数据的某些部分看起来很正常。例如，看一下 Ruby 1.9 中的这个简单的字符串：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby_dev -ve &#39;p &quot;abc&quot;.encode(&quot;UTF-32BE&quot;)&#39;
ruby 1.9.0 (2008-10-10 revision 0) [i386-darwin9.5.0]
&quot;\x00\x00\x00a\x00\x00\x00b\x00\x00\x00c&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;从上面的代码可以看到有很多的空字节，也请注意其中也有正常的“a”，“b”和“c”字节。我不是想展示坏习惯，但是如果你把“a”字节替换成两个字节的“ab”，那么编码将会被破坏，最终导致一些问题。在处理字符串的截取时也要格外小心，确保你没有在字符的中间截断。&lt;/p&gt;

&lt;p&gt;Unicode 的另一种编码实现是 UTF-8。因为下述的一些原因，这种编码方式在当今的 Email 和网页中比较流行。其一，UTF-8 和 US-ASCII 完全兼容，UTF-8 的前 128 个码位和 US-ASCII 是一致的，而且 UTF-8 使用单一字节对其编码。在 Ruby 1.9 中执行：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat ascii_and_utf8.rb
str   = &quot;abc&quot;
ascii = str.encode(&quot;US-ASCII&quot;)
utf8  = str.encode(&quot;UTF-8&quot;)

[ascii, utf8].each do |encoded_str|
  p [encoded_str, encoded_str.encoding.name, encoded_str.bytes.to_a]
end

$ ruby_dev -v ascii_and_utf8.rb
ruby 1.9.0 (2008-10-10 revision 0) [i386-darwin9.5.0]
[&quot;abc&quot;, &quot;US-ASCII&quot;, [97, 98, 99]]
[&quot;abc&quot;, &quot;UTF-8&quot;, [97, 98, 99]]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;上面的代码片段使用了一些 Ruby 1.9 中的新特性。在这不做深入说明，只是简单介绍一下：&lt;code&gt;encode()&lt;/code&gt; 可以将字符串从当前编码转换到所提供的编码方式；&lt;code&gt;encoding()&lt;/code&gt; 返回字符串当前所用编码方式所对应的 &lt;code&gt;Encoding&lt;/code&gt; 对象；&lt;code&gt;name()&lt;/code&gt; 将 &lt;code&gt;Encoding&lt;/code&gt; 对象显示为编码的名称；Ruby 1.9 中的字符串提供了 &lt;code&gt;Enumerator&lt;/code&gt; 来遍历其所含内容：&lt;code&gt;bytes()&lt;/code&gt;、&lt;code&gt;chars()&lt;/code&gt;、&lt;code&gt;codepoints()&lt;/code&gt; 和 &lt;code&gt;lines()&lt;/code&gt;，我使用 &lt;code&gt;bytes()&lt;/code&gt; 来获取它们的字节。在讨论 Ruby 1.9 处理编码方式时会对此做更深入的说明。&lt;/p&gt;

&lt;p&gt;针对上面的示例，现在你需要关注的点是 US-ASCII 和 UTF-8 在底层字节上的表现是一样的。&lt;/p&gt;

&lt;p&gt;当然，128 个字符无法包含更大容量的 Unicode 字符集，所以你需要更多的字节。UTF-8 是一种变长字节的编码方式，如果需要它会基于下列的规则使用更多的字节来表示更大的码位：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;单字节字符的最高有效位总是 0：&lt;code&gt;0xxxxxxx&lt;/code&gt;；&lt;/li&gt;
  &lt;li&gt;有效位为 1 的数量表明该码位包含的字节数。因此一个由两个字节表示的字符，它的最高有效位是 &lt;code&gt;110xxxxx&lt;/code&gt;；如果是三个字节的字符则是 &lt;code&gt;1110xxxx&lt;/code&gt;；&lt;/li&gt;
  &lt;li&gt;多字节字符的后续字节全部以 10 开头：&lt;code&gt;10xxxxxx&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;同样，Ruby 1.9 可以向我们展示：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat utf8_bytes.rb
# encoding:  UTF-8

chars = %w[a é …]
chars.each do |char|
  p char.bytes.map { |b| b.to_s(2) }
end

$ ruby_dev utf8_bytes.rb
[&quot;1100001&quot;]
[&quot;11000011&quot;, &quot;10101001&quot;]
[&quot;11100010&quot;, &quot;10000000&quot;, &quot;10100110&quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;注意观察，不同的字符是怎样的有不同的长度，每个字节是如何按照上述的规则显示的。这就使得处理 UTF-8 编码的内容更安全，因为你不会看到一个单独的“a”在数据中不表示一个真正的“a”。但是你在截取字符串时还是要小心，避免将多字节的字符截断。&lt;/p&gt;

&lt;p&gt;就我而言，这些因素结合在一起决定了 UTF-8 对全球性的字符编码是一个很好的选择。你需要的字符都有；ASCII 的内容没有修改。而且大多数的软件都支持 UTF-8。&lt;/p&gt;

&lt;p&gt;Unicode 是完美的吗？不，它不完美。&lt;/p&gt;

&lt;p&gt;有些字符有多种表现形式，例如，Unicode 的码位实际上是 Latin-1 的超集，因此 Unicode 包含了像 é 这样的单字节版本的重音符号，然而 Unicode 有标识合成的概念，音调可以有一个码位，字母有另一个码位，当需要显示时二者就会组合在一起，这就会导致一些很微妙的事情发生：两个字符串包含了相同的内容，但根据测试方法的不同却有可能是不相等的。这也会减弱像 UTF-32 这种编码方式的好处，因为四个字节已经可以确保一个码位，但是现在却需要多个码位 来组成一个字符。&lt;/p&gt;

&lt;p&gt;因为一些原因，亚洲文化也在减慢 Unicode 的推广。首先，Unicode 经常会使数据变得更庞大。例如，Shift JIS 可以用两个字节表示全部的日本语字符，但是在 Unicode 中，大多数的字符需要三个字节。硬盘的价格现在已经很便宜了，但是在某些情况下将大多数数据扩容 1.5 倍还是一个很关键的考虑点。&lt;/p&gt;

&lt;p&gt;Unicode 协会在确定全部的字符时还不得不做出一些艰难的抉择，在这些抉择中，&lt;a href=&quot;http://en.wikipedia.org/wiki/Han_unification&quot;&gt;汉字的统一化&lt;/a&gt; 已经激烈的争论了很长时间。我想很多人都意识到为什么现在需要做出一个选择，但是这样的争辩势必会减缓 Unicode 的推广，特别是在日本。&lt;/p&gt;

&lt;p&gt;最后，还有很多现存的数据不是使用 Unicode 编码的。很不幸，现在还无法无痛地将这些数据转换成 Unicode。所有这些因素结合起来使“Unicode 可以解决一切编码问题”这样的观点是不完美的。&lt;/p&gt;

&lt;p&gt;不过，使用一种编码为更多的受众服务，Unicode 仍然是最好的选择。&lt;/p&gt;

&lt;p&gt;本节重点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;字符集和编码方式并不一定是同一种事物&lt;/li&gt;
  &lt;li&gt;Unicode 这个字符集可以使用不同的编码方式编码&lt;/li&gt;
  &lt;li&gt;Unicode 被设计用来支持人类所有的字符&lt;/li&gt;
  &lt;li&gt;在现今的软件开发中，你找不到一个比 Unicode 更好的默认编码方案来满足更高的使用比率&lt;/li&gt;
  &lt;li&gt;UTF-8 几乎是可供选择的最好的 Unicode 编码方法，因为它和 US-ASCII 兼容，而且处理起来更安全&lt;/li&gt;
  &lt;li&gt;多字节编码处理起来很麻烦，特别是像 UTF-32 这种包含常规数据的编码方式&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a id=&quot;um-h-3&quot;&gt;&lt;/a&gt;
## 3. 通用编码策略&lt;/p&gt;

&lt;p&gt;在进入细节之前，让我们来尝试总结一下处理编码问题时的最佳实践方法。我很确定你知道在处理编码问题时有很多方面需要考虑，那么就让我们集中关注那些最能帮助我们的关键点吧。&lt;/p&gt;

&lt;h3&gt;尽量多的使用 UTF-8&lt;/h3&gt;

&lt;p&gt;我们知道 UTF-8 并不完美，但是它已经接近完美了。没有任何一种其他的编码方式供你选择来满足如此大范围的受众。它是我们最好的方法。基于这些原因，&lt;a href=&quot;ftp://ftp.isi.edu/in-notes/rfc2277.txt&quot;&gt;UTF-8 迅速成为网络和 Email 等的首选编码&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;如果你能够决定你的软件能够接受、支持和分发哪一种/哪一些编码，那么尽可能多的使用 UTF-8 吧。这绝对是默认的最好的选项。&lt;/p&gt;

&lt;h3&gt;注明你所使用的编码&lt;/h3&gt;

&lt;p&gt;我们已经知道，如果要正确的处理数据，你必须知道数据的编码方式。虽然有些工具可以帮助你猜测编码的方法，但是你要极力避免这样做，为此你需要在每一个步骤都为你的数据注明编码方式。&lt;/p&gt;

&lt;p&gt;发送邮件时，确保你指定了正确的字符集；为网页添加一个元标签指明编码；为你的 API 允许接受和返回的数据指定编码方式。这样做可以提高大家注明编码的意识，对每个人都有好处。&lt;/p&gt;

&lt;h3&gt;提高编码安全意识&lt;/h3&gt;

&lt;p&gt;你要习惯思考：这种编码方式安全吗？当你调用方法时，问一问你自己。当你在某个过程中处理数据时，亲身去检查一下结果。&lt;/p&gt;

&lt;p&gt;你是否在 Ruby 1.8 中使用过类似 &lt;code&gt;str[1..-2]&lt;/code&gt; 的代码？我想你用过，这样做是不安全的。你在分隔字节，将一个大的字符切成很多小片段，你得到数据就会变得一团糟。&lt;/p&gt;

&lt;p&gt;这么做听起来像是偏执狂，但确实没有看起来那么的不好。往往只有几个很关键的时刻需要你保护你的数据，反复的问自己这个问题可以帮助你发现这些时刻。&lt;/p&gt;

&lt;p&gt;举个例子，我在增强 Ruby 1.9 标准库中的 CSV 库对多语言的支持时，需要使用正则表达式处理一些用户提供的数据。这很简单是吧？&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;Regexp.escape(data)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;幸运的是，我本能的反应是，这么做安全吗？我给 &lt;code&gt;Regexp.escape()&lt;/code&gt; 提供了一些 UTF-32 编码的数据进行测试。记住，多字节编码可以显示一些看起来一般的数据，这有利于进行一些边界测试。Ruby 损坏了我的数据：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;p Regexp.escape(&quot;+&quot;.encode(&quot;UTF-32BE&quot;))
&quot;\x00\x00\x00\+&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;这只是 Ruby 1.9 刚发布不久所带来的后果。这个问题好像在新版本中修正了：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby_dev -ve &#39;p Regexp.escape(&quot;+&quot;.encode(&quot;UTF-32BE&quot;))&#39;
ruby 1.9.0 (2008-10-10 revision 0) [i386-darwin9.5.0]
&quot;\x00\x00\x00\\x00\x00\x00+&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;不过，观点依然成立，某些时候你甚至不能相信 Ruby。一定要小心。&lt;/p&gt;

&lt;p&gt;如此自然会得到一个结论，你需要知道数据在传输过程中的编码。HTML 是否能够以 UTF-8 方式接收表单数据？接收这些数据时，Ruby 是否是 UTF-8 模式？MySQL 中存储这些数据的表是否设置为 UTF-8 编码方式？现今的 Rails 会为你处理这三个步骤中的两个。这就是为什么你需要了解你所使用的工具。&lt;/p&gt;

&lt;p&gt;这些策略不是你需要的全部，但却是一个好的开始。没有太多的内容需要记忆，它会很好的增强你处理编码问题的意识，这才是最重要的。&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;um-h-4&quot;&gt;&lt;/a&gt;
## 4. Ruby 1.8 中的字节和字符&lt;/p&gt;

&lt;p&gt;Gregory Brown 在 Lone Star RubyConf 的一个培训环节中说过：Ruby 1.8 处理的是字节；Ruby 1.9 处理的是字符。Ruby 1.9 的情况有点小复杂，会在后面讨论，不过 Gregory 对 Ruby 1.8 的概括太正确了。&lt;/p&gt;

&lt;p&gt;在 Ruby 1.8 中，一个字符串就是一连串的字节。&lt;/p&gt;

&lt;p&gt;不过关键的问题是，这样的处理方式和我们之前讨论过的字符编码有什么关联呢？实际上 Ruby 将所有的责任都交到作为开发者的你身上了。Ruby 1.8 让你自己决定如何处理这些字节，而没有提供过多的编码方面的帮助。所以在使用 Ruby 1.8 时要了解一些基本的编码知识。&lt;/p&gt;

&lt;p&gt;Ruby 1.8 如此处理字符串和其他任何系统一样有优点也有缺点。先说优点，Ruby 1.8 可以支持可以想象的任何一种编码方式。毕竟字符编码就是将一些字节映射到一个字符集上，而 Ruby 1.8 中的字符串就是一系列的字节。如果你说一个字符串包含了一些 Latin-1 数据，同时也已这种编码来处理它，Ruby 会欣然接受你所说的。&lt;/p&gt;

&lt;p&gt;不过说实话这种处理方式的缺点要比优点多的多。Latin-1 只是一个简单的情况，一个字节对应一个字符。但对于其他很多字符编码，比如我们推荐使用的 UTF-8，事情就要复杂的多。&lt;/p&gt;

&lt;p&gt;Ruby 1.8 中通过索引截取字符串是以字节的形式处理的，这就意味着可能不小心就把多字节的字符截断了。使用正则表达式处理数据时也面临同样的问题。这只是两个我们经常会遇到的情况，而 Ruby 1.8 中很多字符串相关的操作都不是编码安全的。你甚至不能调用一些简单的方法，例如 &lt;code&gt;reverse()&lt;/code&gt;，它会将多字节字符的顺序打乱。还有一点要注意，&lt;code&gt;size()&lt;/code&gt; 总是返回字节的数量，而不是字符的数量。&lt;/p&gt;

&lt;p&gt;Ruby 1.8 也不会监管字符串的内容。也就是说在 Ruby 1.8 中，一个合法的 UTF-8 编码的字符串、一个损坏了的 UTF-8 字符串和一个既有 Latin-1 编码的数据也有 UTF-8 编码的数据的字符串都是字符串，Ruby 不管这些。后面两种字符串对你没有什么用，所以就靠你自己去避免产生这样的问题。如果你从两个不同编码方式的源获取了字符串，你就不能简单的使用 &lt;code&gt;+&lt;/code&gt; 将二者连接起来。&lt;/p&gt;

&lt;p&gt;这些可能已经让你开始绝望了，不过 Ruby 1.8 会在很多情况下抛出一个异常：正则表达式处理引擎不会处理四个字符的编码方式。我们经常可以利用这一点来处理字符。&lt;/p&gt;

&lt;p&gt;Ruby 1.8 提供了哪些编码方式呢？下面是完整的列表：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;不编码（n 或者 N）&lt;/li&gt;
  &lt;li&gt;EUC（e 或者 E）&lt;/li&gt;
  &lt;li&gt;Shift_JIS（s 或者 S）&lt;/li&gt;
  &lt;li&gt;UTF-8（u 或者 U）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;“不编码”是 Ruby 1.8 的默认模式，就是我们之前提到的：把一切都看做字节。如果你要用的编码方式不在上述的列表中，那么就只能使用“不编码”，而且要确保不做会破坏编码的事情。这有点难度，在 Ruby 1.8 中处理没采用上述列表中编码方式的数据是一项很有挑战的工作。&lt;/p&gt;

&lt;p&gt;EUC（Extended Unix Code，详情请参照 &lt;a href=&quot;http://zh.wikipedia.org/wiki/EUC&quot;&gt;Wikipedia&lt;/a&gt;）和 Shift_JIS 都是处理亚洲语言字符的编码方式，Shift_JIS 用于日本语，EUC 主要用于日本语、韩语和简体中文。你应该知道 Ruby 是来自日本的，很显然这些编码方式对亚洲用户很有用，而对我们（译者注：原文作者是西方人士）则没有太多的用处。&lt;/p&gt;

&lt;p&gt;不过幸好 UTF-8 也出现在这个列表中了，是的，这就是说 Ruby 1.8 对处理 UTF-8 编码的数据提供了有限的支持，虽然不全面，但至少有点帮助。&lt;/p&gt;

&lt;p&gt;列表中括弧内的字母在 Ruby 1.8 中用来标识你在以哪种编码方式处理数据，我会在详细讨论的时候告诉你在什么地方需要用到这样的标识。&lt;/p&gt;

&lt;p&gt;如果字符采用上述列表中的方式进行编码意味着什么？意味着正则表达式处理引擎可以识别采用这些编码方式的字符，甚至在多字节的时候也可以。这就保证了正则表达式构建目标的字符，比如字符类（&lt;code&gt;[...]&lt;/code&gt;）和匹配一个字符的快捷方式（&lt;code&gt;.&lt;/code&gt;），可以正确的匹配数据中特定位置上的字符，不管这些字符是由几个字节组成的。同时也改变了匹配空白的 &lt;code&gt;\s&lt;/code&gt; 和匹配单词字符的 &lt;code&gt;\w&lt;/code&gt; 二者的定义方式。在 Unicode 中一个单词中的字符数量要比 ASCII 中的 &lt;code&gt;[A-Za-z0-9_]&lt;/code&gt; 多一些。&lt;/p&gt;

&lt;p&gt;举几个例子来说明一下具体的运作方式吧。我会在 Ruby 1.8 中使用一个简单的使用 UTF-8 编码的字符串，向你展示多种编码方式的效果。记住默认的模式是“不编码”，所以如果你不指定其他方式就会“不编码”。&lt;/p&gt;

&lt;p&gt;在 Ruby 1.8 中经常对字符串进行的一个操作是将其转换成一个包含字符串中所有字符的数组。如果我们这么做就会发现 Ruby 1.8 将字符串按照字节处理是有一些不足之处的。下面的代码基本上可以实现这个操作：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -e &#39;p &quot;Résumé&quot;.scan(/./m)&#39;
[&quot;R&quot;, &quot;\303&quot;, &quot;\251&quot;, &quot;s&quot;, &quot;u&quot;, &quot;m&quot;, &quot;\303&quot;, &quot;\251&quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;你或许已经知道 &lt;code&gt;scan()&lt;/code&gt; 的作用是将字符串中与其参数指定的正则表达式匹配的字符组成一个数组。其中的 &lt;code&gt;/m&lt;/code&gt; 选项将正则表达式设为多行模式，在多行模式下一个 &lt;code&gt;.&lt;/code&gt; 匹配所有的字符（但一般不匹配换行符）。&lt;/p&gt;

&lt;p&gt;那么上面的代码有什么不好的地方呢？字符串中的“é”字符在 UTF-8 模式下占了两个字节，Ruby 1.8 的规则告诉我们一切都是字节，我们看到的结果就证明了这一点。多字节的字符被截断了，这样的操作方式并不好，因为如果我现在修改了数组的内容，很有可能我会破坏数据。&lt;/p&gt;

&lt;p&gt;再次说明，以上是采用默认的“不编码”方式，因为我们没有指明要使用其他的方式。如果我们将正则表达式设为 UTF-8 模式的话，我们就会得到真正的字符：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -e &#39;p &quot;Résumé&quot;.scan(/./mu)&#39;
[&quot;R&quot;, &quot;\303\251&quot;, &quot;s&quot;, &quot;u&quot;, &quot;m&quot;, &quot;\303\251&quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;注意到“é”的两个字节现在是在一起了吗？（我会在下一节中告诉你怎样避免 Ruby 转义内容得到真正的“é”）正则表达式处理引擎将 UTF-8 编码的一个字符的两个字节放在一起了。我所指定的编码让匹配一个字符的 &lt;code&gt;.&lt;/code&gt; 将连个字节一起捕获。&lt;/p&gt;

&lt;p&gt;我通过为正则表达式字面值指定 &lt;code&gt;/u&lt;/code&gt; 将其设为 UTF-8 模式。你或与已经发现了，这个字母就是上述编码列表括弧中的字母。以此类推，你可以指定 &lt;code&gt;/e&lt;/code&gt; 设置 EUC 编码，指定 &lt;code&gt;/s&lt;/code&gt; 设置 Shift_JIS 编码，甚至可以指定 &lt;code&gt;/n&lt;/code&gt; 设置“不编码”。&lt;code&gt;Regexp.new()&lt;/code&gt; 可以接受第三个参数用于设定要创建的正则表达式的编码：&lt;code&gt;Regexp.new(&quot;.&quot;, Regexp::MULTILINE, &quot;u&quot;)&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;使用这种简单的方式，我们可以修正前面提到的不安全的字符串的方法。例如，Ruby 1.8 正常情况下的 &lt;code&gt;size()&lt;/code&gt; 统计字节的数量：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -e &#39;p &quot;Résumé&quot;.size&#39;
8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;不过现在如果需要，可以统计字符的数量：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -e &#39;p &quot;Résumé&quot;.scan(/./mu).size&#39;
6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;我们还可以修正危险的 &lt;code&gt;reverse()&lt;/code&gt; 方法，正常情况下这个方法会搞乱多字节字符“é”的字节顺序：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -e &#39;p &quot;Résumé&quot;.reverse&#39;
&quot;\251\303mus\251\303R&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;\303\251&lt;/code&gt; 是 UTF-8 编码模式下的“é”，但是上面代码所示的&lt;code&gt;\251\303&lt;/code&gt; 将数据破坏了，不表示任何的字符。我们可以通过以下方法进行修正：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -e &#39;p &quot;Résumé&quot;.scan(/./mu).reverse.join&#39;
&quot;\303\251mus\303\251R&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;我们使用正则表达式引擎将字符串转换成由字符组成的数组，然后反转（&lt;code&gt;reverse()&lt;/code&gt;）数组，再把数组的元素连接起来（&lt;code&gt;join()&lt;/code&gt;）组成字符串。你可以看到这样做保证“é”字节的顺序不变。&lt;/p&gt;

&lt;p&gt;认真的理解一下上面的示例知道你知道到底是怎么回事。这些就是 Ruby 1.8 为字符串处理提供的全部支持，所以你要学会如何使用。&lt;/p&gt;

&lt;p&gt;下面的例子说明了上面我提到的针对正则表达式的第二个影响：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -e &#39;p &quot;Résumé&quot;[/\w+/]&#39;
&quot;R&quot;

$ ruby -e &#39;p &quot;Résumé&quot;[/\w+/u]&#39;
&quot;R\303\251sum\303\251&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;在默认的“不编码”模式下，&lt;code&gt;\w&lt;/code&gt; 和 &lt;code&gt;[A-Za-z0-9_]&lt;/code&gt; 是等价的，不会匹配构成字符“é”所需的特殊字节，所以匹配到此为止。但是 UTF-8 模式有所改变，得到了整个单词。&lt;/p&gt;

&lt;p&gt;Ruby 1.8 除正则表达式处理引擎之外没有提供过多额外的编码支持。我们会在后面的章节中讨论一个神奇的变量和一些有用的标准库，但 Ruby 1.8 中对字符编码的支持主要就这些内容。&lt;/p&gt;

&lt;p&gt;另外有一个小特性可以顺便提一下，你可以使用字符串的 &lt;code&gt;unpack()&lt;/code&gt; 方法获取 Unicode 字符的码位：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ruby -e &#39;p &quot;Résumé&quot;.unpack(&quot;U*&quot;)&#39;
[82, 233, 115, 117, 109, 233]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;unpack()&lt;/code&gt; 参数中的 &lt;code&gt;U&lt;/code&gt; 指定将字符转换成 Unicode 的码位，&lt;code&gt;*&lt;/code&gt; 指明后续字符也做此操作。&lt;/p&gt;

&lt;p&gt;我不经常需要需要处理字符的码位，不过你可以使用这种方式实现一个很好玩的功能。Unicode 的码位是 Latin-1 字节值的超集，所以你可以使用 &lt;code&gt;unpack()&lt;/code&gt; 和 &lt;code&gt;pack()&lt;/code&gt; 在这两种编码之间转换：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;utf8 = latin1.unpack(&quot;C*&quot;).pack(&quot;U*&quot;)
# ... 或者 ...
latin1 = utf8.unpack(&quot;U*&quot;).pack(&quot;C*&quot;)  # 更危险
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;我会在后续的章节告诉你一种更好的编码转换方法。&lt;/p&gt;

&lt;p&gt;很重要的一点需要记住，这不是全部的字符编码支持。例如，如果要将 Unicode 字符转换成大写形式要遵循一个很长的规则列表，但是 &lt;code&gt;upcase()&lt;/code&gt; 并不知道这个规则列表，而且你也无法通过正则表达式来解决这个麻烦。如果需要某个编码方式提供这个特性，你需要寻找额外的支持库或者自己实现解决方案。&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;um-h-5&quot;&gt;&lt;/a&gt;
## 5. $KCODE 变量和 jcode 库&lt;/p&gt;

&lt;p&gt;我创建的所有 Ruby 文件都是以一行 &lt;a href=&quot;http://zh.wikipedia.org/wiki/Shebang&quot;&gt;Shebang&lt;/a&gt; 声明开始的：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;#!/usr/bin/env ruby -wKU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;不是每个文件都需要这行声明，它只对可执行的文件有效。但是我还是倾向于把它加入我创建的所有文件，因为：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;你无法预测文件是否会被执行（例如，很多库都有这样的代码 &lt;code&gt;if __FILE__ == $PROGRAM_NAME; end&lt;/code&gt;）&lt;/li&gt;
  &lt;li&gt;它指明了这个文件包含的是 Ruby 代码&lt;/li&gt;
  &lt;li&gt;它指明了这些代码基于 &lt;code&gt;-w&lt;/code&gt; 和 &lt;code&gt;-KU&lt;/code&gt; 的规则&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上述第三条提到的规则，是通过命令行的开关指定的，这些规则比较有趣。&lt;code&gt;-w&lt;/code&gt; 可以开启 Ruby 中很有用的错误提示，推荐你尽量多的使用这个参数。不过这个参数和字符编码没什么关系，真正起作用的是 &lt;code&gt;-KU&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-KU&lt;/code&gt; 设定了一个神奇的 Ruby 变量：&lt;code&gt;$-K&lt;/code&gt;，也叫 &lt;code&gt;$KCODE&lt;/code&gt;。如果你无法设置命令行参数，可以在代码中进行设定：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$KCODE = &quot;U&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;你或许已经发现了，这里的 &lt;code&gt;U&lt;/code&gt; 就是上一节中提到的 Ruby 1.8 的 UTF-8 编码。这个变量还可以设置为 &lt;code&gt;N&lt;/code&gt;（默认值）、&lt;code&gt;E&lt;/code&gt; 和 &lt;code&gt;S&lt;/code&gt;。较新版本的 Rails 为你设置了 &lt;code&gt;$KCODE = &quot;U&quot;&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;那么修改这个神奇变量的值有什么作用呢？首先，有个很小的作用是改变 Ruby 在使用 &lt;code&gt;inspect()&lt;/code&gt; 时的输出转义。看一下下面的代码片段：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -e &#39;p &quot;Résumé&quot;&#39;
&quot;R\303\251sum\303\251&quot;

$ ruby -KUe &#39;p &quot;Résumé&quot;&#39;
&quot;Résumé&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;能够看到数据的原始形式很不错，这能假定你的命令行能够正确的处理 UTF-8 数据。不过这只是设置 &lt;code&gt;$KCODE&lt;/code&gt; 的一个副作用。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$KCODE&lt;/code&gt; 的主要作用是改变所有未指定编码的正则表达式的默认编码方式。因此我们无需在正则表达式后添加一个 &lt;code&gt;/u&lt;/code&gt; 选项就可以截取 UTF-8 数据了：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -e &#39;p &quot;Résumé&quot;.scan(/./m)&#39;
[&quot;R&quot;, &quot;\303&quot;, &quot;\251&quot;, &quot;s&quot;, &quot;u&quot;, &quot;m&quot;, &quot;\303&quot;, &quot;\251&quot;]

$ ruby -KUe &#39;p &quot;Résumé&quot;.scan(/./m)&#39;
[&quot;R&quot;, &quot;é&quot;, &quot;s&quot;, &quot;u&quot;, &quot;m&quot;, &quot;é&quot;]

$ ruby -KUe &#39;p &quot;Résumé&quot;.scan(/./mn)&#39;
[&quot;R&quot;, &quot;\303&quot;, &quot;\251&quot;, &quot;s&quot;, &quot;u&quot;, &quot;m&quot;, &quot;\303&quot;, &quot;\251&quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;注意上述代码的第二段将默认编码换到了 UTF-8。不过我仍可以通过显示指定编码方式进行覆盖，在第三段代码中我就添加了 &lt;code&gt;/u&lt;/code&gt; 选项指定“不编码”。&lt;/p&gt;

&lt;p&gt;最近我倾向于使用 &lt;code&gt;$KCODE&lt;/code&gt; 而不是 &lt;code&gt;$-K&lt;/code&gt;，因为前者看起来在 Ruby 中更常用。实际上，Ruby 1.8 在另外一个地方也是用了这个名称，有一个方法可以获取正则表达式所采用的编码方式：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -e &#39;p /./.kcode&#39;
nil

$ ruby -e &#39;p /./u.kcode&#39;
&quot;utf8&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;仔细的观察后你会发现，&lt;code&gt;kcode()&lt;/code&gt; 隐藏着一些问题。首先，你可以看到它对编码采用不同于前述的名称。而且它好像不能读取 &lt;code&gt;$KCODE&lt;/code&gt; 变量的值，而是返回一个很诡异的名字：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -e &#39;$KCODE = &quot;U&quot;; re = /./m; p &quot;Résumé&quot;.scan(re); p re.kcode&#39;
[&quot;R&quot;, &quot;é&quot;, &quot;s&quot;, &quot;u&quot;, &quot;m&quot;, &quot;é&quot;]
nil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;正如你所看到的，表达式的编码已经很明确的正确设置了，不过 &lt;code&gt;kcode()&lt;/code&gt; 却没有返回设置后的结果。如果在 Ruby 1.8 中你真的想知道正则表达式的编码，我建议你采用如下的代码：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;class Regexp
  def encoding
    if kcode
      kcode[0, 1]
    elsif %w[n N u U e E s S].include? $KCODE
      $KCODE.downcase
    else
      &quot;n&quot;
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;只使用 &lt;code&gt;kcode()&lt;/code&gt; 返回结果的第一个字母可以让我们得到标准字母集合中的值。如果没有设定 &lt;code&gt;kcode()&lt;/code&gt;，我们可以使用 &lt;code&gt;$KCODE&lt;/code&gt;。不过请注意，我确保了它的值被设为一个我们期望看到的值。你可以把 &lt;code&gt;$KCODE&lt;/code&gt; 设为任意的值，不过 Ruby 会悄无声息的忽略它，然后回滚到默认的 N。所以如果你完全依赖于它的返回值就要切身检测一下。最后，如果二者都没有设定就返回默认值。&lt;/p&gt;

&lt;p&gt;以上就是你需要知道的关于 &lt;code&gt;$KCODE&lt;/code&gt; 的一切。Ruby 1.8 提供了一个简单的标准库叫做 jcode，它可以和前面两节所讨论的内容较好的结合。&lt;/p&gt;

&lt;p&gt;如要使用 jcode 库，需要先设置 &lt;code&gt;$KCODE&lt;/code&gt;，然后再引入这个库。先设置 &lt;code&gt;$KCODE&lt;/code&gt; 这一点很重要，如果在此之前你引入了 jcode 库，会得到一个警告（只要你听了我的建议通过 &lt;code&gt;-w&lt;/code&gt; 打开了警告功能）：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -r jcode -e &#39;p &quot;Résumé&quot;.jsize&#39;
8

$ ruby -w -r jcode -e &#39;p &quot;Résumé&quot;.jsize&#39;
Warning: $KCODE is NONE.
8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;所以我说 &lt;code&gt;-w&lt;/code&gt; 很重要。&lt;/p&gt;

&lt;p&gt;一旦正确的设置了 &lt;code&gt;$KCODE&lt;/code&gt;，jcode 库会为字符串添加一系列用来处理字符的方法。这些方法就是将前一节讨论过的技术打包，所以你就获得了类似 &lt;code&gt;jsize()&lt;/code&gt; 这样可以获得字符数量而不是字节数量的方法：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -KU -r jcode -e &#39;p &quot;Résumé&quot;.jsize&#39;
6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;或许 jcode 库添加的最有用的方法就是 &lt;code&gt;each_char()&lt;/code&gt; 了：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -KU -r jcode -e &#39;&quot;Résumé&quot;.each_char { |c| p c }&#39;
&quot;R&quot;
&quot;é&quot;
&quot;s&quot;
&quot;u&quot;
&quot;m&quot;
&quot;é&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;完整的方法列表请查看&lt;a href=&quot;http://www.ruby-doc.org/stdlib-1.8.6/libdoc/jcode/rdoc/index.html&quot;&gt;该库的文档&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;um-h-6&quot;&gt;&lt;/a&gt;
## 6. 使用 iconv 转换字符编码&lt;/p&gt;

&lt;p&gt;如果要完全说明 Ruby 1.8 对字符编码的支持还有最后一个标准库需要介绍，iconv，它可以处理一系列的字符编码转换操作。&lt;/p&gt;

&lt;p&gt;这是个很重要的代码库。你或许接受了我的建议，在有的选择时，只处理 UTF-8 的数据是可行的，但是在现实的世界中还有很多没有采用 UTF-8 编码的数据。旧系统可能在 UTF-8 流行之前产生了数据；很多服务基于一些原因可能在不同的编码方式之间运行着。并不是每个人都完全的转向 UTF-8 了。如果你遇到了这样的数据，在导入数据前你需要将其转换成 UTF-8 编码，或许在输出时还需要再转换回来。这些就是通过 iconv 处理的。&lt;/p&gt;

&lt;p&gt;我们先不看 Ruby 的 iconv 库，而是换一个稍微不同的方式来了解这个库。iconv 实际上是一个 C 语言库，实现了编码之间的转换，在大多数安装了该库的系统上都会为其提供一个命令行的接口。&lt;/p&gt;

&lt;p&gt;iconv 程序使用很简单，只需完全按照下述的三个步骤操作：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;告知 iconv 你要输出数据的编码方式，包括一些转换说明&lt;/li&gt;
  &lt;li&gt;告知 iconv 你所接收数据的编码方式&lt;/li&gt;
  &lt;li&gt;通过 &lt;code&gt;STDIN&lt;/code&gt;（如果你愿意，可以直接列出文件） 向 iconv 提供输入，然后将 iconv 的 &lt;code&gt;STDOUT&lt;/code&gt; 转到你所需要的输出上&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;例如，我有一些 UTF-8 编码的数据：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ echo &quot;Résumé&quot; &amp;gt; utf8.txt
$ wc -c utf8.txt
       9 utf8.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;我的终端处在 UTF-8 模式，所以 &lt;code&gt;echo&lt;/code&gt; 的数据写入了文件。你可以看出这些数据被编码了，因为我们的文件有 9 个字节（“R”、“s”、“u”、“m”各一字节，“\n”一字节，两个“é”各两字节）。&lt;/p&gt;

&lt;p&gt;通过下面的方法可以用 iconv 将这些数据转换成 Latin-1 编码：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ iconv -t LATIN1 -f UTF8 &amp;lt; utf8.txt &amp;gt; latin1.txt
$ wc -c latin1.txt
       7 latin1.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;你可以看到转换成功了，因为“é”在 Latin-1 编码中只占一个字节，我们节省了两个字节。&lt;/p&gt;

&lt;p&gt;注意我三步走的用法：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;我使用 &lt;code&gt;-t LATIN1&lt;/code&gt; 设置转换后的编码，没有指定额外的转换说明&lt;/li&gt;
  &lt;li&gt;我使用 &lt;code&gt;-f UTF8&lt;/code&gt; 指定输入的编码&lt;/li&gt;
  &lt;li&gt;我使用 &lt;code&gt;&amp;lt; utf8.txt&lt;/code&gt; 指定输入文件，&lt;code&gt;&amp;gt; latin1.txt&lt;/code&gt; 指定输出文件&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;我们总是使用这样的三个步骤。&lt;/p&gt;

&lt;p&gt;关于 iconv 还有两件事你需要知道。第一，iconv 支持很多编码方法，包括本文提到的所有常见方法。不过，在不同的平台上可能有所不同，所以你需要检查一下可以使用的有哪些：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ iconv --list
ANSI_X3.4-1968 ANSI_X3.4-1986 ASCII CP367 IBM367 ISO-IR-6 ISO646-US
  ISO_646.IRV:1991 US US-ASCII CSASCII
UTF-8 UTF8
UTF-8-MAC UTF8-MAC
ISO-10646-UCS-2 UCS-2 CSUNICODE
UCS-2BE UNICODE-1-1 UNICODEBIG CSUNICODE11
UCS-2LE UNICODELITTLE
ISO-10646-UCS-4 UCS-4 CSUCS4
UCS-4BE
UCS-4LE
UTF-16
…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;上面的结果每一行代表一种编码，同一行内以空格分开的是 iconv 可以支持的编码的别名。所以，第一行这个因为过长而被我截断成两行的列表都是 US-ASCII 的别名。往下一行我们看一看到，iconv 同时接受 UTF8 和 UTF-8。&lt;/p&gt;

&lt;p&gt;第二件关于 iconv 需要知道的事是，它有一些特殊的转换模式。举例说明一下，我们使用另外一组数据：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ echo &quot;On and on… and on…&quot; &amp;gt; utf8.txt
$ cat utf8.txt
On and on… and on…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;最后一个字符是省略号，或者称之为在一起的三个点号。Unicode 中有这个字符，但是 Latin-1 中没有。让我们来看一下当我们尝试转化数据的话会发生什么：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ iconv -f UTF8 -t LATIN1 &amp;lt; utf8.txt &amp;gt; latin1.txt

iconv: (stdin):1:9: cannot convert
$ cat latin1.txt
On and on
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;如你所见，在遇到第一个有问题的字符时出现了一个错误提示。&lt;code&gt;cat&lt;/code&gt; 命令的结果告诉我们它已经完成了转换。&lt;/p&gt;

&lt;p&gt;这或许就是你所需要的结果，所以你可以告诉用户你无法处理他们的数据。不过我经常发现我需要尽力处理我手上的数据。iconv 的转换模式可以给我们一些帮助。&lt;/p&gt;

&lt;p&gt;首先，你可以设置 iconv 忽略那些无法转换到新编码的字符：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ iconv -t LATIN1//IGNORE -f UTF8 &amp;lt; utf8.txt &amp;gt; latin1_wignore.txt
$ cat latin1_wignore.txt
On and on and on
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;你看到了，这一次我们完成了整个转换过程，只是丢弃了有问题的字符。&lt;code&gt;//IGNORE&lt;/code&gt; 添加了这样的转换模式。转换模式都是在输出编码后面设定的。这已经是个进步了，不过在这种情况下我们还可以做的更好。&lt;/p&gt;

&lt;p&gt;iconv 的另一个模式可以尝试将有问题的字符生硬的转换到目标编码字符集中对应的字符上：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ iconv -t LATIN1//TRANSLIT -f UTF8 &amp;lt; utf8.txt &amp;gt; latin1_wtranslit.txt
$ cat latin1_wtranslit.txt
On and on... and on...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;这一次，iconv 没有丢掉这些省略号，而是将其替换成三个点号。三个点号虽然不如 Unicode 字符中的省略号好看，不过转换的工作却完成了，而且保留了数据的要表达的意思。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;//TRANSLIT&lt;/code&gt; 模式并不能转换所有的字符，所以也有可能会遇到错误。不过你可以将两个模式结合起来成为 &lt;code&gt;//TRANSLIT//IGNORE&lt;/code&gt;。在这个模式下，iconv 会尽量生硬的转换，如果实在无法转换则将其丢掉。注意，模式的先后顺序很重要，你要确保在丢掉字符之前 iconv 已经尽力转换了。&lt;/p&gt;

&lt;p&gt;你还可以为 iconv 指定转换困难字符对应的字符。我从没有用过这样的模式，因为我发现生硬转换模式已经做的足够好了。如果你对这个模式好奇的话，可以通过 &lt;code&gt;man iconv&lt;/code&gt; 查看文档。&lt;/p&gt;

&lt;p&gt;以上就是你需要知道的关于 iconv 的一切。现在你已经是一个字符转换的专家了，恭喜你。&lt;/p&gt;

&lt;p&gt;当然，如果能讨论一下这些对 Ruby 都有哪些作用的话就更好了。那就让我们开始讨论吧。&lt;/p&gt;

&lt;p&gt;Ruby 标准库就像我们上面用到的程序一样，它只是为底层的 C 代码提供了一个方法接口。我们来看一下下面的代码：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;#!/usr/bin/env ruby -wKU

require &quot;iconv&quot;

utf8 = &quot;Résumé&quot;
utf8.size  # =&amp;gt; 8

latin1 = Iconv.conv(&quot;LATIN1&quot;, &quot;UTF8&quot;, utf8)
latin1.size  # =&amp;gt; 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;你可以看到步骤都是一样的。第一个参数是目标编码，第二个参数是数据的当前编码，第三个参数指定需要转换的数据，方法的返回值就是转换后的数据。&lt;/p&gt;

&lt;p&gt;如果一次需要进行多个转换，可以创建一个 &lt;code&gt;Iconv&lt;/code&gt; 实例然后重复使用：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;#!/usr/bin/env ruby -wKU

require &quot;iconv&quot;

utf8_to_latin1 = Iconv.new(&quot;LATIN1//TRANSLIT//IGNORE&quot;, &quot;UTF8&quot;)

resume = &quot;Résumé&quot;
utf8_to_latin1.iconv(resume).size  # =&amp;gt; 6

on_and_on = &quot;On and on… and on…&quot;
utf8_to_latin1.iconv(on_and_on)  # =&amp;gt; &quot;On and on... and on...&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;就是这样，&lt;code&gt;new()&lt;/code&gt; 方法创建一个对象，记住了转换前后的编码方式，然后调用 &lt;code&gt;iconv()&lt;/code&gt; （而不用前面使用的 &lt;code&gt;conv()&lt;/code&gt; 类方法）方法转换数据。&lt;/p&gt;

&lt;p&gt;如果出现问题，Ruby 接口会抛出一个异常，比如 &lt;code&gt;Iconv::InvalidEncoding&lt;/code&gt; 或 &lt;code&gt;Iconv::InvalidCharacter&lt;/code&gt;。详情请参照&lt;a href=&quot;http://www.ruby-doc.org/stdlib-1.8.6/libdoc/iconv/rdoc/index.html&quot;&gt;该库的文档&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;Ruby 1.8 中的这个库没有提供一个简便的方法来列出所有支持的编码，这也是为什么我先说明命令行程序的一个很大的原因，如果你要查看支持的编码，需要通过命令行查看。不过，Ruby 1.9 中已经添加了这个方法：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby_dev -r iconv -r pp -ve &#39;pp Iconv.list&#39;
ruby 1.9.0 (2008-10-10 revision 0) [i386-darwin9.5.0]
[[&quot;ANSI_X3.4-1968&quot;,
  &quot;ANSI_X3.4-1986&quot;,
  &quot;ASCII&quot;,
  &quot;CP367&quot;,
  &quot;IBM367&quot;,
  &quot;ISO-IR-6&quot;,
  &quot;ISO646-US&quot;,
  &quot;ISO_646.IRV:1991&quot;,
  &quot;US&quot;,
  &quot;US-ASCII&quot;,
  &quot;CSASCII&quot;],
 [&quot;UTF-8&quot;, &quot;UTF8&quot;],
…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;至此，对 Ruby 1.8 中字符编码相关工具的探索就结束了。在下一节中，我们将跳出这些讨论，来探究这样的系统存在什么问题。这会为我们对 Ruby 1.9 中新的多语言支持的讨论铺平道路。&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;um-h-7&quot;&gt;&lt;/a&gt;
## 7. Ruby 1.8 中字符编码的缺点&lt;/p&gt;

&lt;p&gt;我们已经概述了 Ruby 1.8 对编码的支持情况，接下来要讨论它的问题所在了。这些长期存在的问题致使核心开发团队为 Ruby 1.9 增加了对多语言（m17n）的支持。&lt;/p&gt;

&lt;p&gt;这些主要问题是：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;提供的编码支持力度不够&lt;/li&gt;
  &lt;li&gt;只对正则表达式提供了支持，支持不够全面&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;$KCODE&lt;/code&gt; 是全局性的编码设置&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我知道大多数的问题很明显，但是我还是会一个个的说明，确保能从过去的失误中学到一些经验。我很确信这样会使我们更好的理解为什么 Ruby 1.9 采用了如此的解决方法。&lt;/p&gt;

&lt;p&gt;“支持力度不够”是三者中最明显的，Ruby 1.8 支持四种编码方法，其中还有一个是“不编码”。这就是说你只拥有 UTF-8 和两个针对亚洲人群的编码。对 UTF-8 的支持使我们继续使用着，不过还有太多的编码没有提供支持。&lt;/p&gt;

&lt;p&gt;很重要的一点需要明确，我们不能一味的为 Ruby 1.8 添加对其他编码的支持。之前系统并没有作此设计。很快我们就会用完能够添加到正则表达式后面的字母。这样做太不优雅了。&lt;/p&gt;

&lt;p&gt;一旦有了更多的编码方法，就要考虑提供更广泛的支持。通过对正则表达式进行改进或许能够解决一些问题，但这也只是允许我们分隔字符。还有很多地方需要编码。如果需要检验数据是否正确编码怎么办？要处理字符组合时怎么办？要检索 Unicode 的码位怎么办？正则表达式无法解决所有的问题。&lt;/p&gt;

&lt;p&gt;而且，对大量的数据进行编码转换是很危险的。有很多地方你需要知道确切的编码方法：字符串中的数据，从 IO 中读取的数据，源代码本身的编码等。在 Ruby 1.8 中你无法区别对待这些情况，你只能在一处进行设置。我的源码使用 UTF-8，也对 &lt;code&gt;$KCODE&lt;/code&gt; 做了相应设置，那么如果我需要加载使用 Shift JIS 编码的代码库怎么办？二者之间必有一个会出问题，这对代码可不好。&lt;/p&gt;

&lt;p&gt;再次说明，我提炼出了这些问题点，因为我觉得这会有助于理解为什么 Ruby 1.9 做了改变。当我们深入讨论 Ruby 1.9 对编码的支持情况时，请留意这些缺点，看一下它们是如何解决的。&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;um-h-8&quot;&gt;&lt;/a&gt;
## 8. Ruby 1.9 中的字符串&lt;/p&gt;

&lt;p&gt;Ruby 1.9 引入了一个全新的编码引擎叫做 m17n（英文是 multilingualization，在 m 和 n 之间有 17 个字母）。这个引擎在其他编程语言中可能不常见。&lt;/p&gt;

&lt;p&gt;一般来说很多人会倾向于选择一个万能的编码，就像 Unicode 编码，然后所有的数据都按照这种编码操作。但是 Ruby 1.9 选择了另一种方法。Ruby 1.9 没有钟情于一种编码方法，而是让其能够处理超过 80 种编码方法。&lt;/p&gt;

&lt;p&gt;为了实现这种解决方法，Ruby 在很多进行数据处理的地方做了改变。你会发现最大的改变发生在字符串上，所以我们先来看看这里的变化。&lt;/p&gt;

&lt;h3&gt;现在所有的字符串都被编码了&lt;/h3&gt;

&lt;p&gt;Ruby 1.8 中的字符串就是一堆的字节。有时你会把这些字节按照不同的方式处理，在正则表达式中看做字符，在调用 &lt;code&gt;each()&lt;/code&gt; 时作为数据行。但是其本质上还是一些字节。索引数据时统计的是字节，查看长度时统计的也是字节。&lt;/p&gt;

&lt;p&gt;但是在 Ruby 1.9 中字符串则是一串被编码的数据。这意味着，字符串包含了原始的字节，同时还附属了编码信息指明如何处理这些字节。&lt;/p&gt;

&lt;p&gt;让我举个简单的例子来说明这个不同点。先不要管我是如何获得这些编码的，稍后会对此说明。现在将集中关注 Ruby 是如何利用附属的编码信息决定如何处理这些数据的：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;# 附属的编码信息
puts utf8_resume.encoding.name    # &amp;gt;&amp;gt; UTF-8
puts latin1_resume.encoding.name  # &amp;gt;&amp;gt; ISO-8859-1

# size() 现在返回编码后的数据（或字符）长度
puts utf8_resume.size    # &amp;gt;&amp;gt; 6
puts latin1_resume.size  # &amp;gt;&amp;gt; 6

# 不过我们还可以调用 bytesize() 来查看一下其间的不同
puts utf8_resume.bytesize    # &amp;gt;&amp;gt; 8
puts latin1_resume.bytesize  # &amp;gt;&amp;gt; 6

# 现在索引的是编码后的数据（字符）
puts utf8_resume[2..4]    # &amp;gt;&amp;gt; sum
puts latin1_resume[2..4]  # &amp;gt;&amp;gt; sum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;这些示例看上起很基础，不过我们可以从中学到很多东西。首先，注意字符串对象现在有了一个附属的编码对象（&lt;code&gt;Encoding&lt;/code&gt;）。我前面说过，字符串这个容器包含了原始的字节和附属其上处理这些字节的编码信息。现在 Ruby 中所有的字符串都包括这两部分，甚至当你指定要把它们作为原始的字节来处理也是如此（稍后会详细说明）。&lt;/p&gt;

&lt;p&gt;上述代码片段的后面两段说明，当我们让 Ruby 返回数据的长度（&lt;code&gt;size()&lt;/code&gt;）时，它会按照附属其上的规则先处理这些字节然后返回编码后的数据长度，一般来说就是字符的数量。如果需要我们可以直接查看原始字节大长度（&lt;code&gt;bytesize()&lt;/code&gt;），但这已经不是常规的处理方法了。这是与 Ruby 1.8 很大的一点不同之处。&lt;/p&gt;

&lt;p&gt;上述代码片段的最后一段说明索引也受到相同的影响。现在是以编码后的数据为准而不是字节了。所以虽然在 UTF-8 编码的字符串中需要跳过三个字节，而在 Latin-1 编码的字符串中只需要跳过两个字节，但是相同的索引长度还是得到了相同的返回结果。&lt;/p&gt;

&lt;p&gt;很重要的结论是：字符串现在包含了字节和处理这些字节的规则。希望这会让你开始有很自然的感觉，因为&lt;a href=&quot;#um-h-1&quot;&gt;这就是我们真正设想要字符编码去实现的&lt;/a&gt;。&lt;/p&gt;

&lt;h3&gt;改变编码&lt;/h3&gt;

&lt;p&gt;现在我还不想讨论&lt;a href=&quot;#um-h-9&quot;&gt;字符串是如何获得原始的 Encoding 对象的&lt;/a&gt;，这个问题会单独去讲。有些时候你想改变 &lt;code&gt;Encoding&lt;/code&gt;，这就涉及到字符串的更多新特性了。下面对此做一些说明。&lt;/p&gt;

&lt;p&gt;改变 &lt;code&gt;Encoding&lt;/code&gt; 的第一种方法是调用 &lt;code&gt;force_encoding()&lt;/code&gt;。这种方法告诉 Ruby，你更了解这些数据，你要改变处理这些数据的规则。例如：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;abc = &quot;abc&quot;
puts abc.encoding.name  # &amp;gt;&amp;gt; US-ASCII

abc.force_encoding(&quot;UTF-8&quot;)
puts abc.encoding.name  # &amp;gt;&amp;gt; UTF-8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;如上代码所示，我创建字符串时，Ruby 赋予它 US-ASCII 编码。再次说明，我们现在不关心 Ruby 是如何处理这个过程的。重点是我不想用 US-ASCII，而想换成 UTF-8 编码。所以我调用了 &lt;code&gt;force_encoding()&lt;/code&gt;，告诉 Ruby 这实际上是 UTF-8 编码的数据，你要改变附属其上的 &lt;code&gt;Encoding&lt;/code&gt; 对象。&lt;/p&gt;

&lt;p&gt;很重要的一点需要注意，在这里我可以作此操作是因为这些字节在 US-ASCII 和 UTF-8 编码中是一样的。我并没有改变数据，只是改变了处理这些数据的规则。&lt;/p&gt;

&lt;p&gt;这么做可能会很危险，有时你要冒着没有正确设置处理数据的规则这样的风险。让我们回到之前的 Latin-1 字符串来说明这个问题：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;# 数据有正确的 Encoding
puts latin1_resume.encoding.name    # &amp;gt;&amp;gt; ISO-8859-1
puts latin1_resume.bytesize         # &amp;gt;&amp;gt; 6
puts latin1_resume.valid_encoding?  # &amp;gt;&amp;gt; true

# 发生了失误，设置了错误的 Encoding
latin1_resume.force_encoding(&quot;UTF-8&quot;)

# 数据没有改变，但是 Encoding 不一致了
puts latin1_resume.encoding.name    # &amp;gt;&amp;gt; UTF-8
puts latin1_resume.bytesize         # &amp;gt;&amp;gt; 6
puts latin1_resume.valid_encoding?  # &amp;gt;&amp;gt; false

# 当需要使用这些数据时
latin1_resume =~ /\AR/  # !&amp;gt; ArgumentError:
                        #    invalid byte sequence in UTF-8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;注意我是如何使用 &lt;code&gt;force_encoding()&lt;/code&gt; 转换 &lt;code&gt;Encoding&lt;/code&gt;，而没有改变数据的。&lt;code&gt;bytesize()&lt;/code&gt; 相同的返回值可以证明这一点。不过 &lt;code&gt;valid_encoding?()&lt;/code&gt; 告诉我们这些字节却不是合法的 UTF-8 数据。更糟的是，如果我们尝试通过正则表达式使用这些被破坏的数据时会得到错误提示。&lt;/p&gt;

&lt;p&gt;这样的情况致使我们使用另一种方法改变 &lt;code&gt;Encoding&lt;/code&gt;。如果我们有一些附有某个 &lt;code&gt;Encoding&lt;/code&gt; 的合法数据，我们想把这些数据转换成不同的 &lt;code&gt;Encoding&lt;/code&gt;，我们需要转码数据本身。在 Ruby 1.9 中你可以使用 &lt;code&gt;encode()&lt;/code&gt; 方法实现这一过程（或者不生成新的字符串，使用 &lt;code&gt;encode!()&lt;/code&gt; 方法修改源字符串）。&lt;/p&gt;

&lt;p&gt;让我们使用 &lt;code&gt;encode()&lt;/code&gt; 在做一次 Latin-1 到 UTF-8 的转换：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;# 合法的 Latin-1 数据
puts latin1_resume.encoding.name    # &amp;gt;&amp;gt; ISO-8859-1
puts latin1_resume.bytesize         # &amp;gt;&amp;gt; 6
puts latin1_resume.valid_encoding?  # &amp;gt;&amp;gt; true

# 把数据转码到 UTF-8
transcoded_utf8_resume = latin1_resume.encode(&quot;UTF-8&quot;)

# 现在已经正确的转换到 UTF-8 了
puts transcoded_utf8_resume.encoding.name    # &amp;gt;&amp;gt; UTF-8
puts transcoded_utf8_resume.bytesize         # &amp;gt;&amp;gt; 8
puts transcoded_utf8_resume.valid_encoding?  # &amp;gt;&amp;gt; true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;你可以看到这种方法的不同之处在于它既改变了 &lt;code&gt;Encoding&lt;/code&gt; 也改变了数据。实际上数据从旧的 &lt;code&gt;Encoding&lt;/code&gt; 转换到了新的。&lt;/p&gt;

&lt;p&gt;这就让我们有了一些非常简单的规则来决定何时使用哪种方案。如果你比 Ruby 更了解数据，则只需要改变 &lt;code&gt;Encoding&lt;/code&gt;，这时使用 &lt;code&gt;force_encoding()&lt;/code&gt;。遇到这种情况要小心一点，如果设置错误下次使用数据时（也许是和转换 &lt;code&gt;Encoding&lt;/code&gt; 完全不同的操作）会触发一些错误。如果需要把数据从一种 &lt;code&gt;Encoding&lt;/code&gt; 转换到另一种，则要使用 &lt;code&gt;encode()&lt;/code&gt;。&lt;/p&gt;

&lt;h3&gt;对比时要小心&lt;/h3&gt;

&lt;p&gt;不幸的是，对字符串处理方式的改变增加了&lt;a href=&quot;http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/20540&quot;&gt;字符串对比规则&lt;/a&gt;的复杂度。在这我要背道而驰，避免你花费过多的精力去记忆这些新的规则。&lt;/p&gt;

&lt;p&gt;相反地，我觉得从长远来看，总结出一条规则就可以很好的为你服务了。为此我建议：在处理一组字符串时首先把它们标准化为相同的 &lt;code&gt;Encoding&lt;/code&gt;。这种方式对对比和其他的共有操作都可用。&lt;/p&gt;

&lt;p&gt;不过我觉得这很难处理多种不同的数据，也很难推测出在处理的过程中会发生怎样的状况。&lt;/p&gt;

&lt;p&gt;在数据标准化阶段，Ruby 中的编码相容性这一概念可以提供些微的帮助。下面的代码展示了如何检查编码相容性并利用它：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;# 两种不同 Encoding 的数据
p ascii_my                      # &amp;gt;&amp;gt; &quot;My &quot;
puts ascii_my.encoding.name     # &amp;gt;&amp;gt; US-ASCII
p utf8_resume                   # &amp;gt;&amp;gt; &quot;Résumé&quot;
puts utf8_resume.encoding.name  # &amp;gt;&amp;gt; UTF-8

# 检查相容性
p Encoding.compatible?(ascii_my, utf8_resume)  # &amp;gt;&amp;gt; #&amp;lt;Encoding:UTF-8&amp;gt;

# 合并相容的数据
my_resume = ascii_my + utf8_resume
p my_resume                   # &amp;gt;&amp;gt; &quot;My Résumé&quot;
puts my_resume.encoding.name  # &amp;gt;&amp;gt; UTF-8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;这个例子中有两个不同编码的数据，US-ASCII 编码和 UTF-8 编码。我问 Ruby 这两种编码是否相容（&lt;code&gt;compatible()&lt;/code&gt;）？Ruby 对这个问题有两种答复。如果返回 &lt;code&gt;false&lt;/code&gt;，说明不相容，如果要对二者就行操作就要转码其中至少一个数据。如果返回一个 &lt;code&gt;Encoding&lt;/code&gt; 对象说明二者相容，可以进行字符串连接操作，连接后的字符串采用返回值所对应的编码。在我连接这两个字符串时你可以看到具体的操作规则。&lt;/p&gt;

&lt;p&gt;这个功能算是本文介绍最有用的了，将 ASCII 编码连接到更大的编码。更复杂的情况就需要转码了。&lt;/p&gt;

&lt;h3&gt;显式迭代&lt;/h3&gt;

&lt;p&gt;在 Ruby 1.8 中，字符串的 &lt;code&gt;each()&lt;/code&gt; 方法按照行来迭代数据。我以为事情就此结束了，因为这是处理数据的常见方式，不过问题是，基于什么原因使得按行处理是正确的选择？按字节或按字符迭代怎么样？在 Ruby 1.8 中你可以使用 &lt;code&gt;each_byte()&lt;/code&gt; 方法按字节迭代，但是你要依靠正则表达式的一些技巧先获取字符。&lt;/p&gt;

&lt;p&gt;这对 Ruby 1.9 中的所有被编码的数据，只依赖一种类型的迭代就有点说不过去了。字符串的 &lt;code&gt;each()&lt;/code&gt; 方法被移除了，而且字符串也不再是可枚举的了。这可能是对核心 API 做的最大的改变，代码需要改写。&lt;/p&gt;

&lt;p&gt;不过尽管放心，字符串迭代并没有消失。只是现在你需要显示指定你所需的迭代类型，因为你有很多选择：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;utf8_resume.each_byte do |byte|
  puts byte
end
# &amp;gt;&amp;gt; 82
# &amp;gt;&amp;gt; 195
# &amp;gt;&amp;gt; 169
# &amp;gt;&amp;gt; 115
# &amp;gt;&amp;gt; 117
# &amp;gt;&amp;gt; 109
# &amp;gt;&amp;gt; 195
# &amp;gt;&amp;gt; 169

utf8_resume.each_char do |char|
  puts char
end
# &amp;gt;&amp;gt; R
# &amp;gt;&amp;gt; é
# &amp;gt;&amp;gt; s
# &amp;gt;&amp;gt; u
# &amp;gt;&amp;gt; m
# &amp;gt;&amp;gt; é

utf8_resume.each_codepoint do |codepoint|
  puts codepoint
end
# &amp;gt;&amp;gt; 82
# &amp;gt;&amp;gt; 233
# &amp;gt;&amp;gt; 115
# &amp;gt;&amp;gt; 117
# &amp;gt;&amp;gt; 109
# &amp;gt;&amp;gt; 233

utf8_resume.each_line do |line|
  puts line
end
# &amp;gt;&amp;gt; Résumé
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;类似地，如果要使用不同于 &lt;code&gt;each()&lt;/code&gt; 的迭代器，你可以让以上的各迭代方式返回 &lt;code&gt;Enumerator&lt;/code&gt; 对象。上面的几个方法可以通过不指定块来获的 &lt;code&gt;Enumerator&lt;/code&gt; 对象，不过有一些方法时专门为这种用法准备的：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;p utf8_resume.bytes.first(3)
# &amp;gt;&amp;gt; [82, 195, 169]

p utf8_resume.chars.find { |char| char.bytesize &amp;gt; 1 }
# &amp;gt;&amp;gt; &quot;é&quot;

p utf8_resume.codepoints.to_a
# &amp;gt;&amp;gt; [82, 233, 115, 117, 109, 233]

p utf8_resume.lines.map { |line| line.reverse }
# &amp;gt;&amp;gt; [&quot;émuséR&quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;我觉得从长远来看这种改变是好的，我觉得这让代码更具可读性。在我看来这是好事。&lt;/p&gt;

&lt;p&gt;移除 &lt;code&gt;each()&lt;/code&gt; 带来的最大烦恼是需要让代码能够同时运行在 Ruby 1.8 和 Ruby 1.9 中。如果遇到这种情况，你可以选择为 Ruby 1.8 的字符串添加一个方法：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;if RUBY_VERSION &amp;lt; &quot;1.9&quot;
  require &quot;enumerator&quot;
  class String
    def lines
      enum_for(:each)
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;或者选择使用类似下面的技巧：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;str.send(str.respond_to?(:lines) ? :lines : :to_s).each do |line|
  # ...
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a id=&quot;um-h-9&quot;&gt;&lt;/a&gt;
## 9. Ruby 1.9 中的三种默认编码类型&lt;/p&gt;

&lt;p&gt;我怀疑很多 Ruby 用户在初次接触新的多语言引擎时会遇到这样的错误信息：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;invalid multibyte char (US-ASCII)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Ruby 1.8 不太在意字符串字面量中的内容，但是 Ruby 1.9 却很在意。我想你会发现这样的改变是好的，不过我们不需要花费太多的时间去学习这些新的规则。&lt;/p&gt;

&lt;p&gt;让我们先来看看 Ruby 三种默认编码类型的第一种。&lt;/p&gt;

&lt;h3&gt;源码的编码&lt;/h3&gt;

&lt;p&gt;Ruby 中被编码的数据越来越多，每一个字符串都需要一个特定的 &lt;code&gt;Encoding&lt;/code&gt; 对象。也就是说在创建字符串的时候就需要为其选定一个 &lt;code&gt;Encoding&lt;/code&gt;。创建字符串的方法之一是由 Ruby 对字符串字面量执行一些代码，如下所示：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;str = &quot;A new String&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;这是个很简单的字符串，但是如果我使用的是如下的字符串字面量呢？&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;str = &quot;Résumé&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;这时字符串的 &lt;code&gt;Encoding&lt;/code&gt; 对象是什么呢？因为有这样的疑问，所以我们才需要和字符编码做一些纠缠。你不能仅靠视觉来判断字符串的编码。现在如果我给你的只是一些字节，你可能会根据经验做出判断，但是这个数据并没有附属 &lt;code&gt;Encoding&lt;/code&gt; 对象。&lt;/p&gt;

&lt;p&gt;这对我们每天处理的大量数据来说是可能发生的，一个纯文本文件是不可能说明储存其中的诗句采用了何种编码。如果你考虑到了这种情况那么你就会发现一些有意思的事情。&lt;/p&gt;

&lt;p&gt;涉及到程序源代码时情况可能更糟。我想以 UTF-8 书写我的代码，可是一些日本的程序员却想使用 Shift JIS。Ruby 应该支持为这种情况提供支持，事实也是如此，在 1.9 中提供了支持。让我们再看看更复杂的情况：试想我用 UTF-8 编写了一个 gem，然后一个日本的程序员想在他使用的 Shift JIS 代码中使用这个 gem，我们怎样将这两个程序无缝整合呢？&lt;/p&gt;

&lt;p&gt;Ruby 1.8 中那个神奇的变量在此没有多大的作用，是时候还一种解决方案了。Ruby 1.9 为此提供的解决办法是“源码的编码”。&lt;/p&gt;

&lt;p&gt;现在所有的 Ruby 源码都有一个 &lt;code&gt;Encoding&lt;/code&gt; 对象，当你在源码中创建字符串时，源码的 &lt;code&gt;Encoding&lt;/code&gt; 赋予了这个字符串。这样简单的方法很好的解决了上述的问题。只要我的源码是 UTF-8 编码、日本程序员的源码编码是 Shift JIS，我的字面量按照我的期望处理着，他的字面量按照他的期望处理着。很显然，如果要共用数据，我们就要通过文档或是代码来建立一些规则，以适应不同的编码，做到这一点就可以解决问题了。&lt;/p&gt;

&lt;p&gt;因此现在唯一的问题就是，源代码的编码是什么？我怎样改变它？&lt;/p&gt;

&lt;p&gt;有很多中方法可以为源代码指定一个 &lt;code&gt;Encoding&lt;/code&gt; 对象。详情如下：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat no_encoding.rb
p __ENCODING__
$ ruby no_encoding.rb
#&amp;lt;Encoding:US-ASCII&amp;gt;

$ cat magic_comment.rb
# encoding: UTF-8
p __ENCODING__
$ ruby magic_comment.rb
#&amp;lt;Encoding:UTF-8&amp;gt;
$ cat magic_comment2.rb
#!/usr/bin/env ruby -w
# encoding: UTF-8
p __ENCODING__
$ ruby magic_comment2.rb
#&amp;lt;Encoding:UTF-8&amp;gt;

$ echo $LC_CTYPE
en_US.UTF-8
$ ruby -e &#39;p __ENCODING__&#39;
#&amp;lt;Encoding:UTF-8&amp;gt;

$ ruby -KU no_encoding.rb
#&amp;lt;Encoding:UTF-8&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;上述代码的第一段说明了两个很重要的事情。第一，源码的编码一条主要的规则就是：如果不指定，默认使用 US-ASCII。这就是本节开头很多人遇到错误的原因，如果在没有改变源码的编码的情况下，在字符串自变量中包含任何的非 ASCII 数据，Ruby 都会以一个错误终止程序运行。所以如果要处理非 ASCII 数据就要改变源码的编码。第二个重要的事情是，一个新的 &lt;code&gt;__ENCODING__&lt;/code&gt; 关键字可以返回当前运行代码的源码编码。&lt;/p&gt;

&lt;p&gt;上述代码的第二段展示了设定源码编码的一种推荐方法，叫做“神奇注释”。如果源码文件的第一行是一个注释，其内容是一个单词 &lt;code&gt;encoding&lt;/code&gt;，跟着一个冒号和一个空格，然后是一个 &lt;code&gt;Encoding&lt;/code&gt; 对象名称，这个源码文件的编码就被设为这个指定的 &lt;code&gt;Encoding&lt;/code&gt; 了。如果文件包含 Shebang，这个“神奇注释”就必须在第二行，这两行之间不许有空格。一旦设置了 &lt;code&gt;Encoding&lt;/code&gt;，这个文件中所有的字符串字面量就都被赋予这种编码了。&lt;/p&gt;

&lt;p&gt;上述代码的第三段是为了方便而设置的一个特殊规则。如果在命令行使用 &lt;code&gt;-e&lt;/code&gt; 开关执行 Ruby 代码，命令行会从所处环境获取源码的编码。我是将 &lt;code&gt;LC_CTYPE&lt;/code&gt; 环境变量设置为 UTF-8，不过有些人也会设置 &lt;code&gt;LANG&lt;/code&gt; 变量实现同样的效果。这让脚本的执行更顺利，Ruby 会自动匹配串联命令的编码。&lt;/p&gt;

&lt;p&gt;上述代码的第四段又是一个很有趣的特例。Ruby 1.9 仍然支持来自 Ruby 1.8 的 &lt;code&gt;-K*&lt;/code&gt; 形式开关，包括本文大量使用的 &lt;a href=&quot;http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/19552&quot;&gt;-KU 开关&lt;/a&gt;。这种开关会产生很多影响，不过只需注意一点，这是除“神奇注释”之外唯一一种改变源码编码的方法。这对向前兼容性是个好消息，多亏了这个方法 Ruby 1.8 的代码才可能不产生任何的编码问题而运行在 Ruby 1.9 中。不过我必须强调一点，这种方法的存在只是为了向前兼容性，“神奇注释”才是王道。&lt;/p&gt;

&lt;p&gt;通过“神奇注释”，源码有了 &lt;code&gt;Encoding&lt;/code&gt; 数据。不过为每个源码文件都加上这样的注释似乎有点多余，好像也没发生太大的变化，因为之前我们已经建议你在文件的第一行加入这行 Shebang 了：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;#!/usr/bin/env ruby -wKU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;现在，对于 Ruby 1.9 我们则推荐使用下面的代码：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;#!/usr/bin/env ruby -w
# encoding: UTF-8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;注意，“神奇注释”的格式很松散，以下的所有形式效果都一样：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;# encoding: UTF-8

# coding: UTF-8

# -*- coding: UTF-8 -*-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;其中一些形式可以被文本编辑器读取使用。&lt;/p&gt;

&lt;p&gt;如果我们习惯添加这个“神奇注释”，不过我们喜好哪种编码，我们的代码都能够很好的结合在一起运行。Ruby 知道怎样处理不同的文件。这么做带来一个额外的好处，作为程序员的我们也能够看到这个注释，可以帮助我们更好的理解所使用的代码。我觉得这样的好习惯值得拥有。&lt;/p&gt;

&lt;h3&gt;默认的外部编码和内部编码&lt;/h3&gt;

&lt;p&gt;字符串经常还可以通过另外一种方法创建：从 &lt;code&gt;IO&lt;/code&gt; 对象读取。这时为字符串设定源码编码就不合理了，因为这是外部数据和源码无关。但是你还是需要知道数据的编码方式才能正确的读取它们，甚至对于读取下一行这样简单的操作也会因为 UTF-8 和 UTF-16LE（LE 的意思是小字节序）两种编码的不同而发生变化。因此，&lt;code&gt;IO&lt;/code&gt; 对象至少要附着一种 &lt;code&gt;Encoding&lt;/code&gt;。Ruby 1.9 很大方，为此它提供了二种编码：外部编码和内部编码。&lt;/p&gt;

&lt;p&gt;外部编码是数据在 &lt;code&gt;IO&lt;/code&gt; 对象内所采用的编码。外部编码影响数据的读取；如果内部编码没有设定的话，返回的数据也会采用外部编码进行编码（稍后详细说明）。下面举例说明：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat show_external.rb
open(__FILE__, &quot;r:UTF-8&quot;) do |file|
  puts file.external_encoding.name
  p    file.internal_encoding
  file.each do |line|
    p [line.encoding.name, line]
  end
end

$ ruby show_external.rb
UTF-8
nil
[&quot;UTF-8&quot;, &quot;open(__FILE__, \&quot;r:UTF-8\&quot;) do |file|\n&quot;]
[&quot;UTF-8&quot;, &quot;  puts file.external_encoding.name\n&quot;]
[&quot;UTF-8&quot;, &quot;  p    file.internal_encoding\n&quot;]
[&quot;UTF-8&quot;, &quot;  file.each do |line|\n&quot;]
[&quot;UTF-8&quot;, &quot;    p [line.encoding.name, line]\n&quot;]
[&quot;UTF-8&quot;, &quot;  end\n&quot;]
[&quot;UTF-8&quot;, &quot;end\n&quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;上例有四点需要注意：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;在设置打开文件的模式时通过后面的 &lt;code&gt;UTF-8&lt;/code&gt; 设定了外部编码&lt;/li&gt;
  &lt;li&gt;如上所示，可以使用 &lt;code&gt;external_encoding()&lt;/code&gt; 查看外部编码&lt;/li&gt;
  &lt;li&gt;类似的 &lt;code&gt;internal_encoding()&lt;/code&gt; 返回内部编码值，如果没有显式设定的话就是 &lt;code&gt;nil&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;留意我指定了外部编码后所生成的字符串&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;内部编码的出现增加了问题的复杂度。如果设置了内部编码，数据还是以外部编码读取，但是在创建字符串时会将其转码到内部编码。这位程序员带来了便利。来看看到底发生了什么变化：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat show_internal.rb
open(__FILE__, &quot;r:UTF-8:UTF-16LE&quot;) do |file|
  puts file.external_encoding.name
  puts file.internal_encoding.name
  file.each do |line|
    p [line.encoding.name, line[0..3]]
  end
end

$ ruby show_internal.rb
UTF-8
UTF-16LE
[&quot;UTF-16LE&quot;, &quot;o\x00p\x00e\x00n\x00&quot;]
[&quot;UTF-16LE&quot;, &quot; \x00 \x00p\x00u\x00&quot;]
[&quot;UTF-16LE&quot;, &quot; \x00 \x00p\x00u\x00&quot;]
[&quot;UTF-16LE&quot;, &quot; \x00 \x00f\x00i\x00&quot;]
[&quot;UTF-16LE&quot;, &quot; \x00 \x00 \x00 \x00&quot;]
[&quot;UTF-16LE&quot;, &quot; \x00 \x00e\x00n\x00&quot;]
[&quot;UTF-16LE&quot;, &quot;e\x00n\x00d\x00\n\x00&quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;这个示例就有些不同之处了：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;文件打开模式的第二个编码（这里的 &lt;code&gt;UTF-16LE&lt;/code&gt;）设置内部编码，如第二个 &lt;code&gt;puts&lt;/code&gt; 所示&lt;/li&gt;
  &lt;li&gt;这个变化让 Ruby 对所有数据进行了转换（我截取了输出结果的一部分，因为 UTF-16LE 的数据很复杂）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在写模式下外部编码以相同的方式工作。外部编码仍然表示 &lt;code&gt;IO&lt;/code&gt; 对象的编码，或者表示写入完成后的数据编码。不过这时你不必指定一个内部编码了，Ruby 会自动将输出字符串的编码设为内部编码，然后如果需要的话会将数据转码到外部编码。例如：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat write_internal.rb
# encoding: UTF-8
open(&quot;data.txt&quot;, &quot;w:UTF-16LE&quot;) do |file|
  puts file.external_encoding.name
  p    file.internal_encoding
  data = &quot;My data…&quot;
  p [data.encoding.name, data]
  file &amp;lt;&amp;lt; data
end
p File.read(&quot;data.txt&quot;)

$ ruby write_internal.rb
UTF-16LE
nil
[&quot;UTF-8&quot;, &quot;My data…&quot;]
&quot;M\x00y\x00 \x00d\x00a\x00t\x00a\x00&amp;amp; &quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;留意一下在内部编码为 &lt;code&gt;nil&lt;/code&gt; 的情况下，数据在被写入之前是如何进行转码的。Ruby 使用字符串的编码决定所需的是什么编码。&lt;/p&gt;

&lt;p&gt;这两个 &lt;code&gt;IO&lt;/code&gt; 相关的编码都很直观明了，但还是留下了一个问题：如果不设置它们会发生什么？答案是，如果任何一个没有设置，&lt;code&gt;IO&lt;/code&gt; 会集成默认的外部编码和（或）内部编码。现在需要知道的就是 Ruby 是如何选择默认值的。&lt;/p&gt;

&lt;p&gt;默认的外部编码从环境中获取，类似于通过命令行设定源码编码的方式：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ echo $LC_CTYPE
en_US.UTF-8

$ ruby -e &#39;puts Encoding.default_external.name&#39;
UTF-8
$ LC_CTYPE=ja_JP.sjis ruby -e &#39;puts Encoding.default_external.name&#39;
Shift_JIS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;默认的内部编码则是 &lt;code&gt;nil&lt;/code&gt;。你需要主动修改去获得其他的编码方式。&lt;/p&gt;

&lt;p&gt;这两个 &lt;code&gt;IO&lt;/code&gt; 相关的编码各自有一个全局性的设置方法：&lt;code&gt;Encoding.default_external=()&lt;/code&gt; 和 &lt;code&gt;Encoding.default_internal=()&lt;/code&gt;。你可以把它们设为 &lt;code&gt;Encoding&lt;/code&gt; 对象或者是所对应的名称字符串。&lt;/p&gt;

&lt;p&gt;你可以通过命令行开关来改变这两个编码的值。&lt;code&gt;-E&lt;/code&gt; 开关可以同时设置这两个编码或者只设置其中一个：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -e &#39;p [Encoding.default_external, Encoding.default_internal]&#39;
[#&amp;lt;Encoding:UTF-8&amp;gt;, nil]

$ ruby -E Shift_JIS \
&amp;gt; -e &#39;p [Encoding.default_external, Encoding.default_internal]&#39;
[#&amp;lt;Encoding:Shift_JIS&amp;gt;, nil]

$ ruby -E :UTF-16LE \
&amp;gt; -e &#39;p [Encoding.default_external, Encoding.default_internal]&#39;
[#&amp;lt;Encoding:UTF-8&amp;gt;, #&amp;lt;Encoding:UTF-16LE&amp;gt;]

$ ruby -E Shift_JIS:UTF-16LE \
&amp;gt; -e &#39;p [Encoding.default_external, Encoding.default_internal]&#39;
[#&amp;lt;Encoding:Shift_JIS&amp;gt;, #&amp;lt;Encoding:UTF-16LE&amp;gt;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;如果所见，这个开关接受的参数形式就和 &lt;code&gt;File.open()&lt;/code&gt; 方法的读写模式字符串一样。&lt;/p&gt;

&lt;p&gt;有一个更简便的命令行开关可以设置在任何地方都使用 UTF-8，这个开关是 &lt;code&gt;-U&lt;/code&gt;，它把默认的内部编码设为 UTF-8。这样设置后你只需为 &lt;code&gt;IO&lt;/code&gt; 对象设置外部编码，或干脆从环境中获取外部编码，你所读取的所有字符串就会被转码到 UTF-8。&lt;/p&gt;

&lt;p&gt;关于默认的外部编码和内部编码有一点很重要，你应该把这二者视为对脚本的便捷操作方式。如果你可以掌控代码运行的场所，从环境或命令行开关获取编码会更为便利，但是对于希望其他人运行的代码来说就要更显式的指定编码。如果有疑虑，那就为 &lt;code&gt;IO&lt;/code&gt; 对象设置你期望的外部编码和内部编码。这样做很繁琐，但是更安全，不会被一些外部力量悄悄的修改。还要记住，这两个编码的设置是全局性的，会对所有加载的代码产生影响，包裹从外部加载（&lt;code&gt;require()&lt;/code&gt;）的代码库。这么做既是福也是祸，所以当你有疑惑的时候记得要思考这个问题：字符串的编码从何而来？&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;um-h-10&quot;&gt;&lt;/a&gt;
## 10. 多语言的其他细节&lt;/p&gt;

&lt;p&gt;我们已经讨论了 Ruby 1.9 中多语言引擎的核心事项，在字符串和 IO 上你可以看到变化所在。不过这个新的多语言系统很强大，还设置一些其他的细节。让我们来讨论一下在 Ruby 1.9 中处理字符编码会涉及到的周边话题。&lt;/p&gt;

&lt;h3&gt;编码类的更多特性&lt;/h3&gt;

&lt;p&gt;在解释多语言特性时我大量的使用了 &lt;code&gt;Encoding&lt;/code&gt; 对象，但是还没有仔细讨论过这个对象。&lt;code&gt;Encoding&lt;/code&gt; 很简单，基本上只是表示了 Ruby 中的编码名称。所以，&lt;code&gt;Encoding&lt;/code&gt; 对象存储了一些工具，在处理编码问题时会很有用。&lt;/p&gt;

&lt;p&gt;首先，你可以获得一个数组列表（&lt;code&gt;list()&lt;/code&gt;）包含所有在 Ruby 中加载的 &lt;code&gt;Encoding&lt;/code&gt; 对象：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -e &#39;puts Encoding.list.first(3), &quot;...&quot;&#39;
ASCII-8BIT
UTF-8
US-ASCII
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;如果你只对某个特定的 &lt;code&gt;Encoding&lt;/code&gt; 对象感兴趣，你可以通过名称找到它（&lt;code&gt;find()&lt;/code&gt;）：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -e &#39;p Encoding.find(&quot;UTF-8&quot;)&#39;
#&amp;lt;Encoding:UTF-8&amp;gt;

$ ruby -e &#39;p Encoding.find(&quot;No-Such-Encoding&quot;)&#39;
-e:1:in `find&#39;: unknown encoding name - No-Such-Encoding (ArgumentError)
    from -e:1:in `&amp;lt;main&amp;gt;&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;如果所见，如果 Ruby 不知道某个 &lt;code&gt;Encoding&lt;/code&gt; 对象就会抛出一个 &lt;code&gt;ArgumentError&lt;/code&gt; 异常。&lt;/p&gt;

&lt;p&gt;有些 &lt;code&gt;Encoding&lt;/code&gt; 对象的名称不止一个，这些别名可以替换着使用，但是指代的是同一个 &lt;code&gt;Encoding&lt;/code&gt; 对象。例如，ASCII 有个别名叫 US-ASCII：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -e &#39;puts Encoding.aliases[&quot;ASCII&quot;]&#39;
US-ASCII
$ ruby -e &#39;p Encoding.find(&quot;ASCII&quot;) == Encoding.find(&quot;US-ASCII&quot;)&#39;
true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;aliases()&lt;/code&gt; 方法返回一个 Hash，通过键可以获取 Ruby 知道的别名，返回的结果是这个别名所指代的编码。在调用像 &lt;code&gt;Encoding.find()&lt;/code&gt; 或 &lt;code&gt;IO.open()&lt;/code&gt; 这样的方法时，在指定 &lt;code&gt;Encoding&lt;/code&gt; 对象时可以使用原名或别名。&lt;/p&gt;

&lt;p&gt;最后，如果你要编写一些支持更大范围编码的 Ruby 代码时，要了解一个会发生问题的地方。Ruby 支持的编码中，有一些还没有完全实现字符处理方式的“空壳编码”，这些空壳编码是用来声称编码支持完整性的。在支持更多的编码时你要将这些空格编码剔除，避免发生问题：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -e &#39;puts &quot;Dummy Encodings:&quot;, Encoding.list.select(&amp;amp;:dummy?).map(&amp;amp;:name)&#39;
Dummy Encodings:
ISO-2022-JP
ISO-2022-JP-2
UTF-7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;字符串转义&lt;/h3&gt;

&lt;p&gt;在 Ruby 1.8 中你可能会使用字节转义将原始字节插入字符串。例如，你可以通过下面的字节转义生成字符串“…”：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -v -KU -e &#39;p &quot;\xe2\x80\xa6&quot;&#39;
ruby 1.8.6 (2009-03-31 patchlevel 368) [i686-darwin9.6.0]
&quot;…&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;在 Ruby 1.9 中情况依旧，但是要记住，前面已经说过，即使是原始字节，其上仍然会附属 &lt;code&gt;Encoding&lt;/code&gt; 对象：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat utf8_escapes.rb
# encoding: UTF-8
str = &quot;\xe2\x80\xa6&quot;
p [str.encoding, str, str.valid_encoding?]

$ ruby -v utf8_escapes.rb
ruby 1.9.1p0 (2009-01-30 revision 21907) [i386-darwin9.6.0]
[#&amp;lt;Encoding:UTF-8&amp;gt;, &quot;…&quot;, true]

$ cat invalid_escapes.rb
# encoding: UTF-8
str = &quot;\xe2\x80&quot;
p [str.encoding, str, str.valid_encoding?]

$ ruby -v invalid_escapes.rb
ruby 1.9.1p0 (2009-01-30 revision 21907) [i386-darwin9.6.0]
[#&amp;lt;Encoding:UTF-8&amp;gt;, &quot;\xE2\x80&quot;, false]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;我实验了两种情况，不过两种情况下都正常的将源码编码赋予字符串了。第一种情况生成了一个合法的 UTF-8 字符串；但是第二中情况中的字符串确实不合法的，如果尝试使用的话可能会产生错误。&lt;/p&gt;

&lt;p&gt;不过有些特例情况字符串转义真的会改变字面量到编码。你还记得“如果不改变源码编码就不同使用多字节字符”吧：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat bad_code.rb
&quot;abc…&quot;

$ ruby -v bad_code.rb
ruby 1.9.1p0 (2009-01-30 revision 21907) [i386-darwin9.6.0]
bad_code.rb:1: invalid multibyte char (US-ASCII)
bad_code.rb:1: invalid multibyte char (US-ASCII)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;但是，&lt;code&gt;\x##&lt;/code&gt; 转义确实个特例：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat ascii_escapes.rb
puts &quot;Source Encoding:  #{__ENCODING__}&quot;
str = &quot;abc\xe2\x80\xa6&quot;
p [str.encoding, str, str.valid_encoding?]

$ ruby -v ascii_escapes.rb
ruby 1.9.1p0 (2009-01-30 revision 21907) [i386-darwin9.6.0]
Source Encoding:  US-ASCII
[#&amp;lt;Encoding:ASCII-8BIT&amp;gt;, &quot;abc\xE2\x80\xA6&quot;, true]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;注意，为了容纳这些字节，字符串的编码升级到了 ASCII-8BIT。在本节的后面内容中会继续讨论这个特殊的编码。现在只需注意这样一个事实：这种特例为二进制数据的处理提供了一种简单的方法。&lt;/p&gt;

&lt;p&gt;八进制转义（&lt;code&gt;###&lt;/code&gt;）、控制符转义（&lt;code&gt;\cx&lt;/code&gt; 或 &lt;code&gt;\C-x&lt;/code&gt;）、元转义（&lt;code&gt;\M-x&lt;/code&gt;）和元控制符转义（&lt;code&gt;\M-C-x&lt;/code&gt;）与十六进制转义（&lt;code&gt;\x##&lt;/code&gt;）一样都遵循这里说明的处理方式。&lt;/p&gt;

&lt;p&gt;另外一个特例是通过码位输入 Unicode 字符的 &lt;code&gt;\u####&lt;/code&gt; 转义。当你使用这种形式的转义时，不过源码编码是什么，这个字符串的编码都是 UTF-8：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat ascii_u_escape.rb
str = &quot;\u2026&quot;
p [str.encoding, str]

$ ruby -v ascii_u_escape.rb
ruby 1.9.1p0 (2009-01-30 revision 21907) [i386-darwin9.6.0]
[#&amp;lt;Encoding:UTF-8&amp;gt;, &quot;…&quot;]

$ cat sjis_u_escape.rb
# encoding: Shift_JIS
str = &quot;\u2026&quot;
p [str.encoding, str]

$ ruby -v sjis_u_escape.rb
ruby 1.9.1p0 (2009-01-30 revision 21907) [i386-darwin9.6.0]
[#&amp;lt;Encoding:UTF-8&amp;gt;, &quot;…&quot;]

$ cat utf8_u_escape.rb
# encoding: UTF-8
str = &quot;\u2026&quot;
p [str.encoding, str]

$ ruby -v utf8_u_escape.rb
ruby 1.9.1p0 (2009-01-30 revision 21907) [i386-darwin9.6.0]
[#&amp;lt;Encoding:UTF-8&amp;gt;, &quot;…&quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;注意上述代码中的字符串，不过源码文件使用哪种编码，三种情况下字符串的编码都是 UTF-8。这个特例为处理 UTF-8 数据提供一种简单的方法，完全不用考虑内置的编码问题。&lt;/p&gt;

&lt;p&gt;如上所示，Unicode 转义可以只包含 4 个数字，也可以使用 &lt;code&gt;\u{#...}&lt;/code&gt; 的形式，在花括号内使用 1 个到 6 个数字。这两种形式对字符串的编码效果一样。&lt;/p&gt;

&lt;h3&gt;处理二进制数据&lt;/h3&gt;

&lt;p&gt;不是所有的数据都是文本的形式，Ruby 的字符串类可以用来吃力原始的字节码。例如你可能需要处理 PNG 格式图片的原始字节码。&lt;/p&gt;

&lt;p&gt;Ruby 1.9 为这种情况提供了一种编码，这种编码单纯的把数据看做原始的字节码。你可以把它看成关闭了字符处理而只处理字节的方法。&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat raw_bytes.rb
# encoding: UTF-8
str = &quot;Résumé&quot;
def str.inspect
  { data:     dup,
    encoding: encoding.name,
    chars:    size,
    bytes:    bytesize }.inspect
end
p str
str.force_encoding(&quot;BINARY&quot;)
p str

$ ruby raw_bytes.rb
{:data=&amp;gt;&quot;Résumé&quot;, :encoding=&amp;gt;&quot;UTF-8&quot;, :chars=&amp;gt;6, :bytes=&amp;gt;8}
{:data=&amp;gt;&quot;R\xC3\xA9sum\xC3\xA9&quot;, :encoding=&amp;gt;&quot;ASCII-8BIT&quot;, :chars=&amp;gt;8, :bytes=&amp;gt;8}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;注意，转换了编码（没改变数据本身）后，Ruby 就没有了字符的概念，字符的长度和字节的长度是一样的，在检视字符串时给出的结果是原始的字节码。&lt;/p&gt;

&lt;p&gt;如果你认为这种编码叫做 BINARY 的话也算正确，在上述代码中我就用了这个名称，它是一个别名。在监视字符串的输出中 Ruby 就使用了真正名称，Ruby 实际上称这中编码为 ASCII-8BIT，不过这也带来了一些麻烦。&lt;/p&gt;

&lt;p&gt;显然，在 Ruby 之外没有叫做 ASCII-8BIT 的事物。即使是处理二进制的数据，检查是否保护 ASCII 数据片段的做法也是不常见的。例如，PNG 图片签名的头几个字节就包含一个完整的 ASCII 字符串“PNG”：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat png_sig.rb
sig = &quot;\x89PNG\r\n\C-z\n&quot;
png = /\A.PNG/

p({sig =&amp;gt; sig.encoding.name, png =&amp;gt; png.encoding.name})

if sig =~ png
  puts &quot;This data looks like a PNG image.&quot;
end

$ ruby png_sig.rb
{&quot;\x89PNG\r\n\x1A\n&quot;=&amp;gt;&quot;ASCII-8BIT&quot;, /\A.PNG/=&amp;gt;&quot;US-ASCII&quot;}
This data looks like a PNG image.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Ruby 让 ASCII-8BIT 和 US-ASCII 相兼容来让事情变的合理，经过这样的处理，在上述的代码中我们就可以使用一个简单的 US-ASCII 正则表达式来验证 PNG 签名了。因此，ASCII-8BIT 的意思是 ASCII 外加一些其他的字节，如果有助于数据的处理你可以将其中部分数据视作 ASCII。&lt;/p&gt;

&lt;p&gt;还有一点值得注意，如果以字节模式读取数据，Ruby 会将编码回滚到 ASCII-8BIT：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat binary_fallback.rb
open(&quot;ascii.txt&quot;, &quot;w+:UTF-8&quot;) do |f|
  f.puts &quot;abc&quot;
  f.rewind
  str = f.read(2)
  p [str.encoding.name, str]
end

$ ruby binary_fallback.rb
[&quot;ASCII-8BIT&quot;, &quot;ab&quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;这种处理方式是合理的，因为在字节模式下读取，你可以截断字符。如果你确实需要读取一些字节，但是不想改变编码，你需要手动设置并检校。你可以通过类似下面的方式实现：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat read_to_char.rb
# encoding: UTF-8
open(&quot;ascii.txt&quot;, &quot;w+:UTF-8&quot;) do |f|
  f.puts &quot;Résumé&quot;
  f.rewind
  str = f.read(2)
  until str.dup.force_encoding(f.external_encoding).valid_encoding?
    str &amp;lt;&amp;lt; f.read(1)
  end
  str.force_encoding(f.external_encoding)
  p [str.encoding.name, str]
end

$ ruby read_to_char.rb
[&quot;UTF-8&quot;, &quot;Ré&quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;在上述的例子中，我先读取（&lt;code&gt;read()&lt;/code&gt;）了固定长度的字节，接着一个字节一个字节的读取，直到数据能够满足设定的编码为止。我复制（&lt;code&gt;dup()&lt;/code&gt;）了原来的数据，然后在确定读取完成之后进行了强制转换编码（&lt;code&gt;force_encoding()&lt;/code&gt;），因为 UTF-8 和 ASCII-8BIT 是不兼容的，所以在这个过程中可能会抛出 &lt;code&gt;Encoding::CompatibilityError&lt;/code&gt; 异常。&lt;/p&gt;

&lt;p&gt;处理二进制数据还需要你了解 &lt;code&gt;IO&lt;/code&gt; 对象的另一个情况，在 Windows 系统中，Ruby 会转换一些你读取的数据，转换的内容很简单：从 &lt;code&gt;IO&lt;/code&gt; 对象中读取的 &lt;code&gt;\r\n&lt;/code&gt; 会变成单一的 &lt;code&gt;\n&lt;/code&gt;。这个功能可以让 Unix 上的脚本顺利的在具有不同行尾形式的平台上运行。这样做会带来一些额外的工作量：在读取非文本数据时，比如说二进制数据或像 UTF-16 这样和 ASCII 不兼容的编码，为了保证能够夸平台执行，你要提醒 Ruby 不要做这样的转换。&lt;/p&gt;

&lt;p&gt;顺便提醒一下，这个功能不是新的，在 Ruby 1.8 中就有。&lt;/p&gt;

&lt;p&gt;告知 Ruby 将数据视为二进制，而且不要做任何的转换（只对 Windows 平台）是很简单的。在调用 &lt;code&gt;open()&lt;/code&gt; 是在操作模式后面添加一个 &lt;code&gt;b&lt;/code&gt;（表示二进制） 就可以了。那么你就可以像这样读取数据：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;open(path, &quot;rb&quot;) do |f|
  # ...
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;或者这样：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;open(path, &quot;wb&quot;) do |f|
  # ...
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;如果你知道这种诡异的行为，也按照上面的方式处理了，那就给自己一个口头表扬吧，你做的很好。如果你没有这么做，那就要摒弃这种坏习惯，不过也无需灰心。我在使用 Perl 时就知道这个诡异的行为了（Perl 和 Ruby 的情况一样），然后就一直尽量按照正确的方式处理，但是最近在我的一个代码库中发现了十个不同的 bug，就是因为我没有添加 &lt;code&gt;b&lt;/code&gt;，这太容易被遗忘了。&lt;/p&gt;

&lt;p&gt;Ruby 1.9 对二进制标签有更严格的规则，如果 Ruby 认为需要而你没有提供这个标签的话它会发出一些抱怨。例如：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat missing_b.rb
# Ruby 1.9 会让这个通过
open(&quot;utf_16.txt&quot;, &quot;w:UTF-16LE&quot;) do |f|
  f.puts &quot;Some data.&quot;
end
# 但这个无法通过
open(&quot;utf_16.txt&quot;, &quot;r:UTF-16LE&quot;) do |f|
  # ...
end

$ ruby missing_b.rb
missing_b.rb:6:in `initialize&#39;: ASCII incompatible encoding needs binmode
                                (ArgumentError)
    from missing_b.rb:6:in `open&#39;
    from missing_b.rb:6:in `&amp;lt;main&amp;gt;&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;当然这很容易修复，把 &lt;code&gt;b&lt;/code&gt; 添加上去就行了：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat with_b.rb
open(&quot;utf_16.txt&quot;, &quot;wb:UTF-16LE&quot;) do |f|
  f.puts &quot;Some data.&quot;
end
open(&quot;utf_16.txt&quot;, &quot;rb:UTF-16LE&quot;) do |f|
  puts f.external_encoding.name
end

$ ruby with_b.rb
UTF-16LE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;上述代码片段中我用 &lt;code&gt;external_encoding()&lt;/code&gt; 的结果来说明这就是我指定的编码。不过将这个过去丢掉的 &lt;code&gt;b&lt;/code&gt; 加入会产生一个副作用，添加 &lt;code&gt;b&lt;/code&gt; 后 Ruby 会认为你想要的外部编码是 ASCII-8BIT 而不是默认的外部编码：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat b_means_binary.rb
open(&quot;utf_16.txt&quot;, &quot;r&quot;) do |f|
  puts &quot;Inherited from environment:  #{f.external_encoding.name}&quot;
end
open(&quot;utf_16.txt&quot;, &quot;rb&quot;) do |f|
  puts %Q{Using &quot;rb&quot;:  #{f.external_encoding.name}}
end

$ ruby b_means_binary.rb
Inherited from environment:  UTF-8
Using &quot;rb&quot;:  ASCII-8BIT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;有个地方需要注意一下，Ruby 1.8 无形中让我们习惯了不使用 &lt;code&gt;b&lt;/code&gt;。例如，你可以使用 &lt;code&gt;IO::read()&lt;/code&gt; 读取一些数据，但这个方法没有办法指明数据是二进制的。所以你就需要一个更强大的办法实现夸平台支持：&lt;code&gt;open(path, &quot;rb&quot;) { |f| f.read }&lt;/code&gt;。不过 &lt;code&gt;IO::read()&lt;/code&gt; 却很常用。&lt;code&gt;IO::readlines()&lt;/code&gt; 和 &lt;code&gt;IO::foreach()&lt;/code&gt; 有同样的问题。Ruby 核心开发团队承认了这一点并了改进，现在你可以为所有打开 &lt;code&gt;IO&lt;/code&gt; 的方法添加一个 Hash 类型的参数，可以设置 &lt;code&gt;:mode&lt;/code&gt;，或者分别设置 &lt;code&gt;:external_encoding&lt;/code&gt; 和 &lt;code&gt;:internal_encoding&lt;/code&gt;，还可以设置 &lt;code&gt;:binmode&lt;/code&gt;。下面是一些例子：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;File.read(&quot;utf_16.txt&quot;, mode: &quot;rb:UTF-16LE&quot;)

File.readlines(&quot;utf_16.txt&quot;, mode: &quot;rb:UTF-16LE&quot;)

File.foreach(&quot;utf_16.txt&quot;, mode: &quot;rb:UTF-16LE&quot;) do |line|

end

File.open(&quot;utf_16.txt&quot;, mode: &quot;rb:UTF-16LE&quot;) do |f|

end

open(&quot;utf_16.txt&quot;, mode: &quot;rb:UTF-16LE&quot;) do |f|

end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;还有一个较为快捷的方式，直接使用新的 &lt;code&gt;IO::binread()&lt;/code&gt; 方法，它和 &lt;code&gt;IO.read(..., mode: &quot;rb:ASCII-8BIT&quot;)&lt;/code&gt; 作用一样。&lt;/p&gt;

&lt;h3&gt;正则表达式的编码&lt;/h3&gt;

&lt;p&gt;现在所有的数据都有了编码，所以为正则表达式也附属编码就在情理之中了。我们确实这么做了，但是正则表达式如何选择编码的方式有点不一样。让我们来看一下具体是怎么回事。&lt;/p&gt;

&lt;p&gt;先看一下会让我们惊讶的例子：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat re_encoding.rb
# encoding: UTF-8
utf8_str   = &quot;résumé&quot;
latin1_str = utf8_str.encode(&quot;ISO-8859-1&quot;)
binary_str = utf8_str.dup.force_encoding(&quot;ASCII-8BIT&quot;)
utf16_str  = utf8_str.encode(&quot;UTF-16BE&quot;)

re = /\Ar.sum.\z/
puts &quot;Regexp.encoding.name:  #{re.encoding.name}&quot;

[utf8_str, latin1_str, binary_str, utf16_str].each do |str|
  begin
    result = str =~ re ? &quot;Matches&quot; : &quot;Doesn&#39;t match&quot;
  rescue Encoding::CompatibilityError
    result = &quot;Can&#39;t match non-ASCII compatible?() Encoding&quot;
  end
  puts &quot;#{result}:  #{str.encoding.name}&quot;
end

$ ruby re_encoding.rb
Regexp.encoding.name:  US-ASCII
Matches:  UTF-8
Matches:  ISO-8859-1
Doesn&#39;t match:  ASCII-8BIT
Can&#39;t match non-ASCII compatible?() Encoding:  UTF-16BE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;前面讨论的源码编码在这一刻失效了，如你所见这里的正则表达式的编码是 US-ASCII 而不是 UTF-8。这初看可能觉得不可思议，但是这么做却是有很靠谱的原因的。&lt;/p&gt;

&lt;p&gt;上述代码片段中的正则表达式字面量只包含了七位长度的 ASCII 字符，所以 Ruby 选择了更为简单的编码。如果将其设为 UTF-8，对处理 UTF-8 数据可能有点用，但是现在它却可以处理任何和 ASCII 兼容的数据。从输出你可以看到，这个正则表达式试图处理了三种不同编码的字符串，因为这些字符串的编码和 ASCII 兼容。（有一个失败了，因为我改变了处理数据的方式，把一个字符变成了两个字节，但是正则表达式还是做了努力。）第四个没有进行尝试，因为 UTF-16 和 ASCII 是不兼容的。&lt;/p&gt;

&lt;p&gt;当然，如果正则表达式包含八位长度字符的话，你可以使用特殊的转义来改变编码，或者利用 Ruby 1.8 形式的编码选项，然后就可以得到一个非 ASCII 编码的正则表达式：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat encodings.rb
# encoding: UTF-8
res = [
  /…\z/,       # source Encoding
  /\A\uFEFF/,  # special escape
  /abc/u       # Ruby 1.8 option
]
puts res.map { |re| [re.encoding.name, re.inspect].join(&quot; &quot;) }

$ ruby encodings.rb
UTF-8 /…\z/
UTF-8 /\A\uFEFF/
UTF-8 /abc/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;上面用到的 &lt;code&gt;/u&lt;/code&gt; 你或许还记得，它能在 Ruby 1.8 中获得一个 UTF-8 编码的正则表达式。&lt;code&gt;/e&lt;/code&gt; （EUC_JP）和 &lt;code&gt;/s&lt;/code&gt; （Shift_JIS 的一个扩展 Windows-31J）也同样可以继续使用。Ruby 1.9 还支持原来的 &lt;code&gt;/n&lt;/code&gt; 选项，不过&lt;a href=&quot;http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/23204&quot;&gt;因为遗留原因会产生一些错误&lt;/a&gt;，所以建议不要再用了。下面会为你介绍一种生成 ASCII-8BIT 编码正则表达式的方法。&lt;/p&gt;

&lt;p&gt;在 Ruby 1.9.2 中，“正则表达式可以匹配任意和 ASCII 兼容的数据”这种概念有了一个新名称：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat fixed_encoding.rb
[/a/, /a/u].each do |re|
  puts &quot;%-10s %s&quot; % [ re.encoding, re.fixed_encoding? ? &quot;fixed&quot; :
                                                    &quot;not fixed&quot; ]
end

$ ruby fixed_encoding.rb
US-ASCII   not fixed
UTF-8      fixed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;“编码锁定”的正则表达式，在处理不完全由 ASCII 字符组成（&lt;code&gt;ascii_only?()&lt;/code&gt;）的字符串时，如果这个字符串包含与正则表达式不一样编码的内容就会抛出 &lt;code&gt;Encoding::CompatibilityError&lt;/code&gt; 异常。如果 &lt;code&gt;fixed_encoding?()&lt;/code&gt; 返回 &lt;code&gt;false&lt;/code&gt;，正则表达式则可以用来处理任何与 ASCII 兼容的编码。甚至还有一个名为 &lt;code&gt;FIXEDENCODING&lt;/code&gt; 的常量可以用来禁止对 ASCII 的降级处理：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat force_re_encoding.rb
puts Regexp.new(&quot;abc&quot;.force_encoding(&quot;UTF-8&quot;)).encoding.name
puts Regexp.new( &quot;abc&quot;.force_encoding(&quot;UTF-8&quot;),
                 Regexp::FIXEDENCODING ).encoding.name

$ ruby force_re_encoding.rb
US-ASCII
UTF-8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;注意，如果为 &lt;code&gt;Regexp.new()&lt;/code&gt; 指定了 &lt;code&gt;Regexp::FIXEDENCODING&lt;/code&gt; 参数，正则表达式就会使用传入的字符串的编码。你可以使用这种方式生成采用任何一种编码的正则表达式，包括前面提到的 ASCII-8BIT。&lt;/p&gt;

&lt;p&gt;只要正则表达式的编码和数据的编码是兼容的，那么模式匹配功能就可以正常运行。（好吧，事实上 Ruby 1.9 引入了一款新的很强大的正则表达式引擎叫做 Oniguruma，不过这不在本文讨论范围之内。）一般情况下，在 Ruby 1.9 中使用正则表达式的编码选项就可以处理大多数的数据了，不过如果在使用过程中遇到一些问题的话就，就不要使用简单的正则表达式字面量形式（&lt;code&gt;/.../&lt;/code&gt;），而使用上面介绍的这种构建正则表达式的方法来选择与数据能够较好匹配的编码。&lt;/p&gt;

&lt;h3&gt;处理 BOM&lt;/h3&gt;

&lt;p&gt;有些多字节的编码方法推荐在数据的开头加入字节顺序标记（Byte Order Mark，简称 BOM），用来指明字节的顺序。UTF-16 就是个很好的例子。&lt;/p&gt;

&lt;p&gt;不过，Ruby 实际上不支持 UTF-16 编码，你能使用的是 UTF-16BE 和 UTF-16LE，二者分别对应的是“大端”（Big Endian）和“小端”（Little Endian）字节顺序。这里的“字节顺序”是指最高有效字节出现在开头还是结尾处：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ ruby -e &#39;p &quot;a&quot;.encode(&quot;UTF-16BE&quot;)&#39;
&quot;\x00a&quot;

$ ruby -e &#39;p &quot;a&quot;.encode(&quot;UTF-16LE&quot;)&#39;
&quot;a\x00&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;如果需要读取这些 UTF-16 编码的数据就必须知道使用了哪种字节顺序才能正确处理。你可以直接指明这些 UTF-16 数据的具体编码方式（UTF-16BE 或 UTF-16LE），或者为这些数据添加一个 BOM。&lt;/p&gt;

&lt;p&gt;Unicode 的 BOM 就是数据开头处的一个字符 &lt;code&gt;U+FEFF&lt;/code&gt;，这个字符无法反转，所以这么做可以简单而正确的指明数据的字节顺序。这种方法还有一个小有点是它指明了你要读取的是 Unicode 数据。很多软件都会在数据的开头检查这个特殊的字符，用它来设置正确的字节顺序，然后假设这个字符并不存在而将余下的数据显示给用户。&lt;/p&gt;

&lt;p&gt;Ruby 1.9 不会自动为数据添加 BOM，所以如果你需要的话就要自己动手了。不过过程不是很麻烦，基本的思路是将所需的字节放置在文件的开头。例如我们可以为 UTF-16LE 文件添加一个 BOM：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat utf16_bom.rb
# encoding: UTF-8
File.open(&quot;utf16_bom.txt&quot;, &quot;w:UTF-16LE&quot;) do |f|
  f.puts &quot;\uFEFFThis is UTF-16LE with a BOM.&quot;
end

$ ruby utf16_bom.rb
$ ruby -e &#39;p File.binread(&quot;utf16_bom.txt&quot;)[0..9]&#39;
&quot;\xFF\xFET\x00h\x00i\x00s\x00&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;注意我使用了 Unicode 转义将 BOM 所需的字符加入到数据中。因为输出字符串是 UTF-8 编码，Ruby 必须要把它转码到 UTF-16LE，转码的过程中正确的保留了字节的顺序，如你在输出结果中看到的那样。&lt;/p&gt;

&lt;p&gt;BOM 的读取也很简单，我们要取出相关的字节，看它是否和某个 Unicode BOM 匹配，如果匹配我们就使用相匹配的编码再次读取数据。我们可以这样编写代码：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;$ cat read_bom.rb
class File
  UTFS = [32, 16].map { |b| %w[BE LE].map { |o| &quot;UTF-#{b}#{o}&quot; } }
                 .flatten &amp;lt;&amp;lt; &quot;UTF-8&quot;

  def self.open_using_unicode_bom(path, *args, &amp;amp;blk)
    # 读取 BOM 查找编码
    encoding = UTFS[0..-2].find(lambda { UTFS[-1] }) do |utf|
      bom = &quot;\uFEFF&quot;.encode(utf)
      binread(path, bom.bytesize).force_encoding(utf) == bom
    end
    # 设置编码
    if args.first.nil?
      args &amp;lt;&amp;lt; &quot;r#{&#39;b&#39; unless encoding == UTFS[-1]}:#{encoding}&quot;
    elsif args.first.is_a? Hash
      args.first.merge!(external_encoding: encoding)
    else
      args.first.sub!(/\A([^:]*)/, &quot;\1:#{encoding}&quot;)
    end
    # 处理文件的 open()
    if blk
      open(path, *args) do |f|
        f.read_unicode_bom
        blk[f]
      end
    else
      f = open(path, *args)
      f.read_unicode_bom
      f
    end
  end

  def read_unicode_bom
    bytes = external_encoding.name[/\AUTF-?(\d+)/i, 1].to_i / 8
    read(bytes) if bytes &amp;gt; 1
  end
end

# example usage with the File we created earlier
File.open_using_unicode_bom(&quot;utf16_bom.txt&quot;) do |f|
  line = f.gets
  p [line.encoding, line[0..3]]
end

$ ruby read_bom.rb
[#&amp;lt;Encoding:UTF-16LE&amp;gt;, &quot;T\x00h\x00i\x00s\x00&quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;上述的代码片段只针对几种 Unicode 的 BOM，不过你可以使用类似的方式处理其他 BOM：找到确定编码方法所需的字节，在读取数据之前设置好所学的编码，然后在读取数据之后再检验数据的编码是否正确。字符串转义在写入字节时很便利，&lt;code&gt;binread()&lt;/code&gt; 在检查 BOM 时也很便利。&lt;/p&gt;

&lt;p&gt;我建议为 UTF-16 和 UTF-32 这样的 Unicode 编码加入 BOM，但是请不要为 UTF-8 添加。UTF-8 编码的字节顺序是其规范所定的，永远都不会变化，所以正确读取 UTF-8 数据无需 BOM 相助。如果添加了 BOM 就会破坏 UTF-8 可以装作 US-ASCII 这个好处（假设所有的字符都是七位）。&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;um-h-11&quot;&gt;&lt;/a&gt;
## 11. Ruby 1.9 为我们提供了什么&lt;/p&gt;

&lt;p&gt;这是本文的最后一节，我想重新审视前面讨论过的编码策略。你已经看到了，Ruby 1.9 加强了字符编码处理功能，所以我们要了解一下这对我们来说有什么变化。&lt;/p&gt;

&lt;h3&gt;UTF-8 仍然时首选&lt;/h3&gt;

&lt;p&gt;最重要的是要注意 Ruby 1.9 没有改变的事情，在&lt;a href=&quot;#um-h-3&quot;&gt;第三节&lt;/a&gt;中我说过普遍适用的编码是 UTF-8，这一点现在仍然正确。&lt;/p&gt;

&lt;p&gt;我仍然推荐使用 UTF-8，它基本上可以解决一切编码问题。我坚信我们可以也应该在源码中、在数据转换过程中、在转码输出时使用 UTF-8。我们越多的使用它，世界就会变得越美好。&lt;/p&gt;

&lt;p&gt;如前所述，Ruby 1.9 也为 UTF-8 的处理增加了一些新特性。例如，你可以使用命令行开关（&lt;code&gt;-E&lt;/code&gt; 和 &lt;code&gt;-U&lt;/code&gt;）设置编码对所有读取的数据进行转换。这种简便的方式对简单的脚本很有用，但是在处理正式的代码时最好能在源码中显式注明所需的编码。&lt;/p&gt;

&lt;h3&gt;新规则&lt;/h3&gt;

&lt;p&gt;Ruby 1.9 表面上为我们提供了对数据处理的全新能力，不过这些新能力也意味着新责任。从现在开始在 Ruby 1.9 中养成一些好习惯吧：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;在源码的顶部加入“&lt;a href=&quot;#um-h-9&quot;&gt;神奇注释&lt;/a&gt;”&lt;/li&gt;
  &lt;li&gt;使用 &lt;code&gt;open()&lt;/code&gt; 时显式的&lt;a href=&quot;#um-h-9&quot;&gt;为 IO 对象声明编码&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;不错，这稍微增加了一点工作量，但却很值得这么做：这会在潜意识里增加你对编码的重视，也能让 Ruby 正确处理数据。&lt;/p&gt;

&lt;h3&gt;新策略&lt;/h3&gt;

&lt;p&gt;UTF-8 作为一个简单而强大的选择，Ruby 1.9 在字符处理方面为我们提供一些让人兴奋的新选项。在这我只举一个例子，引导你按正确的方向思考，但是天空很广阔，我确信在未来的几年中会出现更为优雅的用法。&lt;/p&gt;

&lt;p&gt;当我将 FasterCSV 代码库转成 Ruby 1.9 的 CSV 标准库时，我坐下来认真的思考了应该怎样处理对多语言的支持。下面这些想法最终实现了我的计划：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;我们经常会抛给 CSV 处理器大量的数据，比如当我们转存数据库的时候&lt;/li&gt;
  &lt;li&gt;我宁愿复出性能底下的代价也要将所有输入的数据转码到 UTF-8。我无法确定这个代价有多大，但是这要比 Ruby 1.8 只能按照字节读取要好一些。当然我想尽量保证代码库的处理速度够快。&lt;/li&gt;
  &lt;li&gt;因为处理器总是直接从 IO 对象读取数据，所以我们已经有办法进行 UTF-8 转码处理。&lt;/li&gt;
  &lt;li&gt;CSV 是一种很容易处理的格式，它只需要支持 Ruby 提供的编码中的四种标准方法即可&lt;/li&gt;
  &lt;li&gt;最后，当然我只是想熟悉一下怎么处理对多语言的支持&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我把所有的这些思考结合在一起制定了一个策略：不转码数据，转码处理器。&lt;/p&gt;

&lt;p&gt;如果转码数据，每次读取都会复出一些代价；而转码处理器则是一次性消费。数据中的字符是以数据所使用的编码方法存在的，对处理器进行转码则可以按照正常的方式读取然后处理这些数据。写入数据的过程仍然照此处理，除非用户特别声明不要这么做。这样用户就可以自由的为他们的数据选择编码了。&lt;/p&gt;

&lt;p&gt;不难发现在处理过程中的某些地方可能很繁琐，首先要做的是找出数据真实使用的编码方法。下面这段来自 Ruby 1.9 CSV 标准库的代码可以做到这一点：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;@encoding =   if @io.respond_to? :internal_encoding
                @io.internal_encoding || @io.external_encoding
              elsif @io.is_a? StringIO
                @io.string.encoding
              end
@encoding ||= Encoding.default_internal || Encoding.default_external
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;这段代码确保在读取数据之后 &lt;code&gt;@encoding&lt;/code&gt; 被设置为我所要处理的数据的真正编码方法。如果设置了 IO 的内部编码，那么就把数据转码到这种编码，然后赋值给 &lt;code&gt;@encoding&lt;/code&gt;；否则，&lt;code&gt;@encoding&lt;/code&gt; 的值就是外部编码。这段代码还可以处理直接包含在 &lt;code&gt;StringIO&lt;/code&gt; 对象中的字符串，这时我们就可以直接读取所含字符串的编码。如果以上的操作都没有找到编码，那么就采用默认值（很可能是还没有设定），因为 Ruby 就是这么处理的。&lt;/p&gt;

&lt;p&gt;一旦知道了编码方法，我们就需要一些方法来使用这个编码生成字符串对象和正则表达式对象。这些方法是：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;def encode_str(*chunks)
  chunks.map { |chunk| chunk.encode(@encoding.name) }.join
end

def encode_re(*chunks)
  Regexp.new(encode_str(*chunks))
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;如果你阅读了&lt;a href=&quot;#um-h-8&quot;&gt;前面对转码是如何运作&lt;/a&gt;的讨论，你就会发现这些代码很容易理解。你可以向 &lt;code&gt;encode_str()&lt;/code&gt; 传递一个字符串或多个字符串作为参数，这个方法会对每一个字符串进行转码，然后把它们连接（&lt;code&gt;join()&lt;/code&gt;）在一起形成一个完整的字符串。&lt;code&gt;encode_re()&lt;/code&gt; 只是包含了 &lt;code&gt;encode_str()&lt;/code&gt;，因为 &lt;code&gt;Regexp.new()&lt;/code&gt; 会根据传入的字符串正确的设置正则表达式对象的编码。&lt;/p&gt;

&lt;p&gt;接下来是比较繁琐的一步。在处理原始数据的时候，你要完全避免使用字符串和正则表达式的字面量形式。例如，下面的代码就是 CSV 标准库用来在读取数据之前准备处理器的：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;# Pre-compiles parsers and stores them by name for access during reads.
def init_parsers(options)
  # store the parser behaviors
  @skip_blanks      = options.delete(:skip_blanks)
  @field_size_limit = options.delete(:field_size_limit)

  # prebuild Regexps for faster parsing
  esc_col_sep = escape_re(@col_sep)
  esc_row_sep = escape_re(@row_sep)
  esc_quote   = escape_re(@quote_char)
  @parsers = {
    # for empty leading fields
    leading_fields: encode_re(&quot;\A(?:&quot;, esc_col_sep, &quot;)+&quot;),
    # The Primary Parser
    csv_row:        encode_re(
      &quot;\G(?:\A|&quot;, esc_col_sep, &quot;)&quot;,                # anchor the match
      &quot;(?:&quot;, esc_quote,                              # find quoted fields
             &quot;((?&amp;gt;[^&quot;, esc_quote, &quot;]*)&quot;,             # &quot;unrolling the loop&quot;
             &quot;(?&amp;gt;&quot;, esc_quote * 2,                   # double for escaping
             &quot;[^&quot;, esc_quote, &quot;]*)*)&quot;,
             esc_quote,
             &quot;|&quot;,                                    # ... or ...
             &quot;([^&quot;, esc_quote, esc_col_sep, &quot;]*))&quot;,  # unquoted fields
      &quot;(?=&quot;, esc_col_sep, &quot;|\z)&quot;                    # ensure field is ended
    ),
    # a test for unescaped quotes
    bad_field:      encode_re(
      &quot;\A&quot;, esc_col_sep, &quot;?&quot;,                   # an optional comma
      &quot;(?:&quot;, esc_quote,                          # a quoted field
             &quot;(?&amp;gt;[^&quot;, esc_quote, &quot;]*)&quot;,          # &quot;unrolling the loop&quot;
             &quot;(?&amp;gt;&quot;, esc_quote * 2,               # double for escaping
             &quot;[^&quot;, esc_quote, &quot;]*)*&quot;,
             esc_quote,                          # the closing quote
             &quot;[^&quot;, esc_quote, &quot;]&quot;,               # an extra character
             &quot;|&quot;,                                # ... or ...
             &quot;[^&quot;, esc_quote, esc_col_sep, &quot;]+&quot;, # an unquoted field
             esc_quote, &quot;)&quot;                      # an extra quote
    ),
    # safer than chomp!()
    line_end:       encode_re(esc_row_sep, &quot;\z&quot;),
    # illegal unquoted characters
    return_newline: encode_str(&quot;\r\n&quot;)
  }
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;不要纠缠于那些重度优化的正则表达式，这段代码的要点是理解数据最终是如何在 &lt;code&gt;encode_str()&lt;/code&gt; 和 &lt;code&gt;encode_re()&lt;/code&gt; 之间传递的。&lt;/p&gt;

&lt;p&gt;这些就是为了使用内置于数据的编码进行处理需要对 CSV 标准库改动的主要部分。不过我也添加了一些额外代码用来解决遇到的问题，不过它们和这个策略本身没有太多的关联：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;Regexp.escape()&lt;/code&gt; 没有正确处理我测试的所有编码，在此之后虽然做了一些改进，但是上一次我测试发现还是不能支持一些奇怪的编码。鉴于此我就要自己想办法了，如果你想知道我是怎么做的，查看 &lt;code&gt;CSV.initialize()&lt;/code&gt;（&lt;a href=&quot;http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/lib/csv.rb?revision=22788&amp;amp;view=markup&quot;&gt;链接&lt;/a&gt;）中的代码，看一下 &lt;code&gt;@re_esc&lt;/code&gt; 和 &lt;code&gt;@re_chars&lt;/code&gt; 是如何设定的；然后再看一下 &lt;code&gt;CSV.escape_re()&lt;/code&gt;（&lt;a href=&quot;http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/lib/csv.rb?revision=22788&amp;amp;view=markup&quot;&gt;链接&lt;/a&gt;）。&lt;/li&gt;
  &lt;li&gt;CSV 对行尾的检测会向前偏移固定的几个字节。在处理编码后的数据时就存在着安全问题，因为你总是会停留在字符的中间。如果你感兴趣的话，可以查看 &lt;code&gt;CSV.read_to_char()&lt;/code&gt;（&lt;a href=&quot;http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/lib/csv.rb?revision=22788&amp;amp;view=markup&quot;&gt;链接&lt;/a&gt;）看一下我是如何处理这个问题的。&lt;/li&gt;
  &lt;li&gt;最后，对 Ruby 支持的所有编码进行测试有点麻烦，因为有些是“空壳编码”。如何提出这些“空壳编码”请参照&lt;a href=&quot;#um-h-10&quot;&gt;之前的讨论&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;与其他事情一样，这个策略尤其好的地方也有不好的地方。我已经说过了，避免使用一般的字面量形式需要一些很繁琐的操作。代码复杂性的提高使代码更加不易阅读，也增加了维护的难度。这就是要复出的代价。&lt;/p&gt;

&lt;p&gt;不过我认为这显式出了利用 Ruby 的新特性能够完成设想的可能性。我们可以像过去一样，坚持认为 UTF-8 是解决问题的万能钥匙。这种坚持在多数情况下是正确的。但是现在我们有了一些对旧版本 Ruby 来说不太可行的新选择。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>中译本的好和不好</title>
   <link href="http://about.ac/2012/05/metaprogruby-book-review.html"/>
   <updated>2012-05-29T00:00:00+08:00</updated>
   <id>http://about.ac/2012/05/metaprogruby-book-review</id>
   <content type="html">
&lt;p&gt;本文是对《&lt;a href=&quot;http://book.douban.com/subject/7056800/&quot;&gt;Ruby元编程&lt;/a&gt;》一书的评论，原文载于豆瓣。&lt;/p&gt;

&lt;p&gt;这本书之前看过英文版，再读中文版时轻松很多，大多数知识是掌握了的，不过这次阅读却也收获了之前忽略的内容。其实技术类书籍是写不出什么读后感的，本文只是针对 literal 层面的东西做个评论。&lt;/p&gt;

&lt;h2&gt;外观&lt;/h2&gt;

&lt;p&gt;和同时买的另一本技术书译本相比，这本书的外观简直就是精品。无瑕疵是必须的，能够使用英文原版的封面更是让我喜出望外。熟悉 Ruby/Rails 的人，大都对 Pragmatic 的书有很深的感情，所以看到这个封面我觉得异常的高兴。&lt;/p&gt;

&lt;p&gt;翻一下内页，白白的纸也给我一种纯粹感，技术类书籍就是应该如此毫无多余的装饰，白白净净。&lt;/p&gt;

&lt;h2&gt;排版&lt;/h2&gt;

&lt;p&gt;我对中译本的内容排版还是有点微词的。&lt;/p&gt;

&lt;p&gt;翻了几页后，我发现很多页下方都有大片的空白，开始我有些不解，再往后翻，底部有空白的页还有很多，所以我打开英文版电子书做了一下对照，这才发现，原来是为了和原书保持一致。我自己曾经也翻译过一些技术书籍，知道大多数情况下一页英文翻译到中文基本上都会少于一页。所以我知道了这些空白是为了“完全”和英文原书保持一致。&lt;/p&gt;

&lt;p&gt;我一开始把这种处理方式理解为“好的”，毕竟是译本，和原书保持一致也无可厚非，但是当发现另一处排版问题时，我改变的想法，这种处理方式是“不好的”，完全可以理解为&lt;strong&gt;偷懒行为&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;这个想法的改变发生在 P66 代码片段的第五行：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;data_source.methods.grep(/^get_(.*)_info$/) { Computer.define_component $
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;这行代码就这样戛然而止了，很明显这行代码并不完整，后面应该补上：&lt;code&gt;1 }&lt;/code&gt;。仅此一例，似乎说明不了问题，而后我又发现了其他两处这种代码截断的排版。&lt;/p&gt;

&lt;p&gt;遇到这种情况很难让我不联想到“偷懒”二字：大片的空白是为了偷懒，照搬原书的排版就行了，没必要二次排版，省下不少工夫；代码的截断是偷懒，因为录入人员连换行都没必要用，还可以和原版保持一致，因为原版在这个地方没有换行啊。&lt;/p&gt;

&lt;p&gt;说完排版的不好，然后说一个好的。&lt;/p&gt;

&lt;p&gt;中译本的行间代码终于使用了 monospace 字体族，再也不是中文字体默认的渲染效果了，这一点很值得称赞。&lt;/p&gt;

&lt;h2&gt;代码的行文错误&lt;/h2&gt;

&lt;p&gt;这本书的译者之前翻译过 Ruby 方面的书，我推测他应该是一个懂 Ruby 的技术人员，或者至少懂得其他的编程语言，很可惜，我没有搜索到译者的相关介绍或是个人网站。&lt;/p&gt;

&lt;p&gt;而且这本书还特意标明了“审校”人员，我不知道这名审校是否为技术人员，但更有可能是出版社的编辑。&lt;/p&gt;

&lt;p&gt;所以这本书出现的一些代码错误就有些让人不可原谅了。&lt;/p&gt;

&lt;p&gt;这些错误基本上只有一种：空格的错用，要不多了，要不少了。最常见的错误是将类似 &lt;code&gt;attr_accessor&lt;/code&gt; 的代码写成 &lt;code&gt;attr_ accessor&lt;/code&gt;，这种错误不下数十处。&lt;/p&gt;

&lt;p&gt;P214 的一处错误更是让我啼笑皆非：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;class String
  includeActiveSupport::CoreExtensions::String::Access
  includeActiveSupport....
  includeActiveSupport....
  includeActiveSupport....
  includeActiveSupport....
  includeActiveSupport....
  includeActiveSupport....
  includeActiveSupport....
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;不过由此也从侧面证实，这些代码片段并不是直接从原书 copy 的，而是经过了二次加工。&lt;/p&gt;

&lt;p&gt;还有一处错误发生在 P115 底部代码片段的倒数第五行和倒数第三行，将 &lt;code&gt;*args&lt;/code&gt; 写成了 &lt;code&gt;×args&lt;/code&gt;，难不成相关人员在输入大量英文时还在使用中文输入法？&lt;/p&gt;

&lt;p&gt;其他的小错误还有很多，在此不一一列举了。听说这本书会再印，希望下次印刷前能把这些错误更正。&lt;/p&gt;

&lt;h2&gt;总结&lt;/h2&gt;

&lt;p&gt;这本书的内容自不必说，很好，看后绝对会有很大的收获，虽然书名是元编程，但大很多内容都在讲对象模型，可以结合 &lt;a href=&quot;http://v.youku.com/v_show/id_XNDAwNDk5ODg4.html&quot;&gt;Dave 的一个视频&lt;/a&gt;来深入理解这部分的内容。&lt;/p&gt;

&lt;p&gt;这本中译本本想给5星，可毕竟出现了一些让我不可原谅的错误，所以扣1星。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>不要站在巨人的肩上耍人</title>
   <link href="http://about.ac/2012/05/nosql-book-review.html"/>
   <updated>2012-05-27T00:00:00+08:00</updated>
   <id>http://about.ac/2012/05/nosql-book-review</id>
   <content type="html">
&lt;p&gt;本文是对《&lt;a href=&quot;http://book.douban.com/subject/10736899/&quot;&gt;NoSQL数据库入门&lt;/a&gt;》一书的评论，原文载于豆瓣。&lt;/p&gt;

&lt;p&gt;差不多2个半小时翻完了这本书，这本书算是入门中的入门，没有涉及具体的技术细节，加之有一些代码示例占了篇幅，所以读起来比较快。&lt;/p&gt;

&lt;p&gt;读完本书最大的收获有两点：&lt;/p&gt;

&lt;p&gt;1. 原来 NoSQL 的意思是“不只有 SQL”，之前一直以为是 No SQL；&lt;br /&gt;
2. 被耍了。&lt;/p&gt;

&lt;p&gt;至于第一点没什么好展开细说的，我不是专家，对 NoSQL 技术没有更深入的见解；那么就说一说第二点。&lt;/p&gt;

&lt;p&gt;在说之前还要申明一点：我不是出版行业业内人员，对这一行了解也不多，以下言论完全基于个人“臆测”。&lt;/p&gt;

&lt;p&gt;为什么被耍了？因为这本书不是把你当读者的，而是把你当成 QA（质量保证）人员。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;拿到书的第一眼我就发现，书脊下方严重变形。这是在考验我的质检能力啊，不过这个考察太简单了。这本书的印刷量 1-4000 册，技术类书籍大都如此。这个数量级，如果交给印刷厂的话，半天应该没问题（猜测），印刷厂应该会有质检人员（不会没有吧），我理解这位质检人员疲于工作，没有发现这种扫一眼就能发现的质量问题；那么出版社应该有监管人员吧，如果这位监管也没有发现，最终导致这本次品流入了我的手，我就忍了吧，毕竟这是外表，不能以貌取书；&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;看了外面看内容吧。怀揣着对 NoSQL 的好奇心，我立马忘记了书脊的变形，开始翻看内页。因为 1.1 节在豆瓣看过试读，大概的扫了一眼就翻过去了。到现在，心情一直不错。但是在 P12 时我愣了一下，“http://nosql-databse”？这是什么类型的 URL，再看下面的附图，我明白了，少了“.org”。至此之后我的心情算是被完全破坏了：&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;P17 最后一行，“那就更不起了”是什么意思？&lt;br /&gt;
P19 第一段第三行，“Reds” 是什么？&lt;br /&gt;
P22 的注释1在穿越了吧？&lt;br /&gt;
P27 第二行，“⋯⋯ SQL 文”是什么意思？&lt;br /&gt;
P28 倒数第三行，“CACHESIAZE”?&lt;br /&gt;
P34 注释1，“https: //….”，你确定 // 之前需要空格？&lt;br /&gt;
P36 表 2-4，你确定有种编程语言叫“Pert”？&lt;br /&gt;
P40 第二个代码片段，“# {}”（# 后有空格），你确定 Ruby 中的字符串插值是这个句法？（类似错误很多）&lt;br /&gt;
P45 注释2，“…/blog? p=31”，你确定“p=31”之前有空格？（类似错误很多）&lt;br /&gt;
P47 注释3，“…./nosqltokoy &lt;em&gt;tyrantnetvibes…”，你确定“&lt;/em&gt;”之前需要空格？（类似错误很多）&lt;br /&gt;
P62 注释1，“….wikipedia.0org/…”，原来维基百科的 URL 是e这样的&lt;/p&gt;

&lt;p&gt;然后基本上都是示例，读的比较快，没发现什么错误。终于翻到最后一夜了，但是：&lt;/p&gt;

&lt;p&gt;P199 倒数第二行，“….开始正式使用 HandlerSocke 了 t”，“了”明显做了第三者，非要插足“e”和“t”之间。&lt;/p&gt;

&lt;p&gt;然后是扉页，硕大的几个字“站在巨人的肩上”。&lt;/p&gt;

&lt;p&gt;又免费做了一次校订。&lt;/p&gt;

&lt;p&gt;还有一个国内技术书籍的一个通病，中英文混排时英文字体的使用，咱能不使用系统自带的宋体来渲染英语好吗？咱在写代码的时候使用 monospace 字体族好吗？这是做技术人员的基本素质，您老这样不把下一代码农教坏了吗？&lt;/p&gt;

&lt;p&gt;看完整本书最大的感受是被耍了，作为一个读者没有被尊重，这完全是一个“草稿”，为什么却已经推向了市场？&lt;/p&gt;

&lt;p&gt;这些错误，作为一个完全的初学者都能发现，难道出版社专业的图书编辑没有发现？&lt;/p&gt;

&lt;p&gt;如果一个出版社不能尊重读者、不重视图书的质量，站在巨人的肩上不是看得远，而是摔得更疼。&lt;/p&gt;

&lt;p&gt;致出版社：&lt;/p&gt;

&lt;p&gt;1. 重视读者，关注细节&lt;br /&gt;
2. 招个懂技术的图书编辑吧&lt;br /&gt;
3. 译者一定要靠谱&lt;/p&gt;

&lt;p&gt;这是在逼我学日语看原版书吗？&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>怎样学习 Ruby</title>
   <link href="http://about.ac/2012/04/how-to-learn-ruby.html"/>
   <updated>2012-04-20T00:00:00+08:00</updated>
   <id>http://about.ac/2012/04/how-to-learn-ruby</id>
   <content type="html">
&lt;p&gt;学给自己的忠告。&lt;/p&gt;

&lt;h2&gt;静心读书，远离电脑、网络&lt;/h2&gt;

&lt;p&gt;虽说 Ruby 是一门电脑编程语言，但是任何学习的过程都离不开“书本”。网络上有大量电子书可以下载阅读，条件允许的话，还是打印出来看吧，纸质书和电子书的触感是不同的，而且对着电脑容易分心。Ruby 中有很多概念是需要记忆、理解的，这时最好拿着书离开电脑桌，找个舒服的地方精下心来认真阅读体会；学习到语法细节时则需要在电脑上敲击键盘了。&lt;/p&gt;

&lt;h2&gt;专注 Ruby，远离 gem、Rails&lt;/h2&gt;

&lt;p&gt;随着学习的推进，你会发现 Ruby 的世界太精彩了，有多的让人眼花的 gem，还有各种奇怪、很酷、很 geek 的用法，这些就是 Ruby 世界的“糖衣炮弹”，在学习 Ruby 的初期一定要避免诱惑，半年之内，不要接触任何功能强大的 gem，当然包括 Rails，专心学习 Ruby 编程语言才是正事。&lt;/p&gt;

&lt;p&gt;不要觉得已经掌握了所有知识，即便是 Matz 本人也说过：对于从事面向对象编程有 15 年以上（经验）的我来说，有很多概念还是觉得很难理解。（《松本行弘的程序世界》P16 2.2.1 面向对象的历史）&lt;/p&gt;

&lt;h2&gt;遵循 Ruby 代码规范&lt;/h2&gt;

&lt;p&gt;Ruby 语言本身没有严格限制代码行文规范，你可以把代码写的像 PHP 一样乱，也可以写的像 Python 一样整洁。代码整洁、格式统一的代码对自己、对他人都有很多的好处。目前比较被接受的 Ruby 代码规范是 &lt;a href=&quot;https://github.com/bbatsov/ruby-style-guide/blob/master/README.md&quot;&gt;ruby-style-guide&lt;/a&gt;。&lt;/p&gt;

&lt;h2&gt;重复造（大量的）轮子&lt;/h2&gt;

&lt;p&gt;对初学者来说，重复造轮子是最有用的方法，在制造轮子的过程中可以从零开始认识轮子的结构、材料、工艺流程、质量检验、不良返修等过程。这些不是看来的，是动手做出来的。&lt;/p&gt;

&lt;p&gt;即便到了学习的中期也要对他人的代码持怀疑态度，发扬“&lt;a href=&quot;http://en.wikipedia.org/wiki/Not_invented_here&quot;&gt;NIH 综合征&lt;/a&gt;”精神，提出自己的观点，改进原有代码或者自己造一个更好地轮子。&lt;/p&gt;

&lt;h2&gt;学习英语，看原版书&lt;/h2&gt;

&lt;p&gt;Ruby 的技术虽然更新不快，但是也不慢，如果一个新的技术出现，等出现中文翻译，估计已经是两三年之后的事了，那时新的已经变成旧的了。所以，不要依赖中文，要阅读英文原版书籍、文章，一来锻炼了英语，二来接触到了最新的技术。&lt;/p&gt;

&lt;h2&gt;不要沉浸在论坛中&lt;/h2&gt;

&lt;p&gt;论坛是无用的，特别是中文论坛，不要沉浸其中，实在闲来无事倒是可以用来解解闷子。如果遇到问题，第一个想到的应该是到 &lt;a href=&quot;http://stackoverflow.com/&quot;&gt;Stack Overflow&lt;/a&gt; 寻求帮助，这个社区会让你异常欣喜。&lt;/p&gt;

&lt;h2&gt;遵循“守破离”原则&lt;/h2&gt;

&lt;p&gt;任何知识都不是一朝一夕能够掌握的，学习过程切记三心二意、半途而废。整个的学习过程可以分阶段，参照日本合气道的指导思想进行，即“&lt;a href=&quot;http://en.wikipedia.org/wiki/Shuhari&quot;&gt;守破离（shuhari）&lt;/a&gt;”。&lt;/p&gt;

&lt;p&gt;知识的学习就像练习一门功夫一样，需要经历三个阶段。&lt;/p&gt;

&lt;p&gt;第一个阶段“守”。再强大的功夫也有心法套路（降龙十八掌 九九八十一式），这些是初学者必须死记硬背，反复练习的，是“重复造轮子”的最佳阶段，也是整个学习过程最枯燥乏味的，如果能熬过第一阶段，成为大侠就指日可待了。&lt;/p&gt;

&lt;p&gt;第二个阶段是“破”。多年的潜心修炼，现在已经可以脱离套路去肆意发挥了，只要还在心法控制的范围之内即可。在这个阶段可以四处找高手切磋过招、指点刚入门的学徒，在潜移默化中提高自己的修为，这也是练习内功的最佳时机。如果一切顺利，基本上就是“大侠”了。&lt;/p&gt;

&lt;p&gt;第三个阶段是“离”。吸收了多家之长后就可以像张三丰一样自创“太极”，让他人敬仰了。但是很少有人能够达到这一境界，在武侠之外的编程界，据说只有 C 语言的创始人 Dennis Ritchie 达到了这一阶段，可见其修为了得。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>使用 rbenv 安装管理 Ruby</title>
   <link href="http://about.ac/2012/04/install-ruby-with-rbenv.html"/>
   <updated>2012-04-19T00:00:00+08:00</updated>
   <id>http://about.ac/2012/04/install-ruby-with-rbenv</id>
   <content type="html">
&lt;p&gt;Ruby 有不同的版本会同时存在，现在最新版本是 1.9.3-9125，但是有些开发者仍然使用 1.8.x 系列。而且很多程序只针对特定的 Ruby 版本。所以对 Ruby 做版本管理是即为重要的，这其中也就涉及到安装的问题。&lt;/p&gt;

&lt;p&gt;常用的几个 Ruby 版本管理工具有：&lt;a href=&quot;https://github.com/wayneeseguin/rvm&quot;&gt;rvm&lt;/a&gt;，&lt;a href=&quot;https://github.com/sstephenson/rbenv&quot;&gt;rbenv&lt;/a&gt;，&lt;a href=&quot;https://github.com/jayferd/ry&quot;&gt;ry&lt;/a&gt;，&lt;a href=&quot;https://github.com/hmans/rbfu&quot;&gt;rbfu&lt;/a&gt;。rvm 应该是最早出现、使用最多的，因为过于强大以至于违背了某个 Linux 软件开发原则，所以出现了很多轻便的替代者，其中来自 37signals 的 rbenv 就很受欢迎。ry 和 rbfu 看上去更轻便，不过使用不广泛。所以我最终选择使用 rbenv。&lt;/p&gt;

&lt;p&gt;我电脑的环境是：Mac OS X 10.6.8，XCode 3.2.3，终端是 bash。&lt;/p&gt;

&lt;h2&gt;1. 安装 rbenv&lt;/h2&gt;

&lt;p&gt;在终端中，从 github 上将 rbenv 源码 clone 到本地，然后设置 &lt;code&gt;$PATH&lt;/code&gt; 等。&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;git clone git://github.com/sstephenson/rbenv.git ~/.rbenv
echo &#39;export PATH=&quot;$HOME/.rbenv/bin:$PATH&quot;&#39; &amp;gt;&amp;gt; ~/.bash_profile
echo &#39;eval &quot;$(rbenv init -)&quot;&#39; &amp;gt;&amp;gt; ~/.bash_profile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;2. 安装 ruby-build&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/sstephenson/ruby-build&quot;&gt;ruby-build&lt;/a&gt; 这个工具用来安装编译 Ruby 源码，如果选择手动编译，可不使用这个工具。&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;mkdir -p ~/.rbenv/plugins
cd ~/.rbenv/plugins
git clone git://github.com/sstephenson/ruby-build.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;3. 安装 Ruby&lt;/h2&gt;

&lt;p&gt;使用 ruby-build 可以自动下载编译安装 Ruby 相应的版本，只需指定版本号。&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;rbenv install 1.9.3-p125
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;等待一大会儿，安装完毕后可以查看已经安装的 Ruby 版本：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;rbenv versions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;以上命令列出了安装在 rbenv 中的各 Ruby 版本，前面带有 * 号的表示是当前使用的版本。&lt;/p&gt;

&lt;h2&gt;4. 选择一个 Ruby 版本&lt;/h2&gt;

&lt;p&gt;rbenv 中的 Ruby 版本有三个不同的作用域：全局，本地，当前终端。&lt;/p&gt;

&lt;h3&gt;4.1 设置全局版本&lt;/h3&gt;
&lt;p&gt;全局版本是在没有找到“当前终端”或“本地”作用域的设置时执行。通过以下命令设置：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;rbenv global 1.9.3-p125
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;如果要使用系统原有的 Ruby，则通过 &lt;code&gt;system&lt;/code&gt; 指定：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;rbenv global system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;4.2 设置本地版本&lt;/h3&gt;

&lt;p&gt;“本地”作用域是针对各个项目的，因为不同的项目可以基于不同的 Ruby 版本开发。“本地”作用域通过项目文件夹中的 &lt;code&gt;.rbenv-version&lt;/code&gt; 这个文件进行管理，需要将相应的 Ruby 版本号写入这个文件。这个过程可以通过以下命令执行：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;rbenv local 1.9.2-p290
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;4.3 设置当前终端版本&lt;/h3&gt;

&lt;p&gt;“当前终端”作用域的优先级最高。通过以下命令设置：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;rbenv shell 1.9.2-p290
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;设置完毕后可以通过以下命令进行验证：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;which ruby
rbenv version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;5. 安装 gem&lt;/h2&gt;

&lt;p&gt;使用 rbenv 后，gem 还是按照原有的方式进行安装、升级，只是 gem 的安装路径是在 rbenv 文件夹中当前 Ruby 版本文件夹下。而且，安装带有可执行文件的 gem 后，需要执行一个特别的命令，告诉 rbenv 更新相应的映射关系，这个命令在安装新版本的 Ruby 后也需要执行：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;rbenv rehash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;</content>
 </entry>
 
 <entry>
   <title>新用户角色无法访问后台的解决方法</title>
   <link href="http://about.ac/2012/04/wordpress-solve-custom-role-can-not-access-admin.html"/>
   <updated>2012-04-07T00:00:00+08:00</updated>
   <id>http://about.ac/2012/04/wordpress-solve-custom-role-can-not-access-admin</id>
   <content type="html">
&lt;p&gt;在最近的一个项目中要添加一些新的用户角色，这本是一件很简单的事情，网上有很多教程可以参考。但是在实际的使用过程中却遇到了一个问题：新添加的用户角色所辖的用户无法进入后台，提示权限不足。即便是 WordPress 默认角色中权限最低的“订阅者”也是可以进入后台查看个人资料的。这是怎么回事呢？经过一个多小时的反复实验，最终找到一个解决方法。&lt;/p&gt;

&lt;p&gt;以下是这个解决方法的代码片段，请灵活使用。&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;add_role( &#39;customer&#39;, &#39;Customer&#39;, array() );

$role = &amp;amp;get_role( &#39;customer&#39; );
$role-&amp;gt;add_cap( &#39;custom_cap&#39; );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;add_role&lt;/code&gt; 的用法在&lt;a href=&quot;http://codex.wordpress.org/Function_Reference/add_role&quot;&gt;文档中&lt;/a&gt;说的很明白，第三个参数是给这个新角色指定一个所拥有权限的数组，比如“订阅者”的权限是 &lt;code&gt;array( &#39;read&#39; )&lt;/code&gt;。但是，如果此时指定了权限（比如 &lt;code&gt;read&lt;/code&gt;），这个新角色下的用户是无法进入后台的，也就是出现前面所说的“权限不足”的问题。上述代码片段的第 2、3 行代码可以解决这个问题。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;解决方法&lt;/strong&gt;：在用 &lt;code&gt;add_role&lt;/code&gt; 添加新用户角色时暂不指定任何权限，也就是给其第三个参数赋值一个空数组。然后通过 &lt;code&gt;get_role&lt;/code&gt; 获取刚刚添加的新用户角色，再使用 &lt;code&gt;add_cap&lt;/code&gt; 为其添加相应的权限。如果需要添加多个权限，需要多次调用 &lt;code&gt;add_cap&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;以上是对“新添加的用户角色所辖的用户无法进入后台”这个问题的一种解决方法。这可能是 WordPress 的一个 bug。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>WordPress 的 i18n 和 l10n 流程</title>
   <link href="http://about.ac/2012/02/wordpress-i18n-l10n.html"/>
   <updated>2012-02-12T00:00:00+08:00</updated>
   <id>http://about.ac/2012/02/wordpress-i18n-l10n</id>
   <content type="html">
&lt;p&gt;WordPress 对 i18n 和 l10n 有着很完善的支持。最近在做 V2Press 的时候把 i18n 和 l10n 的流程走了一遍，在此做个记录，备查。&lt;/p&gt;

&lt;p&gt;我的开发环境架设在 Mac OS X 上，所以以下操作均在此系统中完成。Windows 系统请酌情参考。&lt;/p&gt;

&lt;h2&gt;1 安装组件&lt;/h2&gt;

&lt;p&gt;WordPress 的 i18n 和 l10n 是通过 &lt;a href=&quot;http://www.gnu.org/software/gettext/&quot;&gt;GNU gettext&lt;/a&gt; 实现的，因此在进行实际工作之前，首先需要安装相关的组件。&lt;/p&gt;

&lt;h3&gt;1.1 XCode&lt;/h3&gt;

&lt;p&gt;XCode 是 Mac OS 中的常用 IDE 之一，其中也包含了编译相关的库，比如 gcc，在后面会用到 gcc 库，所以首先要安装 XCode。XCode 在购买苹果电脑产品的附带安装盘中有。安装过程中一定要选择安装编译组件。&lt;/p&gt;

&lt;h3&gt;1.2 homebrew&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://mxcl.github.com/homebrew/&quot;&gt;homebrew&lt;/a&gt; – The missing package manager for OS X，用来安装 *nix 系统中常用的库包。homebrew 的安装方法请参照 &lt;a href=&quot;https://github.com/mxcl/homebrew/wiki/installation&quot;&gt;homebrew wiki Installation&lt;/a&gt;。&lt;/p&gt;

&lt;h3&gt;1.3 gettext&lt;/h3&gt;

&lt;p&gt;如果以上所需组件已经安装完毕，那么可以继续以下步骤。打开 Terminal，运行以下命令：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;brew install gettext
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;等待片刻，自动编译完成后即可。gettext 被安装在 &lt;code&gt;/usr/local/Cellar&lt;/code&gt; 目录中。&lt;/p&gt;

&lt;p&gt;为了能够使用 gettext 中相关的命令，还需要修改 &lt;code&gt;$PATH&lt;/code&gt;，打开 &lt;code&gt;~/.profile&lt;/code&gt; 将 &lt;code&gt;/usr/local/Cellar/gettext/0.18.1.1/bin&lt;/code&gt; 加入其中：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;export PATH=&quot;/usr/local/bin:/usr/local/sbin:/usr/local/Cellar/gettext/0.18.1.1/bin:/Developer/usr/bin:$PATH&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;关闭该文件，在 Terminal 中执行以下命令加载新的 &lt;code&gt;$PATH&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;source ~/.profile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;1.4 WordPress i18n 工具&lt;/h3&gt;

&lt;p&gt;WordPress 的 i18n 和 l10n 相关工具放在 svn 中。这些工具可以方便的为我们添加 textdomain，生成 POT 文件。&lt;/p&gt;

&lt;p&gt;在 Terminal 中执行以下命令安装：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;svn co http://svn.automattic.com/wordpress-i18n/tools/trunk/ ./
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;以上命令将这些工具安装到当前目录中。&lt;/p&gt;

&lt;h2&gt;2 工作流程&lt;/h2&gt;

&lt;p&gt;WordPress 的 i18n 和 l10n 的工作流程大概是这样的：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;在主题或插件中为 l10n 留下接口，也就是合理的使用 &lt;code&gt;__()&lt;/code&gt;、&lt;code&gt;_e()&lt;/code&gt;、&lt;code&gt;_x()&lt;/code&gt;、&lt;code&gt;_n()&lt;/code&gt; 等函数；&lt;/li&gt;
  &lt;li&gt;在上述的接口函数中指定 textdomain；&lt;/li&gt;
  &lt;li&gt;生成 POT 文件；&lt;/li&gt;
  &lt;li&gt;生成 PO 文件；&lt;/li&gt;
  &lt;li&gt;本地化，即翻译相应的词条；&lt;/li&gt;
  &lt;li&gt;生成 MO 文件。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;2.1 l10n 接口&lt;/h3&gt;

&lt;p&gt;WordPress 定义了很多 l10n 接口函数，详细用法可以参照 &lt;code&gt;wp-includes/l10n.php&lt;/code&gt;。在主题制作或插件开发的过程中，最好同时加入相应的 textdomain。如果没加入，后期可以通过 WordPress 的 i18n 工具来加入，在 Terminal 执行以下命令：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;php add-textdomain.php -i domain phpfile phpfile ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;2.2 生成 POT 文件&lt;/h3&gt;

&lt;p&gt;主题制作完成或插件开发完成后，便可以通过前期预留的接口将需要本地化的词条摘出来，放到一个特殊的文件中，这个文件就是 POT 模板文件。执行以下命令来生成主题的 POT 文件：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;php makepot.php wp-theme /path/to/theme /path/to/potfile.pot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;如果需要生成插件的 POT 文件，将上述命令中的 &lt;code&gt;wp-theme&lt;/code&gt; 换成 &lt;code&gt;wp-plugin&lt;/code&gt; 即可。更多用法可以执行 &lt;code&gt;php makepot.php&lt;/code&gt; 命令查看。&lt;/p&gt;

&lt;p&gt;一个好的习惯是，将生产的 POT 及下面生成的 PO、MO 文件放在主题文件夹或插件文件夹下的 &lt;code&gt;languages&lt;/code&gt; 目录中。&lt;/p&gt;

&lt;h3&gt;2.3 生成 PO 文件&lt;/h3&gt;

&lt;p&gt;上一步中生成的 POT 只是一个模板，是为各语言本地化提供的一个样板，也就是指明哪些词条需要翻译。真正的翻译过程是在 PO 文件中进行的。&lt;/p&gt;

&lt;p&gt;复制上一步生成的 POT 文件，修改文件后缀为 &lt;code&gt;po&lt;/code&gt;，并重命名为相应语言代码。比如简体中文，其文件名为 &lt;code&gt;zh_CN.po&lt;/code&gt;。注意，插件的 PO 文件命名规则是：&lt;code&gt;plugin-slug-zh_CN.po&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;使用文本编辑器打开 PO 文件，然后进行词条的翻译。&lt;/p&gt;

&lt;h3&gt;2.4 生成 MO 文件&lt;/h3&gt;

&lt;p&gt;PO 文件是给人类用的，机器则需要 MO 文件。执行以下代码生产 MO 文件：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;msgfmt -o zh_CN.mo zh_CN.mo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;作为一个开发者，只需提供 POT 文件即可。而在生产环境中，只需上传相应的 MO 文件即可。&lt;/p&gt;

&lt;h2&gt;3 参考文章&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://codex.wordpress.org/I18n_for_WordPress_Developers&quot;&gt;I18n for WordPress Developers&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://codex.wordpress.org/Translating_WordPress&quot;&gt;Translating WordPress&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://urbangiraffe.com/articles/translating-wordpress-themes-and-plugins/&quot;&gt;Translating WordPress Plugins &amp;amp; Themes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>禁用 WordPress 的 Toolbar</title>
   <link href="http://about.ac/2012/01/disable-wordpress-toolbar.html"/>
   <updated>2012-01-29T00:00:00+08:00</updated>
   <id>http://about.ac/2012/01/disable-wordpress-toolbar</id>
   <content type="html">
&lt;p&gt;WordPress 3.3 之后，用户可以在个人资料页面设置访问前台时是否显式 Toolbar。如果不想把决定权给用户，可以通过一些代码设置前台不显式 Toolbar，具体方法可以参照“&lt;a href=&quot;http://wp.tutsplus.com/tutorials/how-to-disable-the-admin-bar-in-wordpress-3-3/&quot;&gt;Disable Admin Bar in WordPress 3.3&lt;/a&gt;”一文。但是该文提供的方法有个弊端：虽然 Toolbar “不显式”了，但是它所用到的文件（样式表文件和Javascript 脚本文件）一个都没少，而且还多加了一个样式表。&lt;/p&gt;

&lt;p&gt;其实，有个更简单、更有效的方法可以完全去除 Toolbar：使用 &lt;code&gt;show_admin_bar()&lt;/code&gt; 函数。将以下代码片段加入主题的 &lt;code&gt;functions.php&lt;/code&gt; 文件：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;// 全局禁用 Toolbar
show_admin_bar( false );

// 只在前台禁用 Toolbar
if ( !is_admin() )
    show_admin_bar( false );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;可以根据需要选择禁用全局还是只在前台禁用。&lt;/p&gt;

&lt;p&gt;这个方法的好处是，1）代码量少；2）禁用的更彻底，Toolbar 所需的样式表文件和 Javascript 脚本文件均不会加载。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>修改基于日期的归档页面的 URL 结构</title>
   <link href="http://about.ac/2012/01/alter-wordpress-date-archives-url-structure.html"/>
   <updated>2012-01-29T00:00:00+08:00</updated>
   <id>http://about.ac/2012/01/alter-wordpress-date-archives-url-structure</id>
   <content type="html">
&lt;p&gt;WordPress 的链接格式是很灵活的，几乎可以任意的定制 URL 的格式。如果你所用的服务器支持 mod_rewrite 的话就可以设置“精美”的 URL 格式。WordPress 默认提供了 5 种 URL 格式供选择，而且还可以自由定制。WordPress 还提供了针对“分类”和“标签”的 rewrite base 设置选项。&lt;/p&gt;

&lt;p&gt;默认的，WordPress 的分类 URL 格式是 &lt;code&gt;host/category/xxx/&lt;/code&gt;，其中 &lt;code&gt;category&lt;/code&gt; 就是 rewrite base，这是可定制的部分，例如本站分类 URL 的 rewrite base 就设置为“go”了。同理，可以设置标签 URL 的 rewrite base。&lt;/p&gt;

&lt;p&gt;在设置 WordPress 链接格式的时候我遇到一个问题。我设置的 URL 格式是 &lt;code&gt;/t/%postname%&lt;/code&gt;，rewrite base 是固定的，不再是占位符，这时文章页面的 URL 是没问题的，页面的 URL 是没问题的，分类、标签的页面 URL 也是没问题的，但是，基于时间的页面就出现问题了，其 URL 结构变成 &lt;code&gt;/t/2012/01&lt;/code&gt; 的格式，这个“t”是我不需要的。&lt;/p&gt;

&lt;p&gt;查看 &lt;code&gt;wp-includes/rewrite.php&lt;/code&gt; 第 862 行 &lt;code&gt;get_date_permastruct()&lt;/code&gt; 函数的定义可以发现，最终返回的结果加入了 &lt;code&gt;$front&lt;/code&gt;，这就是“t”为什么会出现的原因。&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;....
$this-&amp;gt;date_structure = $front . $date_endian;
....
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;那么只需要覆盖类 &lt;code&gt;WP_Rewrite&lt;/code&gt; 的 &lt;code&gt;$date_structure&lt;/code&gt; 变量即可。将以下代码片段写入主题的 &lt;code&gt;functions.php&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;codeblock text&quot;&gt;&lt;pre&gt;&lt;code class=&quot;highlight&quot;&gt;function ac_re_rewrite() {
    global $wp_rewrite;

    // Not need the $wp_rewrite-&amp;gt;front
    $wp_rewrite-&amp;gt;date_structure = &#39;%year%/%monthnum%/%day%&#39;;

    $wp_rewrite-&amp;gt;flush_rules();
}
add_action( &#39;init&#39;, &#39;ac_re_rewrite&#39; );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;如此一来，基于日期的存档 URL 格式就定制好了，利用同样的方法还可以定制作者归档等页面的 URL 格式。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>HTML 的未来一片光明</title>
   <link href="http://about.ac/2010/07/brightly-html.html"/>
   <updated>2010-07-31T00:00:00+08:00</updated>
   <id>http://about.ac/2010/07/brightly-html</id>
   <content type="html">
&lt;p&gt;本文是对《&lt;a href=&quot;http://book.douban.com/subject/4903307/&quot;&gt;HTML5 For Web Designer&lt;/a&gt;》一书的评论，原载于豆瓣。&lt;/p&gt;

&lt;p&gt;这本书不厚，只有85页的内容，却卖到了$18，A List Apart 的人还真的会挣 Web Designers 的钱。此书由 A List Apart 的 A Book Apart 出版，国内应该是买不到的，幸好国外的科技类图书大都会有 PDF 电子版本，所以才能在第一时间内读到这本书。&lt;/p&gt;

&lt;p&gt;这不是一本告诉你 how-to，而是告诉你 what it is 的书，正如本书宣传中所提到的，HTML5 spec 足达 500 多页，加上晦涩难懂的专业术语及表达方式，一般人还真的不会去看。但是，不看，一些细节是永远不会知道的。&lt;/p&gt;

&lt;p&gt;前段时间我就利用业余时间看了一下 HTML 4.01 的 spec 文档，这才算系统的了解了 HTML。这是需要一定勇气的。&lt;/p&gt;

&lt;p&gt;这本书恰好就是给你打气的。&lt;/p&gt;

&lt;p&gt;这本书告诉你：HTML 的未来一片光明，HTML5 的未来更是美好的。&lt;/p&gt;

&lt;p&gt;尽管在美好之前有各种利益集团的博弈，但这对 Web Designers 始终都是好事，毕竟最终我们会用到更好的 markup language。其中的斗争不是最终使用者需要关注的，偶尔八一下就好。&lt;/p&gt;

&lt;p&gt;不论是排版、印刷，怎么看，这本书都像一个产品手册，其行文手法更像。由于是出自 ClearLeft 的 Jeremy Keith 之笔，抛掉这些不满，我还是相信了 Keith 的话，HTML5 的明天更美好。只是要等到 2022 年，似乎很遥远。&lt;/p&gt;
</content>
 </entry>
 

</feed>
