<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title><![CDATA[I'm SErHo]]></title>
    <updated>2019-12-16T20:00:00Z</updated>
    <id><![CDATA[https://serholiu.com/]]></id>
    <link rel="alternate" href="https://serholiu.com/" title="I'm SErHo" type="text/html"/>
    <link rel="self" href="https://serholiu.com/blog/feed" title="I'm SErHo" type="application/atom+xml"/>
    <author><name><![CDATA[I'm SErHo]]></name></author>
    <entry>
      <id><![CDATA[http://https://serholiu.com/beijing-snow]]></id>
      <title type="text"><![CDATA[北京的雪]]></title>
      <link href="http://https://serholiu.com/beijing-snow" rel="alternate" type="text/html"/>
      <published>2019-12-16T20:00:00Z</published>
      <content type="html">
       <![CDATA[<p>北京是北方吗？北国风光，万里雪飘，然而不说万里，就连雪飘都很少见。不过以往几年都是春节过后才到来的雪今年却提前许多，前几天晚上下班之时便悄悄到来，雪不是很大，虽然在空中洋洋洒洒，到地上却不成气候。但哪能对它有那么多要求呢，它的到来就是一件开心的事。走在下班路上，让雪停在自己的头发上、衣服上、鞋上，仅以此表达一个南方人对它的渴望。</p>
<p>成都是南方吗？曾也下过一些雪，南方下雪可以说是灾难，潮湿的空气加上零下的温度，便是无处不在的冰凌，毁掉高压输电线，让道路难以通行，但雪是真的很美。飘落的雪花就是童话世界的布景，让我可以假装幼稚，或者说不必掩藏幼稚。来到北方可以说是为生活奔波，但能见到更大更多的雪也算是一种意外之财，然而事与愿违，或许北京还是不够北吧。</p>
<p>意外的是距上次不久，更多的雪到来了，站在路边等着班车，一队人都任由雪落在身上。旁边的马路上车流不息，没有积雪，全都化成了泥水，为什么积雪看起来那么干净白皙，一旦融化便成这样。车流较少的小道最是漂亮，路边早已落完叶子的树枝上堆满积雪，路上也一片雪白，偶尔的几道车辙印，在繁华的闹市能看到这么干净的雪景也实属不易，这或许是早起的福利吧。</p>
<figure><img src="https://static.kelsiz.com/blog/20191216-01.jpeg" alt="雪中行人和车" /></figure>
<p>公司前有一个非常大的草坪，此时已经变成雪原，大雪还在茫茫而落，天色昏暗，露天的座椅、遮阳伞、小雕像们无一例外，都铺上了一层雪，比以往有趣许多。公司旁的小公园应该会更美吧，拿起相机便走了进去。</p>
<figure><img src="https://static.kelsiz.com/blog/20191216-03.jpeg" alt="小公园" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20191216-02.jpeg" alt="踩过雪地" /></figure>
<p>这几天本来是落叶季，公园的树林地上本来铺着一层落叶和枯枝，现在已经完全看不见，只有在踩上雪然后脚陷下去时才注意到，抬头看雪从高大树缝间纷纷落下，偶被惊动的鸟从一颗树梢飞到另一颗，树梢上的积雪受到惊吓便纷纷抖落下来。走过林地，留下一串脚印，木亭、落雪、柳树，还真有点传统山水画的味道。</p>
<figure><img src="https://static.kelsiz.com/blog/20191216-04.jpeg" alt="木亭、落雪、柳树" /></figure>
<p>在公园里走着，瞎拍着照，真是异常的高兴，可以说这是第一次见到这么大的雪，而且持续时间够久，至少积雪够厚，真的像电影中那样。</p>
<figure><img src="https://static.kelsiz.com/blog/20191216-05.jpeg" alt="大早冒雪盘鹰的人" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20191216-06.jpeg" alt="上班的人" /></figure>
<p>不过手冻得挺僵的，在公园转完一圈，便赶紧回到公司里，此时上班的人越来越多，地上也多了车辙印和泥水。</p>
<p>还好，雪还没有变小的迹象。</p>
]]>
      </content>
    </entry>
    <entry>
      <id><![CDATA[http://https://serholiu.com/macao-hk-photography]]></id>
      <title type="text"><![CDATA[澳门香港掠影]]></title>
      <link href="http://https://serholiu.com/macao-hk-photography" rel="alternate" type="text/html"/>
      <published>2018-12-18T22:33:00Z</published>
      <content type="html">
       <![CDATA[<p>曾在珠海工作过两年多，离澳门一步之遥，到香港也很近，不过终因人太懒这两个地方都没有去过。说实话，想象之中若不是去赌钱澳门也没啥可玩的，若不是去买便宜的苹果产品香港也没啥好玩的，除了香港电影中耳熟能详的地名能去到有点意思外，似乎就真的是去不去都无所谓。</p>
<p>这年倒是因公司的团建去了这两个城市，匆匆忙忙走马观花，大部分时间都在睡觉和逛免税店，所谓旅游对我来说也不过只是换了个地方睡觉而已。澳门的各种大酒店都在老城区外的人工填海造岛上，酒店里面就是赌场商场，没有什么意思，即使在酒店里面建个威尼斯，也是显得有点尴尬而已。</p>
<figure><img src="https://static.kelsiz.com/blog/20181218-0.jpg" alt="澳门新街道" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20181218-1.jpg" alt="澳门各种酒店" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20181218-2.jpg" alt="酒店内的威尼斯" /><figcaption>酒店内的威尼斯</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20181218-12.jpg" alt="上山路" /></figure>
<p>澳门老城区倒是十分有意思，因为被葡萄牙殖民的历史，建筑风格都很葡萄牙，颜色鲜明的外墙，精致的窗栅栏。</p>
<figure><img src="https://static.kelsiz.com/blog/20181218-3.jpg" alt="颜色鲜明的建筑" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20181218-4.jpg" alt="颜色鲜明的建筑" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20181218-5.jpg" alt="颜色鲜明的建筑" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20181218-6.jpg" alt="大三巴牌坊" /><figcaption>可以说是澳门的唯一景点</figcaption></figure><p>老城区作为世界文化遗产，以前的风貌还是保留下来的，但是街道狭窄，太多老房子需要维护，走在街上时不时就是一堆脚手架。道路都是按照以前马车的标准修建的，随着汽车越来越多，显得非常拥挤，很多路都只能容许一辆车通过。在这里随处可见新老的交映，真是一种不同的景色。</p>
<figure><img src="https://static.kelsiz.com/blog/20181218-7.jpg" alt="炮轰新葡京" /><figcaption>炮轰新葡京</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20181218-8.jpg" alt="澳门老城区" /><figcaption>澳门和对岸的珠海</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20181218-9.jpg" alt="老街道" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20181218-10.jpg" alt="灭鼠警告" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20181218-11.jpg" alt="摩拜单车" /><figcaption>偷渡被抓的摩拜单车</figcaption></figure><p>香港和澳门其实差别不大，受面积所限，街道狭窄，车多楼密，尤其是尖沙咀一带，加上游客众多，喧嚣无比。街道虽窄，好在遵守交通规则的人还是很多的，香港汽车的行驶速度都非常快。走在狭窄的人行道，两旁林立的高楼，周边快速而过的高且窄的公共汽车，真是在夹缝中生存。</p>
<figure><img src="https://static.kelsiz.com/blog/20181218-13.jpg" alt="沙尖咀的街道" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20181218-14.jpg" alt="沙尖咀的街道" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20181218-24.jpg" alt="沙尖咀的街道" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20181218-23.jpg" alt="沙尖咀的街道" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20181218-15.jpg" alt="沙尖咀的街道" /></figure>
<p>中环和尖沙咀差别还是挺大的，就像澳门的新老城区的差别，全是较新的高楼大厦，组成了维多利亚港的美丽景色。</p>
<figure><img src="https://static.kelsiz.com/blog/20181218-16.jpg" alt="中环不知名的雕像" /><figcaption>中环不知名的雕像</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20181218-17.jpg" alt="看起来很有历史的建筑" /><figcaption>看起来很有历史的建筑</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20181218-18.jpg" alt="中环的高楼" /><figcaption>中环的高楼</figcaption></figure><p>坐公共汽车绕着盘山路而上，来到太平山顶，可以看到著名的维多利亚港，尤其是夜景。</p>
<figure><img src="https://static.kelsiz.com/blog/20181218-20.jpg" alt="太平山顶" /><figcaption>太平山顶</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20181218-27.jpg" alt="维多利亚港" /><figcaption>维多利亚港</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20181218-19.jpg" alt="维多利亚港夜景" /><figcaption>维多利亚港夜景</figcaption></figure><p>临近结束，也只是在酒店睡觉和逛免税店的空隙中看看而已，没有去各种香港电影中的著名景点打卡还是稍微显得有些遗憾，不过身临其境的感受还是和看电影有那么些差别，比如喧嚣比如拥促，倒是红色出租车，格外真实。</p>
<figure><img src="https://static.kelsiz.com/blog/20181218-25.jpg" alt="不知什么花" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20181218-21.jpg" alt="夜晚的羽毛球" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20181218-22.jpg" alt="指示牌" /><figcaption>漂亮的路牌</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20181218-26.jpg" alt="红色出租车" /></figure>
]]>
      </content>
    </entry>
    <entry>
      <id><![CDATA[http://https://serholiu.com/2017-photography]]></id>
      <title type="text"><![CDATA[一年浮光掠影记]]></title>
      <link href="http://https://serholiu.com/2017-photography" rel="alternate" type="text/html"/>
      <published>2017-12-23T22:33:00Z</published>
      <content type="html">
       <![CDATA[<p>这一年又将在平平淡淡、无无聊聊、浑浑噩噩之中过去。此时心中非常平静，毕竟和以往一样，也大概算正常的一年。点点滴滴如过眼云烟早已记不清，只能浮光掠影般的叨叨一下。</p>
<p>年初跟着公司年会去上海迪士尼转悠了一趟，对我而言也只有公司年会的“号召”下才出去玩玩，平时一般宅在家中，毕竟太懒！</p>
<figure><img src="https://static.kelsiz.com/blog/20171223-0.jpg" alt="迪士尼城堡" /><figcaption>不拍一张城堡鬼知道你去的是迪士尼</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20171223-1.jpg" alt="迪士尼米老鼠" /><figcaption>有米老鼠其实也可以</figcaption></figure><p>迪士尼毕竟主要还是给小朋友玩的，对于我这种中年朋友而言，玩耍的项目还是显得比较无聊。还是各种电影动画片里面的玩意儿比较有意思。</p>
<figure><img src="https://static.kelsiz.com/blog/20171223-2.jpg" alt="迪士尼加勒比海盗" /><figcaption>感受一下加勒比海盗</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20171223-3.jpg" alt="迪士尼索道" /><figcaption>感觉比较有意思的项目</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20171223-4.jpg" alt="迪士尼钢铁侠" /><figcaption>反浩克装甲</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20171223-6.jpg" alt="迪士尼 X 战机" /><figcaption>X 战机，拍糊了</figcaption></figure><p>早上中午下午，基本上在排队，以至于没玩几个项目天色便晚了，于是便去在冷风中等着观看烟火表演。</p>
<figure><img src="https://static.kelsiz.com/blog/20171223-5.jpg" alt="迪士尼城堡" /><figcaption>夜晚的城堡</figcaption></figure><p>虽然无聊了一点，但现在看来也算是这一整年唯一一次出去玩耍。时间一晃就到了九月，开始准备在北京享受第二个秋天。自从毕业去珠海工作，已经两年没有秋天的概念，去年来到北京，某天下班路上突然的凉风，直接把我拉回到了大四那年的秋季。而北京这种「愿把寿命的三分之二折去，换得一个三分之一的零头」来留住的秋天，真是轻松自由至极。当然我这种懒人，是享受不了「陶然亭的芦花，钓鱼台的柳影，西山的虫唱，玉泉的夜月，潭柘寺的钟声」的，只能在附近看看。</p>
<figure><img src="https://static.kelsiz.com/blog/20171223-7.jpg" alt="走廊" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20171223-8.jpg" alt="出镜" /><figcaption>真人太丑看个影子得了</figcaption></figure><p>公司附近的公园居然种了一片向日葵，虽然说这一年平平淡淡无无聊聊，但其实前半年还是有那么一点小波澜的，看着一片向日葵，心中既是狂喜也是悲伤。</p>
<figure><img src="https://static.kelsiz.com/blog/20171223-9.jpg" alt="向日葵" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20171223-10.jpg" alt="向日葵" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20171223-11.jpg" alt="共享单车 ofo" /><figcaption>共享经济腾飞的一年</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20171223-12.jpg" alt="公园小景" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20171223-13.jpg" alt="人造瀑布" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20171223-14.jpg" alt="窗中公园" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20171223-15.jpg" alt="爬山虎" /><figcaption>初秋的爬山虎</figcaption></figure><p>北京入秋后很少下雨，天高气爽。想起国庆回家度过的整个假期，在飞机降落时看到太阳，等到下一次看到太阳时已是飞机起飞的时候，成都平原被很厚的云层盖住，时不时的一点小雨。</p>
<figure><img src="https://static.kelsiz.com/blog/20171223-16.jpg" alt="雨" /><figcaption>雨后没有晴</figcaption></figure><p>北京的秋，还是得深秋之时最好看。小学课本里飘着的香山红叶一直落在我这个成长在帝都千里之外的人心中某个角落。虽来北京已接近两年，然而人懒一直未能去香山，好在附近也有那么一点红色。</p>
<figure><img src="https://static.kelsiz.com/blog/20171223-17.jpg" alt="爬山虎" /><figcaption>深秋的爬山虎</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20171223-19.jpg" alt="爬山虎" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20171223-18.jpg" alt="秋树" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20171223-20.jpg" alt="广场" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20171223-21.jpg" alt="箱屋" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20171223-22.jpg" alt="秋叶" /><figcaption>枫叶红于二月花</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20171223-23.jpg" alt="盘鹰" /><figcaption>盘鹰晨练的人</figcaption></figure><p>人懒也就与美景无缘，只能在些片段之中聊胜于无。在北京深秋之时回了珠海一次，珠海夏天的炎热还没开始消散，干净的空气让街道异常整洁。相比北方来说，南方的冬天太残忍，幸好这个冬天不用在南方度过。</p>
<figure><img src="https://static.kelsiz.com/blog/20171223-24.jpg" alt="椰树" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20171223-26.jpg" alt="登山" /><figcaption>南方的淡蓝天，北方的深蓝天</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20171223-27.jpg" alt="十字路口" /><figcaption>以前上下班路过的十字路口</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20171223-28.jpg" alt="公司楼顶远望" /><figcaption>公司楼顶远眺</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20171223-29.jpg" alt="情侣路夜景" /><figcaption>太晚，瞎拍情侣路夜景</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20171223-30.jpg" alt="拱北车站" /></figure>
<p>相比南方的城市，北京还是显得太土。最后一张胡乱 PS 过的还未下雪的冬景。</p>
<figure><img src="https://static.kelsiz.com/blog/20171223-31.jpg" alt="广场景观" /></figure>
]]>
      </content>
    </entry>
    <entry>
      <id><![CDATA[http://https://serholiu.com/multiprocessing-pool-block]]></id>
      <title type="text"><![CDATA[Python 多进程 Pool 永久阻塞]]></title>
      <link href="http://https://serholiu.com/multiprocessing-pool-block" rel="alternate" type="text/html"/>
      <published>2016-04-04T13:00:00Z</published>
      <content type="html">
       <![CDATA[<p>先说结论，使用 <a href="https://docs.python.org/2.7/library/multiprocessing.html#module-multiprocessing.pool">multiprocessing.Pool</a> 时应该注意确保工作进程不要因为严重的错误(如段错误)和人为的 kill 而挂掉，或者抛出不能被 <code>Exception</code> 捕获的异常，比如调用 <code>sys.exit</code>. 如果出现上述情况，主进程会永远阻塞在 <code>pool.join()</code> 上。</p>
<p>最近一个运行良久的程序突然间经常无法退出，开始怀疑某个子进程卡住，不过通过 <code>ps</code> 发现所有子进程已经退出，只是还未被父进程回收，显然父进程阻塞在对每个子进程调用 <code>join</code> 之前了。</p>
<div class="highlight"><pre><span></span><span class="gp">$ </span>ps<span class="w"> </span>--ppid<span class="o">=</span><span class="m">8772</span>
<span class="go">  PID TTY          TIME CMD</span>
<span class="go"> 8773 pts/1    00:00:00 python &lt;defunct&gt;</span>
<span class="go"> 8774 pts/1    00:00:00 python &lt;defunct&gt;</span>
<span class="go"> 8775 pts/1    00:00:00 python &lt;defunct&gt;</span>
<span class="go"> 8776 pts/1    00:00:00 python &lt;defunct&gt;</span>
<span class="go"> 8777 pts/1    00:00:00 python &lt;defunct&gt;</span>
</pre></div>
<p>因为跑在 Python2.6(没错，就是这么古老的版本) 上，所以这里拿 2.6 的代码看看，根据 <a href="https://hg.python.org/cpython/file/2.6/Lib/multiprocessing/pool.py#l341"><code>pool.join</code></a>，可能是 <code>_task_handler</code> 和 <code>_result_handler</code> 两者或其中一者没有退出，先用 gdb 进去看看现场：</p>
<div class="highlight"><pre><span></span><span class="gp gp-VirtualEnv">(gdb)</span> <span class="go">info threads</span>
<span class="go">  2 Thread 0x7f4062891700 (LWP 8779)  0x00007f406de6982d in read () from /lib64/libpthread.so.0</span>
<span class="go">* 1 Thread 0x7f406e633700 (LWP 8772)  0x00007f406de68a00 in sem_wait () from /lib64/libpthread.so.0</span>
</pre></div>
<p>只有两个线程，主线程没啥可说的, 关键是线程2，看到 <code>read</code> 调用感觉多半是阻塞在这里：</p>
<div class="highlight"><pre><span></span><span class="gp gp-VirtualEnv">(gdb)</span> <span class="go">thread 2</span>
<span class="gp">[Switching to thread 2 (Thread 0x7f4062891700 (LWP 8779))]#</span><span class="m">0</span><span class="w">  </span>0x00007f406de6982d<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="nb">read</span><span class="w"> </span><span class="o">()</span><span class="w"> </span>from<span class="w"> </span>/lib64/libpthread.so.0
<span class="gp gp-VirtualEnv">(gdb)</span> <span class="go">bt</span>
<span class="gp">#</span><span class="m">0</span><span class="w">  </span>0x00007f406de6982d<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="nb">read</span><span class="w"> </span><span class="o">()</span><span class="w"> </span>from<span class="w"> </span>/lib64/libpthread.so.0
<span class="gp">#</span><span class="m">1</span><span class="w">  </span>0x00007f40667810bb<span class="w"> </span><span class="k">in</span><span class="w"> </span>??<span class="w"> </span><span class="o">()</span><span class="w"> </span>from<span class="w"> </span>/usr/lib64/python2.6/lib-dynload/_multiprocessing.so
</pre></div>
<p>调用栈没啥可用信息， <a href="https://hg.python.org/cpython/file/2.6/Lib/multiprocessing/pool.py#l215"><code>_task_handler</code></a> 里面并没有涉及需要调用 <code>read</code> 的操作，倒是 <a href="https://hg.python.org/cpython/file/2.6/Lib/multiprocessing/pool.py#l254"><code>_result_handler</code></a> 需要从 Queue 里面读取数据，而这个 <a href="https://hg.python.org/cpython/file/2.6/Lib/multiprocessing/queues.py#l327"><code>SimpleQueue</code></a> 是由 pipe 实现的，难道是阻塞在了读 pipe 上？先看看打开的文件：</p>
<div class="highlight"><pre><span></span><span class="gp">$ </span>lsof<span class="w"> </span>-p<span class="w"> </span><span class="m">8772</span>
<span class="go">....</span>
<span class="go">python  8772 root    0u   CHR  136,1      0t0      4 /dev/pts/1</span>
<span class="go">python  8772 root    1u   CHR  136,1      0t0      4 /dev/pts/1</span>
<span class="go">python  8772 root    2u   CHR  136,1      0t0      4 /dev/pts/1</span>
<span class="go">python  8772 root    3r  FIFO    0,8      0t0  20370 pipe</span>
<span class="go">python  8772 root    4w  FIFO    0,8      0t0  20370 pipe</span>
<span class="go">python  8772 root    5r  FIFO    0,8      0t0  20373 pipe</span>
<span class="go">python  8772 root    6w  FIFO    0,8      0t0  20373 pipe</span>
</pre></div>
<p>具体 result handler 读的是哪一个 pipe，先在 gdb 中看看 <code>read</code> 调用的第一个参数：</p>
<div class="highlight"><pre><span></span><span class="gp gp-VirtualEnv">(gdb)</span> <span class="go">info all-registers</span>
<span class="go">...</span>
<span class="go">rdx            0x4      4</span>
<span class="go">rsi            0x7f406289028c   139914507780748</span>
<span class="go">rdi            0x5      5</span>
</pre></div>
<p>这里的 rdi 寄存器中的值就是 read 的 fd，读端是 5，想必写端就是 6 了，result handler 中有两处读 pipe，到底阻塞在哪处还不是很清楚，不过可以试试朝里面写点东西，看看结果：</p>
<div class="highlight"><pre><span></span><span class="gp gp-VirtualEnv">(gdb)</span> <span class="go">call write(6, &quot;hello&quot;, 5)</span>
<span class="gp">$</span><span class="nv">1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">5</span>
<span class="gp gp-VirtualEnv">(gdb)</span> <span class="go">c</span>
<span class="go">Continuing.</span>
<span class="go">[Thread 0x7f4062891700 (LWP 8779) exited]</span>

<span class="go">Program exited normally.</span>
</pre></div>
<p>啊哈，退出了，看来前面的猜测是对的。Python 进程打印出来的日志：</p>
<div class="highlight"><pre><span></span><span class="x">Exception in thread Thread-2:</span>
<span class="gt">Traceback (most recent call last):</span>
  File <span class="nb">&quot;/usr/lib64/python2.6/threading.py&quot;</span>, line <span class="m">532</span>, in <span class="n">__bootstrap_inner</span>
<span class="w">    </span><span class="bp">self</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
  File <span class="nb">&quot;/usr/lib64/python2.6/threading.py&quot;</span>, line <span class="m">484</span>, in <span class="n">run</span>
<span class="w">    </span><span class="bp">self</span><span class="o">.</span><span class="n">__target</span><span class="p">(</span><span class="o">*</span><span class="bp">self</span><span class="o">.</span><span class="n">__args</span><span class="p">,</span> <span class="o">**</span><span class="bp">self</span><span class="o">.</span><span class="n">__kwargs</span><span class="p">)</span>
  File <span class="nb">&quot;/usr/lib64/python2.6/multiprocessing/pool.py&quot;</span>, line <span class="m">282</span>, in <span class="n">_handle_results</span>
<span class="w">    </span><span class="n">task</span> <span class="o">=</span> <span class="n">get</span><span class="p">()</span>
<span class="gr">MemoryError</span>
</pre></div>
<p>显然，是阻塞在了<a href="https://hg.python.org/cpython/file/2.6/Lib/multiprocessing/pool.py#l281">第二个读 pipe</a>上。</p>
<h3>Pool 执行与结果返回</h3>
<p>通常使用 Pool 的方法是指定进程池的大小，再将数个任务扔进去，最后等待结果，典型的用法如下：</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">run</span><span class="p">():</span>
    <span class="n">pool</span> <span class="o">=</span> <span class="n">Pool</span><span class="p">(</span><span class="n">processes</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">xrange</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
        <span class="n">pool</span><span class="o">.</span><span class="n">apply_async</span><span class="p">(</span><span class="n">worker</span><span class="p">,</span> <span class="p">(</span><span class="n">i</span><span class="p">,))</span> <span class="c1"># 这里可以不使用结果</span>
    <span class="n">pool</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
    <span class="n">pool</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">run</span><span class="p">()</span>
</pre></div>
<p><a href="https://hg.python.org/cpython/file/2.6/Lib/multiprocessing/pool.py#l390"><code>ApplyResult</code></a> 用来存放结果，每个结果有个唯一的 job id，<a href="https://hg.python.org/cpython/file/2.6/Lib/multiprocessing/pool.py#l86"><code>cache</code></a> 以 job id 来索引结果，task handler 会将 job id 等放到 task 队列中，当工作进程做完一个 job 后，同样会将 job id 和结果放到 result 队列，result handler 接收后，会从 cache 中将这个 job id 删除，直到 cache 为空。</p>
<p>由此可见，上面阻塞住的情况就是有部分工作进程并没有将结果<a href="https://hg.python.org/cpython/file/2.6/Lib/multiprocessing/pool.py#l71">放到 result 队列</a>，要不是放结果之前的异常没有捕获到导致子进程退出，就是子进程被 kill 掉了，Pool 的实现并没有考虑到这个情况。在 Python2.7 中，Pool 会新增一个进程用来取代异常退出的进程，然而这并没有什么用，只要有 job id 丢失，就会导致 cache 无论如何都不会为空，在这种情况下还会导致所有<a href="https://hg.python.org/cpython/file/2.7/Lib/multiprocessing/pool.py#l325">子进程都不会退出</a>，问题查起来将会更麻烦。</p>
<p>当然，这不是实现的问题，谁叫工作函数会抛出不可被 <code>Exception</code> 捕获的异常，或者被 kill 掉呢(没错，后来发现是工作函数中有一处处理大文件时会出现 Segment fault，然后无情的被系统 kill 掉了，这个锅 Python 得背吧？“这个锅咋不背，谁叫你还在用 <a href="https://hg.python.org/cpython/rev/0f520ed901a5">2.6</a> 呢！“)。要解决子进程挂掉导致 cache 不为空这种情况应该非常麻烦，怎么知道哪个工作进程获取了哪个 job id 呢，如果不知道，主进程将毫无办法。</p>
<p>试了下 Python3 中的 <a href="https://docs.python.org/3/library/concurrent.futures.html#processpoolexecutor">ProcessPoolExecutor</a>，这个在某个工作进程挂掉后，会停掉所有工作进程，然后抛个异常退出，至少不会永远的阻塞在那里了。</p>
]]>
      </content>
    </entry>
    <entry>
      <id><![CDATA[http://https://serholiu.com/japan-korea-glimpse]]></id>
      <title type="text"><![CDATA[日韩匆匆一瞥]]></title>
      <link href="http://https://serholiu.com/japan-korea-glimpse" rel="alternate" type="text/html"/>
      <published>2016-02-11T23:00:00Z</published>
      <content type="html">
       <![CDATA[<p>抱着公司的大腿，搭着邮轮来了一次历时五天四夜的海上“瞌睡”之旅，在睡觉的空闲时间顺便跟着队伍在轮船停靠的城市逛一下。本来主题就是“海上之旅”，重点其实还是船上的活动，在两个城市停靠，基本上是走马观花，时间非常急迫的，更不用说大部分时间是需要去逛购物中心的。</p>
<figure><img src="https://static.kelsiz.com/blog/20160211-6.jpg" alt="窗外" /><figcaption>睁眼看船外一成不变的风景</figcaption></figure><p>最先停靠的韩国釜山，原定是济州岛的，由于天气原因无法靠岸而作罢。釜山下着小雨，天气阴沉，感觉挺普通的一个城市。</p>
<figure><img src="https://static.kelsiz.com/blog/20160211-23.jpg" alt="釜山第一瞥" /><figcaption>釜山第一瞥</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20160211-20.jpg" alt="釜山第二瞥" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20160211-22.jpg" alt="歌斯达大西洋号" /><figcaption>停靠在港口的邮轮</figcaption></figure><p>因为下着小雨，气温也偏低，时间也不足，釜山基本上就这样瞥过了。第二站是日本福冈，天公作美，天气十分好，再加上福冈这个城市非常美丽，感觉要比釜山好得多。</p>
<figure><img src="https://static.kelsiz.com/blog/20160211-4.jpg" alt="日本福冈港口" /><figcaption>日本第一印象</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20160211-1.jpg" alt="福冈街道" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20160211-18.jpg" alt="福冈街道" /></figure>
<p>一周以来第一次全身浸在阳光之中，此时正值中午，太宰府天满宫游人众多，第一次走在如此干净的街道上，一切都十分新鲜。</p>
<figure><img src="https://static.kelsiz.com/blog/20160211-16.jpg" alt="日本神社" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20160211-11.jpg" alt="太宰府天满宫" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20160211-15.jpg" alt="太宰府天满宫" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20160211-14.jpg" alt="太宰府天满宫" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20160211-0.jpg" alt="太宰府天满宫" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20160211-19.jpg" alt="太宰府天满宫" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20160211-7.jpg" alt="太宰府天满宫" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20160211-2.jpg" alt="许愿卡片" /><figcaption>这个桐原己沙家是开当铺的吗</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20160211-27.jpg" alt="太宰府天满宫" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20160211-9.jpg" alt="太宰府天满宫" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20160211-17.jpg" alt="日本神社2" /></figure>
<p>游览的时间极短，只能匆匆拍拍照后便往回赶，此时路边的美食与玩物只能看看。天满宫类似国内的孔庙，很多学生在老师的带领下来此求取一份幸运。</p>
<figure><img src="https://static.kelsiz.com/blog/20160211-8.jpg" alt="穿制服的学生妹" /><figcaption>穿制服的学生妹</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20160211-24.jpg" alt="穿和服的女人" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20160211-26.jpg" alt="穿制服的学生妹" /></figure>
<p>免税店并没有什么有意思的东西，就我而言，还是想以不多的时间感受一下福冈之美，这种就如在漫画之中徜徉的感觉。</p>
<figure><img src="https://static.kelsiz.com/blog/20160211-12.jpg" alt="城市的一角" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20160211-13.jpg" alt="城市的一角" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20160211-3.jpg" alt="停靠在福冈的邮轮" /><figcaption>远望邮轮</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20160211-5.jpg" alt="眺望福冈" /><figcaption>到甲板上看看</figcaption></figure><figure><img src="https://static.kelsiz.com/blog/20160211-25.jpg" alt="眺望福冈" /></figure>
<figure><img src="https://static.kelsiz.com/blog/20160211-10.jpg" alt="眺望福冈" /></figure>
<p>邮轮驶离港口，什么时候重游日本呢？</p>
]]>
      </content>
    </entry>
    <entry>
      <id><![CDATA[http://https://serholiu.com/go-http-client-keepalive]]></id>
      <title type="text"><![CDATA[Go HTTP Client 持久连接]]></title>
      <link href="http://https://serholiu.com/go-http-client-keepalive" rel="alternate" type="text/html"/>
      <published>2015-09-05T13:17:00Z</published>
      <content type="html">
       <![CDATA[<p>调用 Go 的 HTTP Client 的 <a href="http://golang.org/pkg/net/http/#Get">Get\Post</a> 之类的方法时，默认是开启 HTTP keepalive 的，不过直接使用还是会遇到一些情况导致持久连接失效。首先，<a href="http://golang.org/pkg/net/http/#Client">Client</a> 构造好 HTTP 请求后，利用 <code>Transport</code> 来发送请求并等待结果，默认使用 <a href="http://golang.org/pkg/net/http/#RoundTripper"><code>DefaultTransport</code></a> 来实现，大多数情况下，自定义 Client 时，配置一下自带的 <a href="http://golang.org/pkg/net/http/#Transport">Transport</a> 即可。</p>
<p><a href="https://github.com/golang/go/blob/release-branch.go1.5/src/net/http/transport.go">transport</a> 主要围绕着 <code>persistConn</code> 来实现，通过当前请求的 <code>proxy, scheme, addr</code> 作为 Key，对已经建立的连接进行缓存，新的请求来时，先从缓存中取一个连接，如果没有，再新发起一个连接。按照 Go 的基本法，毫无疑问会有两个 goroutine 来分别处理连接上的读和写，然后各种 channel 就开始飞来飞去，于是便让人深思这真的会比基于事件回调的实现简单吗。</p>
<p>好了，这里还是简单写点代码浅显的试验一下可能会导致持久连接失效的一些情况。</p>
<h3>准备工作</h3>
<p>在发送请求建立 TCP 连接时输出一些提示消息，这样便可以确定是否发起了新连接。自定义一个 <code>Transport</code> 的 <code>Dial</code> 方法就好了。这里输出连接的 LocalAddr，代码如下：</p>
<div class="highlight"><pre><span></span><span class="kd">func</span><span class="w"> </span><span class="nx">PrintLocalDial</span><span class="p">(</span><span class="nx">network</span><span class="p">,</span><span class="w"> </span><span class="nx">addr</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nx">net</span><span class="p">.</span><span class="nx">Conn</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nx">dial</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">net</span><span class="p">.</span><span class="nx">Dialer</span><span class="p">{</span>
<span class="w">        </span><span class="nx">Timeout</span><span class="p">:</span><span class="w">   </span><span class="mi">30</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">,</span>
<span class="w">        </span><span class="nx">KeepAlive</span><span class="p">:</span><span class="w"> </span><span class="mi">30</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">,</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">dial</span><span class="p">.</span><span class="nx">Dial</span><span class="p">(</span><span class="nx">network</span><span class="p">,</span><span class="w"> </span><span class="nx">addr</span><span class="p">)</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;connect done, use&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nx">LocalAddr</span><span class="p">().</span><span class="nx">String</span><span class="p">())</span>

<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span>
<span class="p">}</span>
</pre></div>
<p>本地起了个在 8888 端口监听的 Web Server。好了，现在使用的 <code>http.Client</code> 如下：</p>
<div class="highlight"><pre><span></span><span class="nx">client</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">http</span><span class="p">.</span><span class="nx">Client</span><span class="p">{</span>
<span class="w">    </span><span class="nx">Transport</span><span class="p">:</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">http</span><span class="p">.</span><span class="nx">Transport</span><span class="p">{</span>
<span class="w">        </span><span class="nx">Dial</span><span class="p">:</span><span class="w"> </span><span class="nx">PrintLocalDial</span><span class="p">,</span>
<span class="w">    </span><span class="p">},</span>
<span class="p">}</span>
</pre></div>
<h3>发起请求</h3>
<p>一个三观正确的，具有普遍意义的请求步骤如下所示：</p>
<div class="highlight"><pre><span></span><span class="kd">func</span><span class="w"> </span><span class="nx">doGet</span><span class="p">(</span><span class="nx">client</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Client</span><span class="p">,</span><span class="w"> </span><span class="nx">url</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nx">resp</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">client</span><span class="p">.</span><span class="nx">Get</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
<span class="w">        </span><span class="k">return</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="nx">buf</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">ioutil</span><span class="p">.</span><span class="nx">ReadAll</span><span class="p">(</span><span class="nx">resp</span><span class="p">.</span><span class="nx">Body</span><span class="p">)</span>
<span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">&quot;%d: %s -- %v\n&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="p">,</span><span class="w"> </span><span class="nb">string</span><span class="p">(</span><span class="nx">buf</span><span class="p">),</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">Body</span><span class="p">.</span><span class="nx">Close</span><span class="p">();</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
<span class="w">    </span><span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>调用 <code>Get</code> 进入 <code>RoundTrip</code>，首先会找到个 <code>persistConn</code> (从缓存中找个已经存在的或者新建一个)，再调用它的 <code>roundTrip</code>，这时会把这个请求发送到 <code>writeLoop</code>，然后等待响应的到来，当 <code>readLoop</code> 中读到响应数据时，便会把响应 <code>Response</code> 发到 <code>roundTrip</code>，自此，<code>Get</code> 方法返回，不过事情还没有结束，响应的 Body 还没有读取，<code>readLoop</code> 会一直阻塞等待读取数据，也就是当前这个 <code>persistConn</code> 一直被占用着，当读取完 <code>resp.Body</code>，<code>readLoop</code> 就会把 <code>persistConn</code> 放回连接缓存中，以便下个请求继续使用。</p>
<p>持续发几个请求试试：</p>
<div class="highlight"><pre><span></span><span class="kd">const</span><span class="w"> </span><span class="nx">URL</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;http://localhost:8888/&quot;</span>

<span class="k">for</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="nx">doGet</span><span class="p">(</span><span class="nx">client</span><span class="p">,</span><span class="w"> </span><span class="nx">URL</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span>
<span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="nx">doGet</span><span class="p">(</span><span class="nx">client</span><span class="p">,</span><span class="w"> </span><span class="nx">URL</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span>
<span class="w">    </span><span class="nx">time</span><span class="p">.</span><span class="nx">Sleep</span><span class="p">(</span><span class="mi">2</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span>
<span class="p">}</span>
</pre></div>
<p>每次同时发送两个请求，并等待请求完成，输出结果如下：</p>
<div class="highlight"><pre><span></span>$<span class="w"> </span>go<span class="w"> </span>run<span class="w"> </span>client.go
connect<span class="w"> </span><span class="k">done</span>,<span class="w"> </span>use<span class="w"> </span><span class="o">[</span>::1<span class="o">]</span>:57571
connect<span class="w"> </span><span class="k">done</span>,<span class="w"> </span>use<span class="w"> </span><span class="o">[</span>::1<span class="o">]</span>:57570
<span class="m">2</span>:<span class="w"> </span>Hello,<span class="w"> </span>world<span class="w"> </span>--<span class="w"> </span>&lt;nil&gt;
<span class="m">1</span>:<span class="w"> </span>Hello,<span class="w"> </span>world<span class="w"> </span>--<span class="w"> </span>&lt;nil&gt;
<span class="m">2</span>:<span class="w"> </span>Hello,<span class="w"> </span>world<span class="w"> </span>--<span class="w"> </span>&lt;nil&gt;
<span class="m">1</span>:<span class="w"> </span>Hello,<span class="w"> </span>world<span class="w"> </span>--<span class="w"> </span>&lt;nil&gt;
...
</pre></div>
<p>可见此时建立了两条 TCP 持久连接，后面的请求都复用了一开始建立好的连接。如果再加一个请求呢，每次同时发送三个请求，输出结果如下：</p>
<div class="highlight"><pre><span></span>$<span class="w"> </span>go<span class="w"> </span>run<span class="w"> </span>client.go
connect<span class="w"> </span><span class="k">done</span>,<span class="w"> </span>use<span class="w"> </span><span class="o">[</span>::1<span class="o">]</span>:57582
connect<span class="w"> </span><span class="k">done</span>,<span class="w"> </span>use<span class="w"> </span><span class="o">[</span>::1<span class="o">]</span>:57583
connect<span class="w"> </span><span class="k">done</span>,<span class="w"> </span>use<span class="w"> </span><span class="o">[</span>::1<span class="o">]</span>:57584
<span class="m">2</span>:<span class="w"> </span>Hello,<span class="w"> </span>world<span class="w"> </span>--<span class="w"> </span>&lt;nil&gt;
<span class="m">1</span>:<span class="w"> </span>Hello,<span class="w"> </span>world<span class="w"> </span>--<span class="w"> </span>&lt;nil&gt;
<span class="m">3</span>:<span class="w"> </span>Hello,<span class="w"> </span>world<span class="w"> </span>--<span class="w"> </span>&lt;nil&gt;
connect<span class="w"> </span><span class="k">done</span>,<span class="w"> </span>use<span class="w"> </span><span class="o">[</span>::1<span class="o">]</span>:57585
<span class="m">2</span>:<span class="w"> </span>Hello,<span class="w"> </span>world<span class="w"> </span>--<span class="w"> </span>&lt;nil&gt;
<span class="m">1</span>:<span class="w"> </span>Hello,<span class="w"> </span>world<span class="w"> </span>--<span class="w"> </span>&lt;nil&gt;
<span class="m">3</span>:<span class="w"> </span>Hello,<span class="w"> </span>world<span class="w"> </span>--<span class="w"> </span>&lt;nil&gt;
connect<span class="w"> </span><span class="k">done</span>,<span class="w"> </span>use<span class="w"> </span><span class="o">[</span>::1<span class="o">]</span>:57586
<span class="m">1</span>:<span class="w"> </span>Hello,<span class="w"> </span>world<span class="w"> </span>--<span class="w"> </span>&lt;nil&gt;
<span class="m">2</span>:<span class="w"> </span>Hello,<span class="w"> </span>world<span class="w"> </span>--<span class="w"> </span>&lt;nil&gt;
<span class="m">3</span>:<span class="w"> </span>Hello,<span class="w"> </span>world<span class="w"> </span>--<span class="w"> </span>&lt;nil&gt;
...
</pre></div>
<p>可见每次都会有一个请求是新建了个 TCP 连接的，也就是说默认只保持两条持久连接，这是因为这里自定义的的 <code>http.Transport</code> 没有设置 <code>MaxIdleConnsPerHost</code>，于是便采用了默认的 <code>DefaultMaxIdleConnsPerHost</code>，这个值是 2，这是 <a href="http://tools.ietf.org/html/rfc2616#page-47">RFC2616</a> 建议的单个客户端发起的持久连接数，不过在大部分情况下，这个值有点过于保守了。如果把 <code>MaxIdleConnsPerHost</code> 设置为 3，结果便和第一种情况一样。</p>
<h3>三观不正的请求</h3>
<p>这里来一个三观不正的请求：</p>
<div class="highlight"><pre><span></span><span class="kd">func</span><span class="w"> </span><span class="nx">doGet</span><span class="p">(</span><span class="nx">client</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Client</span><span class="p">,</span><span class="w"> </span><span class="nx">url</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nx">resp</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">client</span><span class="p">.</span><span class="nx">Get</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
<span class="w">        </span><span class="k">return</span>
<span class="w">    </span><span class="p">}</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">Body</span><span class="p">.</span><span class="nx">Close</span><span class="p">();</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
<span class="w">    </span><span class="p">}</span>
<span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">&quot;%d: done\n&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="p">)</span>
<span class="p">}</span>
</pre></div>
<p>这里并不读取 <code>resp.Body</code>，因为读来也没用，但是也没法用 <code>HEAD</code> 请求(当然，这只是示例)。每次同时发送两个请求，结果如下：</p>
<div class="highlight"><pre><span></span>$<span class="w"> </span>go<span class="w"> </span>run<span class="w"> </span>client.go
connect<span class="w"> </span><span class="k">done</span>,<span class="w"> </span>use<span class="w"> </span><span class="o">[</span>::1<span class="o">]</span>:57974
connect<span class="w"> </span><span class="k">done</span>,<span class="w"> </span>use<span class="w"> </span><span class="o">[</span>::1<span class="o">]</span>:57973
<span class="m">2</span>:<span class="w"> </span><span class="k">done</span>
<span class="m">1</span>:<span class="w"> </span><span class="k">done</span>
connect<span class="w"> </span><span class="k">done</span>,<span class="w"> </span>use<span class="w"> </span><span class="o">[</span>::1<span class="o">]</span>:57975
connect<span class="w"> </span><span class="k">done</span>,<span class="w"> </span>use<span class="w"> </span><span class="o">[</span>::1<span class="o">]</span>:57976
<span class="m">2</span>:<span class="w"> </span><span class="k">done</span>
<span class="m">1</span>:<span class="w"> </span><span class="k">done</span>
connect<span class="w"> </span><span class="k">done</span>,<span class="w"> </span>use<span class="w"> </span><span class="o">[</span>::1<span class="o">]</span>:57978
connect<span class="w"> </span><span class="k">done</span>,<span class="w"> </span>use<span class="w"> </span><span class="o">[</span>::1<span class="o">]</span>:57977
...
</pre></div>
<p>额，每次都是新建的 TCP 连接，看来持久连接没用上。考虑到 TCP 接收到的数据，应用层并没有主动去读取，如果再次复用这个连接发送数据，那么上一次的数据要怎么处理？要么 Go 在库里默默的给读了，要么直接断开连接新建一条。在 Go 1.0 中就是在库里默默的读，想象一下正在 <code>Get</code> 的是几个 G 的东西，调用 Close 的时候... 所以最好的方法还是断开这个没有读取 Body 就直接 Close 的连接。</p>
<p>这里实现上利用 <code>bodyEOFSignal</code> 这个数据类型来包装 <code>readLoop</code> 生成的响应 Body，并设置回调函数 <code>earlyCloseFn</code>，如果 Body 并没有读取完便 <code>Close</code>，这个函数将执行并通知 <code>readLoop</code>，然后 <code>readLoop</code> 关闭连接并退出。所以如果想要使用持久连接，还得处理掉 Body，这就看应用的取舍了，尽量使用 <code>HEAD</code> 代替，也可以读取来丢弃掉。</p>
<div class="highlight"><pre><span></span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">Copy</span><span class="p">(</span><span class="nx">ioutil</span><span class="p">.</span><span class="nx">Discard</span><span class="p">,</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">Body</span><span class="p">)</span>
</pre></div>
<h3>后话</h3>
<p>当然，可以设置 <code>http.Transport</code> 的 <code>DisableKeepAlives</code> 来禁用掉持久连接。前面废话说的有点多，总结一下无非就下面几条：</p>
<ul>
<li>Web Server 得支持持久连接</li>
<li>如果有需要，加大 <code>DefaultMaxIdleConnsPerHost</code> 或者设置 <code>MaxIdleConnsPerHost</code></li>
<li>读完 Response Body 再 Close</li>
</ul>
<p>还记得那个默默奉献的 <code>localhost:8888</code> 不，感谢 Tornado。</p>
]]>
      </content>
    </entry>
    <entry>
      <id><![CDATA[http://https://serholiu.com/the-two-email]]></id>
      <title type="text"><![CDATA[两封邮件]]></title>
      <link href="http://https://serholiu.com/the-two-email" rel="alternate" type="text/html"/>
      <published>2015-04-24T23:00:00Z</published>
      <content type="html">
       <![CDATA[<p>以下两封邮件分别发送于2015年4月12日晚11点21分和2015年4月19日晚上11点53分，因为隐私，和原件相比真实地名或人名采用 XXX 代替，出现的数字用 162342 代替，并有相应删改。</p>
<hr>
<p>权力的游戏明天第五季就要开播啦！还记得去年六月份上一季完结的时候，我发了条短信给你说，剧中人物一个个都踏向了新的路途，我们也像他们一样，出国的出国，读研的读研，工作的工作。当时是正准备踏上路途，而现在却已走在路途。好聚好散之所以总是成为个愿望，就是因为这么久来很难做到，人非沙砾，聚在一起而不产生任何羁绊。</p>
<p>但我曾不是这样想过，我以为散时必会做到无牵无挂，所以聚时就可以肆意妄为，或许我现在的压抑烦闷就是以前所作所为的报应。所以我到底在想些什么，又在犯曾经犯过的错误吗，悲剧是否又在重演。我似乎有一种为喜欢的人总是做蠢事的天赋，被我喜欢，得是一件多么郁闷的事。</p>
<p>说了这么些，也不知道想表达个什么意思。我就是想向你坦诚，我保证对你说的话没有一句假话（当然也包括这句（当然也包括这句（当然也包括这句（...递归到宇宙洪荒...））））。诶，常常想到你这么美丽聪明的姑娘被我这个丑陋的二逼青年纠缠不清就感到伤心难过，但你跟着别人我又老是不放心，真是纠结。</p>
<p>今天看你微博，发现你在北京时间九点半的时候换了个头像，就伦敦和北京时差七个小时来说，你那边应该是凌晨两点半了，那么迟都还没有睡，真是服了你。你真是适合做个猫咪啊，尤其是加菲猫。</p>
<p>喵喵喵。</p>
<hr>
<p>首先，必须道个歉 —— 感觉一直都在道歉啊～ 这周对你是打扰不断，做了些不太好的事，下面我详细说一下。</p>
<p>故事还得从很久前说起，当时我看你的一条微博，大意是说你和某人心有灵犀，连密码都和你的教务密码一样，是162342，我就一直在想这串数字代表什么意思。念念不忘必有回响，就在前几天，我浴室里的镜子竟然对我说话了，当然，什么大场面我没见过，于是我镇定的问道：“魔镜魔镜，请告诉我天底下最漂亮的人是谁？”，魔镜当然没有说话，这种连我都知道的白痴问题它才懒得回答，谁不知道天底下最漂亮的人就是美丽的张XX同学嘛。我为自己问出这种白痴问题而感到羞愧不已，好在脸皮比较厚，就又问了下它，“你知道162342背后的含义吗？”魔镜没有正面回答，它只是在开始回忆往事，镜中的它似乎在开始变的年轻，时光变幻，大约来到了千年之前，魔镜变成了一个屏幕，里面是一个宫殿，房间中一位女王正对着魔镜问着和我相同的第一个问题，当然魔镜的答案是白雪公主。我沉默了半天，说道“这故事我都懂，可你没回答我问题啊？”“白雪公主最后和谁在一起了？”魔镜问道。“王子啊！”，我想了下。“对啊，这串数字就是和你第一个问题的答案有关，有一个叫做 XXXX 的神秘组织，其中的42号正是她的王子！”魔镜消失在这句话之后，镜子里面只剩下拿着牙刷不知所措的我。</p>
<p>既然这样，于是我就想试试镜子说的是不是真的，如果你是加菲喵，我就是好奇害死喵。后面的事情你就都知道了，实在无聊得实在无聊。也许聪明的你已经发现了一个不太对劲的地方 —— 我才问了魔镜两个问题，按照传统，应该得问三个问题才对，其实我是有问第三个问题的，后面就知道了。回到云音乐，先前我看到一首歌的评论特别搞笑，于是 @ 你的微博名分享给你看，结果点了下链接居然到了你在云音乐上的主页。几天前看你喜欢了好多回忆往事伤感的歌，于是觉得时机已到，便用 XXXX42号注册了个账号来关注你，想看看你的反应。</p>
<p>估计你信以为真，突然收藏了好多歌，甚是激动。想你或许又开始被往事侵袭，我就略感伤心（这特么不自找的么）。后来被你发现是我的马甲就给拉黑了。可是还没完，此时的我估计已经走火入魔，自你去英国已经半年，而这段时间我没有你的任何联系方式，好在现在可以发私信了，于是我又换了个用你照片做头像的账号。照片来自于问魔镜的第三个问题：“我想知道最漂亮的人现在是怎么样的呢？”略等片刻，镜子上慢慢的出现你的身影，我问魔镜要怎么截图，魔镜笑了笑，”你的三个问题已经问完了。“还好我机智，赶紧用手机拍下来。现在想来，又做了件蠢事，幼稚貌似出现在它不该出现的年纪，如此喜欢音乐的你，这样被我打扰。</p>
<p>我一直期待着魔镜重现，不再需要问很多问题，只想问一个，“魔镜魔镜，这世界上最漂亮的人现在快乐吗？”</p>
]]>
      </content>
    </entry>
    <entry>
      <id><![CDATA[http://https://serholiu.com/i-will-be]]></id>
      <title type="text"><![CDATA[六月二十七日]]></title>
      <link href="http://https://serholiu.com/i-will-be" rel="alternate" type="text/html"/>
      <published>2014-06-27T23:00:00Z</published>
      <content type="html">
       <![CDATA[<p>手机显示现在不到6点，果然生物钟又起了作用，晚上可是失眠到3点多才睡。不过已然是夏天，清晨还是略感烦闷，再加上疲惫的双眼，感觉一切都是如此糟糕。毕业季的夏天遇上世界杯，晚上怎么可能消停，不过我倒是很早就躺在了床上，作为一个伪球迷都算不上的人，昨晚的比赛实在是没啥兴趣。朋友们在下面看着热闹，而我却一直想着一个姑娘，明天她就将离校，现在她应该在外面聚会吧，是不是应该去看看呢。当然，已经养成一种习惯的我最终还是没有忍住，翻身下床出了寝室。</p>
<p>此时已经凌晨一点多，学校商业街依然无比热闹，早已驾轻就熟的我不一会儿便把她可能出现的地方找了个遍，然而并没有发现她的身影，“可能是去看球赛了，”我想，“还是去寝室楼下等着吧。”。来到15号寝室楼下，沿着外面的大道走来走去，大楼外面的空地上孤零零的立着一个帐篷，这是为毕业生邮寄行李的快递点，不过现在这个时候，早已没了人。曾在这条路上等过多回，这么晚倒是第一次，掏出手机玩了好久的游戏后，终于听到了一阵声音，他们聚会结束正走在回寝室的路上，分道扬镳后，她和她的室友朝15号楼走过来，夜色太黑，不大看得清，我卯足了胆子走上了空地朝寝室大门走去，她俩从我眼前经过，她似乎看到了我，回过头看了一下。</p>
<p>几天前在贴吧看到她在卖各种杂物，比如各种没见她穿过的衣服，各种教材资料，各种奇特配饰，尽管毕业前夕楼下的二手市场非常火热，估计她不太愿意为这毕业季的风景线再添一丝吵闹。本想买个她处理的东西作为纪念，不过交易肯定无法完成，也就只能想想，倒是在她给咨询者的留言中得知她27号离校，但见鬼的是，具体时间并不清楚。而昨晚玩得那么嗨，想必也不会是凌晨的车，所以我放心的睡到了现在。“如果没有好的方法，那就穷举”，这是读计算机专业四年得到的至理箴言，既然不知道她何时出发，那就一早去等着吧！时间毫不留情的跑到6点，此时的我又站在了15号楼下，早起的好处便是见到不同的风景，你会发现食堂的阿姨大都是骑着自行车来上班的，大叔大都骑的是摩托车，你会发现各种乱扔的小垃圾让打扫卫生的人头痛不已，你会发现现在才回寝室的人还是挺多的。太阳已经染红天边，但仔细也看得出它逐渐式微，旁边的乌云开始慢慢侵蚀它的成果，不久居然开始飘来雨滴。躲在对面16号楼的门下也不是长久之计，于是赶紧跑回寝室拿了把伞。</p>
<p>六月份的成都是多雨的时节，多得像是在胡闹，不像三月份的雨提醒人们雨季将至，不像九月份的雨帮助人们驱除燥热，它似乎只是在刷着存在感，在人们习惯之后偶尔雷霆大怒。而我却如这雨般，一次次想在她面前刷存在感，哪怕她撑着伞，淋湿不了她的头发，也想溅起的雨滴沾湿她的鞋。去年差不多这般时候，暴雨如注，我从图书馆二楼下到一楼，她迎面走来，黑色的外套已经淋湿，头发也惨不忍睹，手提包估计也不太好运，我准备打个招呼，却顿了一下，“该怎么称呼呢，张小点？猴子？张YF？“，此时她已经走到面前，“张YF”，声音格外的小，现在我都怀疑当时是不是说了出来，她低着头看着手机从旁边走了过去。还好现在的雨只能算是淅淅沥沥，也未见变大的趋势，我撑着伞站在15号楼下。人渐渐开始多起来，有拉着行李的，有还在期末考的。</p>
<p>像这样的等待早已经出现过多次，好像临死之前的挣扎，出自本能，似乎觉得以前错失了什么，以后将不再看到些什么，我不太考虑她的感受，毕竟知道又能怎么样呢，注定是和自己冲突的，而此时的自私已经战胜了另一面，或者说，根本就不曾有过另一面，真是细思恐极。当在即将永远告别的毕业季，我疯狂的一遍又一遍重复着相同的事，到处找她，似乎看到她已经成为一种宗教仪式。不知她几时会出寝室，便在饭点的时候到楼下等着，此时陆陆续续下来吃饭的女生、从图书馆或教室回来的人们和各种送外卖的小哥让视线变的杂乱，还好，每次都能从中找出她。她特别喜欢熬夜，睡觉能到中午，因此大多时候她并不会下来，不过后来这种盲目的等待终于有了一点好转，我发现手机 QQ 在线状态可以显示联系人处于的网络情况，而她的 QQ 一直在线，当网络从 WiFi 变成 3G 时，也就说明她从寝室走到了外面。我便不再一直站在楼下，只需要盯着她的状态，当网络变的时候，就赶紧下楼，此时她基本上已经走到四食堂外面，这种办法还是相当有效，估计她也在纳闷为啥我总是出现得恰到时候。即使我如此在她面前刷着存在感，她也不说些什么，某种意义上她也无话可说，她眼中的我变得透明，她心中的情绪弥漫着厌恶，她坚持着曾落下的话“不会再跟你说什么了”。所以我都是在寻找尴尬，看着她和朋友在校园四处拍着留念照，晚上从南校门看着她的背影一直到寝室楼——我在人行道上骑着车，她在马路上走着路。</p>
<p>一切疯狂都有尽头，而今天即是尽头，这是最后一次站在这里，这是最后一次等待。人越来越多，我开始不好意思起来，毕竟其他在这里等着的每一个人，楼里都有一个女生心甘情愿。好在下着雨，撑着伞似乎能遮住些什么，时不时的转一下雨伞，或者朝后面退一两步，雨声人声皆成伴奏，竭力的渲染出一种尴尬的氛围。“耿爷，你在这儿做什么？”彪哥搭着春哥骑着的一辆破电动车出现在我面前，我竟然无言以对，心中一直暗涌的羞愧感涌到脸上挤出了一丝苦笑。时间貌似走的很慢，想必昨晚她睡得很迟，而现在可能还在睡觉吧，或者她已经早走了，只是我没有发现？时间已经到10点，雨也停了，她出现在寝室大门口，穿着那件黑色的伯克利文化衫，头发扎了起来，退完钥匙后便回了寝室。片刻过后她走了出来，穿过空地朝小卖部走去，我跟了上去，但终究也只是跟了上去，每次迎面看着她的脸，脸上似乎都有“滚远点”三个大字在闪闪发光，但即使这样，也丝毫不影响这美丽的容颜。「在你左边的容颜，我搁浅，我却要，继续冒险，最好没有人明白我说什么，只有你听懂我想什么」然而这美丽，却是如此的陌生，好像和以前完全两样。</p>
<hr>
<p>“Lucky--Jason Mraz”，我在学校广播台点歌的微博评论框里输入了这些。前些时日特别喜欢在学校广播台点歌，与其说是想让喜欢的歌被大家听到，不如说是想让她听到我喜欢的歌。虽然同班四年，但实际上也没接触过多久，除了上课能看到外，其他时候真是不多，何况我还经常逃课。不过喜欢一个人这些时间已经绰绰有余。「I keep you with me in my heart, You make it easier when life gets hard」，耳塞里传来一阵熟悉的旋律，“怎么样，耿爷，好听么？”小明在旁边低声问道，“嗯，不错”，我把耳塞递了过去，他正偷看着女神不久前发的微博：“Lucky太好听啦，单曲循环听不厌”。我抬头朝左边看了下，她也如大部分人一样，在这无聊的暑期实习培训中昏昏欲睡，这是唯一一次有她参加的暑期实习，此时正值大二的夏季，模糊开始变得透明，这过程异常的烦躁。</p>
<p>我已经记不起第一次看到她是在什么时候，在什么地方，初入大学，一切似乎都还停留在高中时间，心里放不下的也不想放下，整个大一都沉浸在琐碎的往事之中。只能记起少的可怜的片段，大多还是来自她的微博，她的空间——空间里的癫狂逗比和微博上的伤感吐槽就像双子座的两极，善变如同深渊一样吸引着我。然而开始时却大都只是平淡，只是偶尔羡慕实验课上跟她分在一组的人，在课上听听她的笑声，已经记不起是怎样的笑声，这个时候我大概并没有喜欢她。</p>
<p>“小明给他女神送伞，啊哈哈哈~”，坐在前排的她尽兴的给大家八卦，时不时的发出一串笑声，我坐在她的后排，盯着车窗外，看着立交桥上的车流，想象着龙泉漫山的桃花。然而来迟了，她看着已经没啥花朵的桃树似乎有点失望，但又一下变得兴奋起来，拿着相机跑开了。后来大家决定去湖里划船，一开始要我坐到船上我是拒绝的，一想到在水上荡来荡去就不寒而栗，好在发现她也在这只船上，便豁了出去，后面的水仗并没有翻船，让我一直有命在船上喋喋不休，这次算是我跟她说话最多的一次吧。我开始走向深渊。去上课的次数开始变多，早早跑去坐在她经常坐的位置后面，等着她慢悠悠的踩着点走进教室，而此时的她，扎着头发，戴着眼镜，将酒红色格子衬衫扎进牛仔裤，显得异常漂亮。「害怕悲剧重演，我的命中命中，越美丽的东西我越不可碰」多情无奈人丑，美丽的东西即是深渊，即使侵吞不了生命，也会耗尽时光，还是离远一点比较好。</p>
<p>于是我继续着我的生活，除了偶尔在她空间评论打趣之外，没有丝毫交集。时间过得很快，已到大三下期，七教的实验室里，我正愉快的敲着代码，她坐在后面，一句不经意的话飘进我耳朵，“猴子，你有男朋友啦？”，小明在旁边问道。她说些什么我没听清楚，早在微博上看到过她和一个男的很是亲密，原来那就是她男朋友，我顿了下，继续做事，反正我又不喜欢她，她有男朋友关我什么事，对，不关我事。还是如以前那样期待她上课坐在我前面，还是如以前那样看到她就觉得异常兴奋，就这样过去了两个月。</p>
<p>接下来便是漫长的半年，漫长得来感觉是场梦，在这段时间，我在她心中从无关人员变成厌恶人物。我开始追她的朋友，做法不太恰当，她为朋友受到这样的折磨而感到愤愤不平，以她的性格，迟早会爆发......</p>
<p>刷着人人，发现自己已被她拉黑，“拉黑就拉黑呗，很正常的事嘛”，不过我终究还是感到郁闷，好似她曾跟我关系多好似的，于是我打开 QQ，把她给删了，心中洋洋得意：“这下你没法删我 QQ 了吧，哈哈。”我在15号楼下面站着，她走了出来，把羽绒服的帽子掀起来戴在头上，双手插在口袋中，慢悠悠的朝教学楼走去，突然想问一下她为什么把我拉黑，便跟了上去，不过又十分犹豫，毕竟这样是不是过于突兀，就顾着思想斗争，居然已经走到一教，她发现我，便朝我走来，我假装没看见，径直从旁边走了过去，回到图书馆。正看着书，手机出现峰导发过来的信息，原来她在空间发说说警告我，大意就是“我不只骚扰她的朋友，还跟踪她”之类的，我感到莫名其妙，决定去找她，走到一教时发现她已经走到二教邮局，便追上去问道：“你在空间上骂我做什么？”你跟着我干嘛？”，她回了一句。“你为啥把我人人给拉黑了？”，“原因你自己清楚！”，原因我当然清楚，我转身走上虹桥，不想再说些什么，好像也没啥可说的，毕竟错的是我。正走在二食堂前面，突然听到后面有人叫我名字，我转过身去，她气喘吁吁的跟了上来，“你以后不要在跟着欢欢了！”，一脸怒气冲冲地说道，我一下居然想笑，从来没看到过她生气的样子，还是挺可爱的嘛。“我不想跟你说这些，她想说什么叫她自己来说。”“你再继续这样，我们就对你采取措施！”，我没说什么，转身便走，一会儿后，我转过头看了看。</p>
<p>就这样，我消停下来，我发现似乎是喜欢她的，不然很多东西没法解释。但这对我来说，又略显奇葩，如果说出去，岂不是会被人认为是因为追她朋友不成，转而去喜欢她么。春节过后，大学只剩下短短的四个月，而其中有两个月我在外面实习，最理智的做法就是像以前那样过去，这么几年都过去了，短短几个月算什么呢。然而这却是不那么相同，以前总觉得时间很多，可以慢慢来，而现在，时间显得如此急迫。我常常难以选择理智的做法，「人太忠于感觉，就难好好思考」正确的道路大家基本上都知道，之所以很少人选择，正是因为要走下去太他妈难了。当我躺在火车上时，满脑子都是她的身影，以前积压的一切如火山爆发把我推入深渊。在实习的那段日子，每天循环着 The Scientist，不知道它在讲些什么，只知道「Nobody said it was easy」。我开始给她发短信，她回复说“不要再发了，不想再跟你说什么”，对，她做到了，直到现在，她再也没有回复过我的任何信息。她不回复，并不意味着我不再继续，我一直在想，什么时候告诉她我喜欢她呢，正好清明节就要到来，还有比这更适合这种情况的日子么？来到五月，得知学院在16号有个毕业晚会，而且她会表演节目，便提早回校。</p>
<hr>
<p>买完东西后她回了寝室，我便继续在楼下等着，心里倒是很顺畅，因为这意味着她已经准备好出发，我正琢磨着待会儿即使被骂也一定要鼓起劲去帮她拿行李，她就撑着伞走了出来，她的室友们在后面帮她拖着行李，“我去，这么多人，我还是不过去了”，心里念道。她们并没有坐上去高铁站的校车，而是沿着大路朝西校门走去，我可不好意思跟在后面，便从树林小道走近路朝校门走去。中途她们停下来几次，等上了另外几个人，最终来到校门。我看了看新加入的那几个男生，甚是眼熟，但并不认识，他们是不是很早就约好，这些我倒是不得而知，知道的是他们并没有立即走的打算，一堆人站在校门口不时的朝寝室楼的方向望望，好像在等什么。我站起来，拿起垫着屁股的伞，朝那个方向看去，一部小面包车正开着过来。“他们不会是要一起包车去机场吧？我也要跟着去。”，摸了摸口袋，钱不够，还好不远处有个建行，我飞奔过去。</p>
<p>沿着到建行的路再往前走，就是一食堂，学院的毕业晚会便是在那里举办的。彩排的那几天，我跑去想看下她，不过站在外面不好意思进去，只听到她用一种特意卖萌的声音说着话。“I Will Be”，得知她在毕业晚会上要唱这首歌，便在学校广播台点了，一食堂在南区，她应该没能听到。晚会上我坐在后面，她唱完歌，走了下去，我回过神，突然感觉很诧异，她现在和以前完全两样，我都差点没认出来，除去表演节目的化妆之外，她已经摘掉了眼镜，扎起的头发开始披了起来，留着圆形刘海。就像毕业照里的她，在上面找不出一点曾经的痕迹。晚会完后，发短信给她，说着关于歌的一档子破事，我又犯着老毛病，觉得她唱这首歌肯定是为了某个人，最后她实在受不了，便在微信上咒骂我，而我理亏，也就只能跑到南门外找了个公共电话打给她问下情况。在而后的一个多月，除了到处猜测她喜欢的人是谁外，就只是看了下她喜欢的「Once」 和「海岛七号」，除此之外什么也没做，也没什么可做。</p>
<p>取完钱回来过后，他们并没有坐上车，几个女生在那里说个不停，脚早就酸痛不已的我在马路对面坐了下来。时间来到11点，随着其他人的到来，他们走出了校门，“看来还是要去高铁站嘛，我跟着去不呢？”，我站了起来，朝门口走去，“能走多远就走多远吧，毕竟此后再也见不到。”校门离高铁站不远，不过我想着快点过去看能不能买到去北站的票，这样便能一直跟到火车站。急忙跳上一辆三轮，三轮作为成都交通的主力军，在短途运输上发挥着自己的优势，他们不畏严寒酷暑，默默奉献着一切。路边的涂鸦墙快速倒退，从下车的地方走到高铁站还需要穿过一条马路，马路正在维修，加上刚才的雨，变得泥泞不堪，踩着上面的砖块如跳着芭蕾一样到了对面，即使这样鞋也未能幸免，沾上不少泥浆。我跑进售票厅，等待买票的人特别多，离最近一班车停售的时间越来越近，“应该是赶不上了”，我走出售票厅，“不过能到这里也没啥遗憾了。”想到刚才那段泥泞的路，她肯定不好拿行李，便回转过去，然而天无绝人之路，突然看到高铁站外的空地上新建了很多自动售票机，而且空无一人，“妈蛋，这不就是给我准备的嘛！”，我过去买好往返票后就朝路口走去。</p>
<p>她已经过来，并非一人，欢欢跟着她一起，看着她们朝候车厅走来，我便先进去等着。她俩一直在门口聊着，直到刚才一起的三个男的过来后，她才检票进来，欢欢这才离开。站台上，我的是站票，便跟在他们候车的地方站着，她背着一个大包，把手提包绕在硕大行李箱的拉杆上，四个人聊着些什么，好像他们要去西安。她曾多次说过她要来个毕业旅行，可是西安能有啥好玩的，我唯一的记忆就是晓鹏老师说得“民风彪悍...”。其中一人提议说加个微信吧，我差点流出了口水：我无数次加她微信都没能成功，你们倒是说加就加了，真是羡慕啊。列车到站，她推着行李箱进了车，前面一个人行李上的包差点掉下，她一下过去用手挡住，有个车厢没多少人，我们便走了过去，走廊过于狭窄，她小心翼翼的推着行李箱走在前面，和其中一个人做在一起，我在她后排坐下。</p>
<p>砰，足球终于飞了起来，我跟着跑过去，尽量表现的很认真刻苦，这是最后一期体育课，试考完就可以永远的告别这蛋疼的足球。好在体育老师人非常好，知道我就这破水平，只要态度好一点就给过，“踢得杂样了？”老师问道，“啊？”，我无奈的笑了下，杂样，还是那个样呗，明显我就是在敷衍嘛。“张RZ，你去教教他。”，我看着老师对着说话的那个人，他足球踢得挺好，好像是计科专业的，身影显得削瘦。这些都已是两年前的事，而现在列车已经开动，到北站只需要11分钟，他俩在前面聊着天，她说着昨晚喝酒是如何如何的厉害，说着东阳兄发过来送别的信息是怎样的逗比，他们开始互相说着自己家乡的种种特产。时间很快，列车停在了火车北站。火车站设计得不太人性化，要出站首先的上天桥，然后下地下通道，如果行李过多得话，实在是一件苦事。熙熙攘攘的人群走向天桥，她走在前面，拉着那个硕大的行李箱，我豁了出去，“猴子，我帮你提吧？”，边说边把手伸向行李箱，她的手没有放开，一脸的不情愿，差一点就抓到了她的手，还好她没有叫“流氓”，我赶紧收回了手，“不要这样行吗？”，我在喧闹中低声问道，因为我知道不行。“耿爷，你怎么在这儿？”，彪哥突然出现在面前，特么差点吓死我了，她赶紧跟彪哥打了声招呼，聊了起来，以缓解这尴尬的气氛。在地下通道的阶梯前，她把拉杆上套着的小包给和他坐一起的人，自己两只手一起提着行李箱，走了下去。当然我知道，这个看起来娇小的姑娘其实蕴含着巨大的力量，骑车跑步那是样样强悍，简直让人佩服。</p>
<p>他们四人一起去吃完午餐后，便走向检票口。北站永远都有这么多人，走在这个将人迎来又送走的地方，脸上似乎都只是急躁，或许只是不舍和留念没有任何表情可以对应而已，人心若是透露，岂不是泄了天机。我用先前买的返程票过了安检，他们有行李，比较慢，我在安检口前等着，因为并不知道他们乘的班次，也就不知道该去哪个候车厅。她先过了安检，站立片刻后便拉着行李箱退着踱到旁边的安检口，好似在跟其他人炫耀自己先通过。四人告了别，有两人去的是西安，而她和跟她坐一起的人一起去云南，是啊，她的毕业旅行去云南才比较正常嘛。而跟她一起的人，家乡就在云南，我突然感到心慌，一阵凉意袭来，前几天看到一个解说双子座的微博，说双子座的人在这个时候可能会喜欢上和她一起去旅行的人，我当然不信什么星座，但她信啊！</p>
<p>跟着他们走去候车室，此时离发车已经不远，昆明闸口前的队伍已经排到最后，我站在她旁边，看着她散乱的发丝，盯着她散乱的发丝，时间没法凝固，一切就像这头发一样，怎么也理不顺，而这一切就是最后一幕，我努力的记着，想让这画面清晰的刻在脑海。队伍开始移动，我也跟着走，我扭头看了下旁边那个人，突然觉得好熟悉，难道是张RZ？他家就在云南，只是为什么比以前健壮了许多，或许是其他人？管这么多干嘛，反正不是我。在这最后，我似乎也不曾后悔做过的那些事，即使当时没有那样对待她的朋友，当时追的是她，结果肯定也如现在这般，没关系，都一样。即将走到闸口，我在旁边的椅子上坐下，早已腰酸腿痛饥渴难耐，此时疲倦得就想在这里躺下，抬头朝闸口看去，她验完票后转过身来用熟悉的声音说着。</p>
<p>“嗯，那就拜拜咯。”</p>
]]>
      </content>
    </entry>
    <entry>
      <id><![CDATA[http://https://serholiu.com/about-go-net]]></id>
      <title type="text"><![CDATA[初探 Go 的网络编程]]></title>
      <link href="http://https://serholiu.com/about-go-net" rel="alternate" type="text/html"/>
      <published>2014-01-27T16:00:00Z</published>
      <content type="html">
       <![CDATA[<p>最近趁着寒假在家空闲比较多，改进了一下 <a href="https://github.com/SerhoLiu/fakio">Fakio</a>，并给它写了个 Go 语言的客户端。很早之前也了解过 Go 语言，不过当时并没有使用的地方，所以也就不了了之。这几天重新学习了一下，并一边 Google，一边 Godoc，最后勉强写出来了。Go 的资料较少，文档基本没用，好在代码结构清晰，查看文档不得要领，还可以直接打开源码，一探究竟。</p>
<p>不过以前网络编程基本使用的是 C，Python 也只是用来做做 Web 而已，对于层层封装和抽象过的 socket，在使用时不免会产生诸多疑问，说好听一点知其然也要知其所以然，说的不好听一点就是纠缠在细节之中而无法提高一个层次思考。不过为了消除疑虑，就大致看了一下 Go net 包里的东西，下面就扯一点心得。不过不先说 Go，说说其它的。</p>
<h3>CallBack</h3>
<p>以前接触最多的网络编程模式还是基于回调函数的，比如 Node.js 就是很明显的采用回调函数。这个模式简单易懂，将文件描述符、事件、处理事件的回调函数作为一个整体加入一个队列，当文件描述符上发生指定的事件后，就调用相应的回调函数，而获得文件描述符上发生的事件， 可以使用 select、epoll 这类系统调用。虽然模式简单，但使用这种模式经常需要配合非阻塞 I/O 来使用，因此真要这么写一个大一点或者流程复杂一点的应用，真是困难重重，即使现在有 libevent\libev\libuv 这种将事件处理封装起来的库，使用上也无法真正的将应用与这些实现细节脱离开来。</p>
<p>比如应用里一些数据的状态保存，因为执行某个回调函数后，某些处理可能并没用完成，此时需要返回去执行其它事件，因此不能使用函数栈来保存信息，必须将需要保存状态的结构使用指针传给回调函数，当然还有一些用户数据，比如下面这个接受信息的回调函数</p>
<div class="highlight"><pre><span></span><span class="kt">void</span><span class="w"> </span><span class="nf">readable_cb</span><span class="p">(</span><span class="n">event_loop</span><span class="w"> </span><span class="o">*</span><span class="n">loop</span><span class="p">,</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">fd</span><span class="p">,</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="n">evdata</span><span class="p">)</span>
<span class="p">{</span>
<span class="w">    </span><span class="n">buffer</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">evdata</span><span class="p">;</span>

<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">(;;)</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="kt">int</span><span class="w"> </span><span class="n">rc</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">recv</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span><span class="w"> </span><span class="n">WRITE_AT</span><span class="p">(</span><span class="n">buf</span><span class="p">),</span><span class="w"> </span><span class="n">WRITE_LEN</span><span class="p">(</span><span class="n">buf</span><span class="p">),</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">rc</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">errno</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">EAGAIN</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">                </span><span class="k">return</span><span class="p">;</span>
<span class="w">            </span><span class="p">}</span>
<span class="w">            </span><span class="c1">// 出现错误，执行一些清理工作，省略</span>
<span class="w">            </span><span class="k">return</span><span class="p">;</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">rc</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="c1">// 对端关闭，执行一些清理工作，省略</span>
<span class="w">            </span><span class="k">return</span><span class="p">;</span>
<span class="w">        </span><span class="p">}</span>

<span class="w">        </span><span class="n">COMMIT_WRITE</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="n">rc</span><span class="p">);</span>

<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">WRITE_LEN</span><span class="p">(</span><span class="n">buf</span><span class="p">)</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="c1">// 终于 buf 满了，可以做其它事了</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">    </span><span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>上面的 <code>buf</code> 就是用户数据，因为你必须在 <code>buf</code> 还没能接收满，函数就从第9行返回时保存好已经接收的数据。libev 使用了一个 <code>ev_io</code> 结构来得到用户数据的指针，一般将这个作为希望传递给回调函数的结构体的第一个域，这样它的地址也就是这个结构体的地址。</p>
<p>因为状态问题，如果处理更加复杂的网络协议，比如 HTTP，则更加的麻烦，回调过去回调过来，真是让人痛苦。如果能在切换出函数时可以保存函数的栈信息就好了，比如使用线程呢？</p>
<h3>回到 Go</h3>
<p>一般学习网络编程时就会介绍一种多线程模型，其中有一种方法是主线程负责监听新的请求，有一个请求到来时就拿一个线程来处理这个请求，因此请求之间就不会相互阻塞，线程的调度则由操作系统来进行，也就不用在用户态去检测文件描述符上的状态了。不过线程的问题就是资源消耗和上下文切换开销过大。为了解决这种多线程问题，于是开始大量使用上面的 CallBack 模式，看来改用线程是在开历史倒车啊。</p>
<p>于是协程(Coroutine)开始显示出了它的魅力，协程并非一个新概念，早在 1963 年就提出，现在很多语言都支持。协程类似于一种轻量级的用户态线程，操作系统对其一无所知，调度之类的由程序员搞定，切换成本比较低。不过要基于协程来达到类似线程的使用，还是困难重重，虽然 POSIX 中有 ucontext 这类可用来实现协程的函数，不过要将其用于网络编程之上，还有很多轮子需要造：比如一个调度器，现在操作系统帮不上你的忙了，你得自己设计调度策略，以及获取各种信息来进行决策，比如要在文件不可写时切换到其它协程去，那么得获取到文件的信息；协程间的通信，线程通信可以采用共享内存，也正因为此，得“处心积虑”，那么如何设计协程间通信就是个难题；常见的系统调用的封装，比如 <code>read</code> 之类的。</p>
<p>不过使用 Go 的话，上面的一切都有了，goroutine 就是一种协程的实现，在 Go 里面用起来也非常简单，于是类似于上面的多线程模型，Go 进行网络编程时也有一个常用的模式，比如下面这个 TCP Server：</p>
<div class="highlight"><pre><span></span><span class="kd">func</span><span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nx">ln</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">net</span><span class="p">.</span><span class="nx">Listen</span><span class="p">(</span><span class="s">&quot;tcp&quot;</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;:8080&quot;</span><span class="p">)</span>
<span class="w">    </span><span class="nx">checkError</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="nx">conn</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">ln</span><span class="p">.</span><span class="nx">Accept</span><span class="p">()</span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="k">continue</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">        </span><span class="k">go</span><span class="w"> </span><span class="nx">run</span><span class="p">(</span><span class="nx">conn</span><span class="p">)</span>

<span class="w">    </span><span class="p">}</span>
<span class="p">}</span>

<span class="kd">func</span><span class="w"> </span><span class="nx">run</span><span class="p">(</span><span class="nx">conn</span><span class="w"> </span><span class="nx">net</span><span class="p">.</span><span class="nx">Conn</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nx">buffer</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">([]</span><span class="kt">byte</span><span class="p">,</span><span class="w"> </span><span class="mi">1024</span><span class="p">)</span>
<span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nx">Close</span><span class="p">()</span>

<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">conn</span><span class="p">.</span><span class="nx">Read</span><span class="p">(</span><span class="nx">buffer</span><span class="p">)</span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="k">return</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;read:&quot;</span><span class="p">,</span><span class="w"> </span><span class="nb">string</span><span class="p">(</span><span class="nx">buffer</span><span class="p">[:</span><span class="nx">n</span><span class="p">]))</span>
<span class="w">    </span><span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>每到来一个连接，就启用一个新的 goroutine 来进行处理，因为 goroutine 非常轻量，所有能同时启动很多个，当然也就能同时处理大量请求。在 goroutine 内部，<code>Read, Write</code> 之类的调用是阻塞的，因此使用起来很是简单，不用各种回调。不过对于整个进程，当某个 goroutine 阻塞在 Read 之类上时，就会被调度器唤出，以便让其它 goroutine 执行。而 Go 后台会有一个线程监控这些网络连接(实际上是文件描述符)，如果连接重新可读可写，就会唤醒这个 goroutine。</p>
<p>在 net 包中 <a href="http://golang.org/src/pkg/net/fd_unix.go">fd_unix.go</a> 这个文件实现了对文件描述符的封装，<code>netFD</code> 中的 <code>pd pollDesc</code> 用来实现对文件描述符的检测，当调用 net 包中需要对网络进行读写的函数时，最终都会调用 pd 的各种方法来对文件描述符状态进行查询或者修改，比如下面是 <code>Read</code> 的一部分：</p>
<div class="highlight"><pre><span></span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">fd</span><span class="p">.</span><span class="nx">pd</span><span class="p">.</span><span class="nx">PrepareRead</span><span class="p">();</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">OpError</span><span class="p">{</span><span class="s">&quot;read&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">fd</span><span class="p">.</span><span class="nx">net</span><span class="p">,</span><span class="w"> </span><span class="nx">fd</span><span class="p">.</span><span class="nx">raddr</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">}</span>
<span class="p">}</span>

<span class="k">for</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">syscall</span><span class="p">.</span><span class="nx">Read</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="nx">fd</span><span class="p">.</span><span class="nx">sysfd</span><span class="p">),</span><span class="w"> </span><span class="nx">p</span><span class="p">)</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="nx">n</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">0</span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">syscall</span><span class="p">.</span><span class="nx">EAGAIN</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">fd</span><span class="p">.</span><span class="nx">pd</span><span class="p">.</span><span class="nx">WaitRead</span><span class="p">();</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span>
<span class="w">                </span><span class="k">continue</span>
<span class="w">            </span><span class="p">}</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">    </span><span class="p">}</span>
<span class="w">    </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">chkReadErr</span><span class="p">(</span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">,</span><span class="w"> </span><span class="nx">fd</span><span class="p">)</span>
<span class="w">    </span><span class="k">break</span>
<span class="p">}</span>
</pre></div>
<p>其中 <code>pollDesc</code> 的实现在 <a href="http://golang.org/src/pkg/net/fd_poll_runtime.go">fd_poll_runtime.go</a> 文件中，最终实现是在 <a href="http://golang.org/src/pkg/runtime/netpoll.goc">netpoll.goc</a> 文件中，而在 Linux 下，是通过 epoll 来检测文件描符的，具体的实现细节可以看这里：<a href="https://github.com/tiancaiamao/go-internals/blob/master/ebook/08.1.md">Go 的非阻塞 I/O</a>。 <code>PrepareRead</code> 方法用于检测文件是否关闭和读写是否已经超时。当发生 <code>EAGAIN</code> 错误时，表示暂时还没有资源可读，需要待会儿再试，于是调用 <code>WaitRead</code> 来等待可读，再继续。通过这种方式，在用户层编程时，只需要按照正常的思维编写程序即可，不用再考虑各种由非阻塞I/O带来的错误。</p>
<p>Go 的 net 包非常强大，其中 <code>Conn</code> 类型是主角，通过 <code>Dial</code> 来建立各种类型的连接，并且可以实现自己的 <code>Dial</code>，比如在其中加上一些验证之类的握手过程。</p>
<h3>一点吐槽</h3>
<p>Go 的库是基于包的，于是各种类型或者方法散布在很多文件中，看起来真是让人崩溃，为了去找个突然出现的类型或者方法，在没用明显特征时，需要打开一个一个文件查找，或者 <code>find</code> 搜索，还有基于组合的类型系统，常常让人摸不着头脑。</p>
<p>在运行时，如果出现未使用变量或者导入了但没有使用的包，就会报错，这在调试程序时真的很是麻烦，如果能在使用 <code>go run</code> 时加一个不用检测未用变量和包的参数，或者只有在最终编译时才检测就好了。 </p>
]]>
      </content>
    </entry>
    <entry>
      <id><![CDATA[http://https://serholiu.com/bash-by-example]]></id>
      <title type="text"><![CDATA[通过示例入门 Bash 编程]]></title>
      <link href="http://https://serholiu.com/bash-by-example" rel="alternate" type="text/html"/>
      <published>2014-01-15T22:00:00Z</published>
      <content type="html">
       <![CDATA[<p>此前认为有了 Python，也就不太需要使用 Bash，不过到现在，觉得 Bash 用武之地仍然广泛。虽相比于其它设计精良的脚本语言，它表现的实在过于随意，不适合用于太大的工程，不过当其作为称职的“胶水”，将操作系统提供的各种命令粘在一起时，才会觉得它是必不可少的。那么，当大概了解各种命令后，对”胶水“自然就有了需求，恰好看到这篇<a href="http://matt.might.net/articles/bash-by-example/">通过示例学习 Bash</a> 的文章，于是大概将原文翻译一下，因为初学，可能多少会有点错误，还请各位指正。</p>
<p>这里主要是介绍 Bash 的基本语法，前提是你得了解类 Unix 系统和 shell，当然这里不会介绍各种 shell 命令的用法，如果你想让 Bash 成为一把利剑，掌握这些命令的用法是不可或缺的。Bash 是一种脚本语言，所以得有个解释器，而这个解释器的名称也叫 Bash，当然还有 Zsh 之类的，这个是可以设置的。</p>
<h3>执行</h3>
<p>Bash 是交互式的，你可以打开 shell，然后输入命令，然后得到结果，好吧，这其实就是使用命令行时做的事情，只不过没有用到太多 Bash 的语法而已。对于 Bash 源文件，需要将 <code>#!/bin/bash</code> 放在文件开头单独一行，然后让文件拥有可执行属性：<code>chmod u+x scriptname</code>，如果是在文件的当前目录，运行 <code>./scriptname</code> 后面加上参数(如果需要的话)就可以了。当 shell 执行这个脚本时，它就会寻找 <code>#!/path/to/interpreter</code> 这个解释器来运行。下面的代码将打印传入的第一个参数。因为这个 <code>#!</code> 的原因，Bash 使用 <code>#</code> 作为注释，以 <code>#</code> 号开始，一直到这行末尾都被视为注释。</p>
<div class="highlight"><pre><span></span><span class="ch">#!/bin/bash</span>

<span class="c1"># 使用 $1 获得第一个命令行参数</span>
<span class="nb">echo</span><span class="w"> </span><span class="nv">$1</span>
</pre></div>
<h3>基本类型</h3>
<p>类似于 Python，Bash 的变量不用显示声明类型，所以一个变量既可以是个数字，也可以将其赋值成数组。设置一个变量使用 <code>foo=3</code>，不过 <code>=</code> 两边不能有空格，如果写成 <code>foo = 3</code>，则 Bash 会认为是调用 <code>foo</code> 命令，<code>=</code> 和 <code>3</code> 则作为参数，如果确实想使用空格，需要使用 <code>((</code> 和 <code>))</code> 将表达式括起来。引用变量的值，使用美元符号 <code>$foo</code>。</p>
<p>删除变量的定义使用 <code>unset</code>:</p>
<div class="highlight"><pre><span></span><span class="nv">foo</span><span class="o">=</span><span class="m">42</span>
<span class="nb">echo</span><span class="w"> </span><span class="nv">$foo</span><span class="w">    </span><span class="c1"># prints 42</span>
<span class="nb">unset</span><span class="w"> </span>foo
<span class="nb">echo</span><span class="w"> </span><span class="nv">$foo</span><span class="w">    </span><span class="c1"># prints nothing</span>
</pre></div>
<p>当然可以将一个变量赋值给其它变量：</p>
<div class="highlight"><pre><span></span><span class="nv">foo</span><span class="o">=</span><span class="nv">$bar</span><span class="w">   </span><span class="c1"># 将 bar 的值赋给 foo</span>
</pre></div>
<p>如果值包含空格，需要使用引号括起来：</p>
<div class="highlight"><pre><span></span><span class="c1"># 错误:</span>
<span class="nv">foo</span><span class="o">=</span>x<span class="w"> </span>y<span class="w"> </span>z<span class="w">   </span><span class="c1"># 将 foo 设置为 x，然后执行 y 和 z</span>

<span class="c1"># 正确:</span>
<span class="nv">foo</span><span class="o">=</span><span class="s2">&quot;x y z&quot;</span><span class="w"> </span><span class="c1"># 设置 foo 为 &quot;x y z&quot;</span>
</pre></div>
<p>必要的时候可以使用大括号包裹变量来进行引用（实际上这是标准的做法，直接使用 <code>$foo</code> 只是简写而已）</p>
<div class="highlight"><pre><span></span><span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">foo</span><span class="si">}</span><span class="w"> </span><span class="c1"># prints $foo</span>
</pre></div>
<p>这个对输出数组元素时非常有用，Bash 里不用显示的声明一个数组，任何变量都可以是数组，只要按照数组的方式使用就行了，你可以用任何变量对数组进行赋值：</p>
<div class="highlight"><pre><span></span>foo<span class="o">[</span><span class="m">0</span><span class="o">]=</span><span class="s2">&quot;first&quot;</span><span class="w">  </span><span class="c1"># 设置第一个元素为 &quot;first&quot;</span>
foo<span class="o">[</span><span class="m">1</span><span class="o">]=</span><span class="s2">&quot;second&quot;</span><span class="w"> </span><span class="c1"># 设置第二个元素为 &quot;second&quot;</span>
</pre></div>
<p>使用索引来引用数组元素值的时候，需要使用大括号：</p>
<div class="highlight"><pre><span></span>foo<span class="o">[</span><span class="m">0</span><span class="o">]=</span><span class="s2">&quot;one&quot;</span>
foo<span class="o">[</span><span class="m">1</span><span class="o">]=</span><span class="s2">&quot;two&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">foo</span><span class="p">[1]</span><span class="si">}</span><span class="w">  </span><span class="c1"># prints &quot;two&quot;</span>
</pre></div>
<p>当你直接引用一个变量时，其实隐式引用的是其第一个元素：</p>
<div class="highlight"><pre><span></span>foo<span class="o">[</span><span class="m">0</span><span class="o">]=</span><span class="s2">&quot;one&quot;</span>
foo<span class="o">[</span><span class="m">1</span><span class="o">]=</span><span class="s2">&quot;two&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="nv">$foo</span><span class="w">       </span><span class="c1"># prints &quot;one&quot;</span>

<span class="nv">bar</span><span class="o">=</span><span class="s2">&quot;hello&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">bar</span><span class="p">[0]</span><span class="si">}</span><span class="w">  </span><span class="c1"># prints &quot;hello&quot;</span>
</pre></div>
<p>您也可以使用括号来创建一个数组:</p>
<div class="highlight"><pre><span></span><span class="nv">foo</span><span class="o">=(</span><span class="s2">&quot;a a a&quot;</span><span class="w"> </span><span class="s2">&quot;b b b&quot;</span><span class="w"> </span><span class="s2">&quot;c c c&quot;</span><span class="o">)</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">foo</span><span class="p">[2]</span><span class="si">}</span><span class="w">  </span><span class="c1"># prints &quot;c c c&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="nv">$foo</span><span class="w">       </span><span class="c1"># prints &quot;a a a&quot;</span>
</pre></div>
<p>可以使用 <code>@</code> 或者 <code>*</code> 这两个特殊下标来访问数组的所有元素：</p>
<div class="highlight"><pre><span></span><span class="nv">array</span><span class="o">=(</span>a<span class="w"> </span>b<span class="w"> </span>c<span class="o">)</span>
<span class="nb">echo</span><span class="w"> </span><span class="nv">$array</span><span class="w">       </span><span class="c1"># prints a</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">array</span><span class="p">[@]</span><span class="si">}</span><span class="w">  </span><span class="c1"># prints a b c</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">array</span><span class="p">[*]</span><span class="si">}</span><span class="w">  </span><span class="c1"># prints a b c</span>
</pre></div>
<p>可以这样拷贝一个数组：</p>
<div class="highlight"><pre><span></span><span class="nv">foo</span><span class="o">=(</span>a<span class="w"> </span>b<span class="w"> </span>c<span class="o">)</span>
<span class="nv">bar</span><span class="o">=(</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">foo</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&quot;</span><span class="o">)</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">bar</span><span class="p">[1]</span><span class="si">}</span><span class="w">    </span><span class="c1"># prints b</span>
</pre></div>
<p>不要试图直接赋值变量来进行拷贝，因为这样只是表示数组第一个元素而已:</p>
<div class="highlight"><pre><span></span><span class="nv">foo</span><span class="o">=(</span>a<span class="w"> </span>b<span class="w"> </span>c<span class="o">)</span>
<span class="nv">bar</span><span class="o">=</span><span class="nv">$foo</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">bar</span><span class="p">[1]</span><span class="si">}</span><span class="w">    </span><span class="c1"># prints nothing</span>
</pre></div>
<p>当然，不要忘了引号，否则含有空格的元素会出现错误：</p>
<div class="highlight"><pre><span></span><span class="nv">foo</span><span class="o">=(</span><span class="s2">&quot;a 1&quot;</span><span class="w"> </span><span class="s2">&quot;b 2&quot;</span><span class="w"> </span><span class="s2">&quot;c 3&quot;</span><span class="o">)</span>
<span class="nv">bar</span><span class="o">=(</span><span class="si">${</span><span class="nv">foo</span><span class="p">[@]</span><span class="si">}</span><span class="o">)</span>
<span class="nv">baz</span><span class="o">=(</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">foo</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&quot;</span><span class="o">)</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">bar</span><span class="p">[1]</span><span class="si">}</span><span class="w">            </span><span class="c1"># oops, print &quot;1&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">baz</span><span class="p">[1]</span><span class="si">}</span><span class="w">            </span><span class="c1"># prints &quot;b 2&quot;</span>
</pre></div>
<h3>特殊变量</h3>
<p>下面这些特殊变量用来得到传给脚本或者函数的参数：</p>
<div class="highlight"><pre><span></span><span class="nb">echo</span><span class="w"> </span><span class="nv">$0</span><span class="w">      </span><span class="c1"># 执行脚本的名称</span>

<span class="nb">echo</span><span class="w"> </span><span class="nv">$1</span><span class="w">      </span><span class="c1"># 打印第一个参数</span>
<span class="nb">echo</span><span class="w"> </span><span class="nv">$2</span><span class="w">      </span><span class="c1"># 打印第二个参数</span>
<span class="nb">echo</span><span class="w"> </span><span class="nv">$10</span><span class="w">     </span><span class="c1"># 打印第一个参数，后面再跟着个 0 </span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">10</span><span class="si">}</span><span class="w">   </span><span class="c1"># 正确的打印第十个参数的写法</span>

<span class="nb">echo</span><span class="w"> </span><span class="nv">$#</span><span class="w">      </span><span class="c1"># 打印参数个数</span>
</pre></div>
<p>变量 <code>$?</code> 用来访问前一个执行进程的退出状态（就是使用 <code>exit</code> 的值)</p>
<p>退出状态是 0 表示进程成功执行没有出现错误，其它状态表示出现了一些错误，在 shell 编程中，使用 <code>true</code> 命令来表示程序总是成功的， <code>false</code> 表示程序总是失败的：</p>
<div class="highlight"><pre><span></span><span class="nb">true</span>
<span class="nb">echo</span><span class="w"> </span><span class="nv">$?</span><span class="w">   </span><span class="c1"># prints 0</span>

<span class="nb">false</span>
<span class="nb">echo</span><span class="w"> </span><span class="nv">$?</span><span class="w">   </span><span class="c1"># 永远不会输出 0，通常是输出 1</span>
</pre></div>
<p>当前 shell 的进程 ID 通过 <code>$$</code> 访问。通过 <code>$!</code> 来得到最近后台进程的 ID：</p>
<div class="highlight"><pre><span></span><span class="c1"># sort two files in parallel:</span>
sort<span class="w"> </span>words<span class="w"> </span>&gt;<span class="w"> </span>sorted-words<span class="w"> </span><span class="p">&amp;</span><span class="w">        </span><span class="c1"># 启动后台进程</span>
<span class="nv">p1</span><span class="o">=</span><span class="nv">$!</span>
sort<span class="w"> </span>-n<span class="w"> </span>numbers<span class="w"> </span>&gt;<span class="w"> </span>sorted-numbers<span class="w"> </span><span class="p">&amp;</span><span class="w"> </span><span class="c1"># 启动后台进程</span>
<span class="nv">p2</span><span class="o">=</span><span class="nv">$!</span>
<span class="nb">wait</span><span class="w"> </span><span class="nv">$p1</span>
<span class="nb">wait</span><span class="w"> </span><span class="nv">$p2</span>
<span class="nb">echo</span><span class="w"> </span>Both<span class="w"> </span>files<span class="w"> </span>have<span class="w"> </span>been<span class="w"> </span>sorted.
</pre></div>
<h3>变量操作</h3>
<p>bash 可以引用一个对已有变量进行改变而得到的变量，如下面的字符串替换:</p>
<div class="highlight"><pre><span></span><span class="nv">foo</span><span class="o">=</span><span class="s2">&quot;I&#39;m a cat.&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">foo</span><span class="p">/cat/dog</span><span class="si">}</span><span class="w">  </span><span class="c1"># prints &quot;I&#39;m a dog.&quot;</span>

<span class="c1"># 使用双斜线替换所有匹配的字符串</span>
<span class="nv">foo</span><span class="o">=</span><span class="s2">&quot;I&#39;m a cat, and she&#39;s cat.&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">foo</span><span class="p">/cat/dog</span><span class="si">}</span><span class="w">   </span><span class="c1"># prints &quot;I&#39;m a dog, and she&#39;s a cat.&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">foo</span><span class="p">//cat/dog</span><span class="si">}</span><span class="w">  </span><span class="c1"># prints &quot;I&#39;m a dog, and she&#39;s a dog.&quot;</span>

<span class="c1"># 这些操作一般不会修改原变量</span>
<span class="nv">foo</span><span class="o">=</span><span class="s2">&quot;hello&quot;</span><span class="w"> </span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">foo</span><span class="p">/hello/goodbye</span><span class="si">}</span><span class="w">  </span><span class="c1"># prints &quot;goodbye&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="nv">$foo</span><span class="w">                  </span><span class="c1"># still prints &quot;hello&quot;</span>

<span class="c1"># 如果没有替换的，将直接删除被替换的</span>
<span class="nv">foo</span><span class="o">=</span><span class="s2">&quot;I like meatballs.&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">foo</span><span class="p">/balls</span><span class="si">}</span><span class="w">       </span><span class="c1"># prints I like meat.</span>
</pre></div>
<p>使用 <code>${name#pattern}</code> 来移除 <code>${name}</code> 匹配模式的最短前缀，如果是 <code>##</code> 则移除最长前缀：</p>
<div class="highlight"><pre><span></span><span class="nv">minipath</span><span class="o">=</span><span class="s2">&quot;/usr/bin:/bin:/sbin&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">minipath</span><span class="p">#/usr</span><span class="si">}</span><span class="w">           </span><span class="c1"># prints /bin:/bin:/sbin</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">minipath</span><span class="p">#*/bin</span><span class="si">}</span><span class="w">          </span><span class="c1"># prints :/bin:/sbin</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">minipath</span><span class="p">##*/bin</span><span class="si">}</span><span class="w">         </span><span class="c1"># prints :/sbin</span>
</pre></div>
<p>使用 <code>%</code> 代替 <code>#</code> 则可以用来匹配后缀：</p>
<div class="highlight"><pre><span></span><span class="nv">minipath</span><span class="o">=</span><span class="s2">&quot;/usr/bin:/bin:/sbin&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">minipath</span><span class="p">%/usr*</span><span class="si">}</span><span class="w">           </span><span class="c1"># prints nothing</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">minipath</span><span class="p">%/bin*</span><span class="si">}</span><span class="w">           </span><span class="c1"># prints /usr/bin:</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">minipath</span><span class="p">%%/bin*</span><span class="si">}</span><span class="w">          </span><span class="c1"># prints /usr</span>
</pre></div>
<h3>字符串/数组操作</h3>
<p>Bash 处理字符串和数组时往往使用的是相同的运算符，例如，前缀运算符 <code>＃</code> 用来计算字符串的字符数或者数组的成员数，一个常见的错误是，数组的第一个元素恰好是字符串，比如很多初学者教程都用下面这个例子来说明这种错误用法： </p>
<div class="highlight"><pre><span></span><span class="nv">ARRAY</span><span class="o">=(</span>one<span class="w"> </span>two<span class="w"> </span>three<span class="o">)</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${#</span><span class="nv">ARRAY</span><span class="si">}</span><span class="w">          </span><span class="c1"># prints 3 -- 好像是对的</span>

<span class="nv">ARRAY</span><span class="o">=(</span>a<span class="w"> </span>b<span class="w"> </span>c<span class="o">)</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${#</span><span class="nv">ARRAY</span><span class="si">}</span><span class="w">          </span><span class="c1"># prints 1 -- 咦？</span>
</pre></div>
<p>这是因为 <code>${#ARRAY}</code> 和 <code>${#ARRAY[0]}</code> 是一样的，这样只是得到数组第一个元素的字符数而已。正确的方法是：</p>
<div class="highlight"><pre><span></span><span class="nv">ARRAY</span><span class="o">=(</span>a<span class="w"> </span>b<span class="w"> </span>c<span class="o">)</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${#</span><span class="nv">ARRAY</span><span class="p">[@]</span><span class="si">}</span><span class="w">      </span><span class="c1"># prints 3</span>
</pre></div>
<p>对字符串或者数组进行切片：</p>
<div class="highlight"><pre><span></span><span class="nv">string</span><span class="o">=</span><span class="s2">&quot;I&#39;m a fan of dogs.&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">string</span><span class="p">:</span><span class="nv">6</span><span class="p">:</span><span class="nv">3</span><span class="si">}</span><span class="w">           </span><span class="c1"># prints fan</span>

<span class="nv">array</span><span class="o">=(</span>a<span class="w"> </span>b<span class="w"> </span>c<span class="w"> </span>d<span class="w"> </span>e<span class="w"> </span>f<span class="w"> </span>g<span class="w"> </span>h<span class="w"> </span>i<span class="w"> </span>j<span class="o">)</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">array</span><span class="p">[@]:</span><span class="nv">3</span><span class="p">:</span><span class="nv">2</span><span class="si">}</span><span class="w">         </span><span class="c1"># prints d e</span>
</pre></div>
<h3>存在性测试</h3>
<p>有些操作测试变量是否被设置：</p>
<div class="highlight"><pre><span></span><span class="nb">unset</span><span class="w"> </span>username
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">username</span><span class="p">-default</span><span class="si">}</span><span class="w">        </span><span class="c1"># prints default</span>

<span class="nv">username</span><span class="o">=</span>admin
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">username</span><span class="p">-default</span><span class="si">}</span><span class="w">        </span><span class="c1"># prints admin</span>
</pre></div>
<p>如果需要强制测试变量是否为空，则可以使用 <code>:-</code>:</p>
<div class="highlight"><pre><span></span><span class="nb">unset</span><span class="w"> </span>foo
<span class="nb">unset</span><span class="w"> </span>bar

<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">foo</span><span class="p">-abc</span><span class="si">}</span><span class="w">   </span><span class="c1"># prints abc</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">bar</span><span class="k">:-</span><span class="nv">xyz</span><span class="si">}</span><span class="w">  </span><span class="c1"># prints xyz</span>

<span class="nv">foo</span><span class="o">=</span><span class="s2">&quot;&quot;</span>
<span class="nv">bar</span><span class="o">=</span><span class="s2">&quot;&quot;</span>

<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">foo</span><span class="p">-123</span><span class="si">}</span><span class="w">   </span><span class="c1"># prints nothing</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">bar</span><span class="k">:-</span><span class="nv">456</span><span class="si">}</span><span class="w">  </span><span class="c1"># prints 456</span>
</pre></div>
<p>操作符 <code>= (or :=)</code> 和 <code>-</code> 类似, 但它可以在变量没有值时对变量赋值：</p>
<div class="highlight"><pre><span></span><span class="nb">unset</span><span class="w"> </span>cache
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">cache</span><span class="p">:=1024</span><span class="si">}</span><span class="w">   </span><span class="c1"># prints 1024</span>
<span class="nb">echo</span><span class="w"> </span><span class="nv">$cache</span><span class="w">           </span><span class="c1"># prints 1024</span>

<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">cache</span><span class="p">:=2048</span><span class="si">}</span><span class="w">   </span><span class="c1"># prints 1024</span>
<span class="nb">echo</span><span class="w"> </span><span class="nv">$cache</span><span class="w">           </span><span class="c1"># prints 1024</span>
</pre></div>
<p>操作符 <code>+</code> 改变已经设置的变量，如果变量未设置，则什么都不做:</p>
<div class="highlight"><pre><span></span><span class="nb">unset</span><span class="w"> </span>foo
<span class="nb">unset</span><span class="w"> </span>bar

<span class="nv">foo</span><span class="o">=</span><span class="m">30</span>

<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">foo</span><span class="p">+42</span><span class="si">}</span><span class="w">    </span><span class="c1"># prints 42</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">bar</span><span class="p">+1701</span><span class="si">}</span><span class="w">  </span><span class="c1"># prints nothing</span>
</pre></div>
<p>操作符 <code>?</code> 测试变量是否设置，如果没有则显示指定的信息并终止程序执行:</p>
<div class="highlight"><pre><span></span>:<span class="w"> </span><span class="o">{</span><span class="m">1</span>?failure:<span class="w"> </span>no<span class="w"> </span>arguments<span class="o">}</span><span class="w"> </span><span class="c1"># 如果没有第一个参数将终止程序</span>
</pre></div>
<p>(:是一个特殊的命令，称为空命令，该命令不做任何事，并且忽略给它的所有参数，但 Exit Status 总是真)</p>
<h3>间接查找</h3>
<p>Bash 允许使用 <code>!</code> 前缀来间接的实现变量/数组的查找。其实，<code>${!expr}</code> 和 <code>${${expr}}</code> 效果类似:</p>
<div class="highlight"><pre><span></span><span class="nv">foo</span><span class="o">=</span>bar
<span class="nv">bar</span><span class="o">=</span><span class="m">42</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="p">!foo</span><span class="si">}</span><span class="w">  </span><span class="c1"># 打印 $bar, 即 42</span>

<span class="nv">alpha</span><span class="o">=(</span>a<span class="w"> </span>b<span class="w"> </span>c<span class="w"> </span>d<span class="w"> </span>e<span class="w"> </span>f<span class="w"> </span>g<span class="w"> </span>h<span class="w"> </span>i<span class="w"> </span>j<span class="w"> </span>k<span class="w"> </span>l<span class="w"> </span>m<span class="w"> </span>n<span class="w"> </span>o<span class="w"> </span>p<span class="w"> </span>q<span class="w"> </span>r<span class="w"> </span>s<span class="w"> </span>t<span class="w"> </span>u<span class="w"> </span>v<span class="w"> </span>w<span class="w"> </span>x<span class="w"> </span>y<span class="w"> </span>z<span class="o">)</span>
<span class="nv">char</span><span class="o">=</span>alpha<span class="o">[</span><span class="m">12</span><span class="o">]</span>

<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="p">!char</span><span class="si">}</span><span class="w"> </span><span class="c1"># 打印 ${alpha[12]}, 即 m</span>
</pre></div>
<h3>数组中的 * 和 @</h3>
<p>有两个额外的特殊变量 <code>$*</code> 和 <code>$@</code>，大多数时候通过 <code>${array[*]</code> 或者 <code>${array[@]}</code> 来访问数组都可以。但这两者表示传递给当前脚本/函数的参数，并被引号括起来时，他们会有些不同，为了说明这一点的区别，这里创建几个辅助脚本。</p>
<p>首先是 print12：</p>
<div class="highlight"><pre><span></span><span class="ch">#!/bin/bash</span>

<span class="c1"># 打印前两个参数</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;first:  </span><span class="nv">$1</span><span class="s2">&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;second: </span><span class="nv">$2</span><span class="s2">&quot;</span>
</pre></div>
<p>然后创建 showargs：</p>
<div class="highlight"><pre><span></span><span class="ch">#!/bin/bash</span>

<span class="nb">echo</span><span class="w"> </span><span class="nv">$*</span>
<span class="nb">echo</span><span class="w"> </span><span class="nv">$@</span>

<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$*</span><span class="s2">&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$@</span><span class="s2">&quot;</span>

bash<span class="w"> </span>print12<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$*</span><span class="s2">&quot;</span>
bash<span class="w"> </span>print12<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$@</span><span class="s2">&quot;</span>
</pre></div>
<p>现在执行 showargs：</p>
<div class="highlight"><pre><span></span>$<span class="w"> </span>bash<span class="w"> </span>showargs<span class="w"> </span><span class="m">0</span><span class="w">  </span><span class="s2">&quot; 1    2  3&quot;</span>
</pre></div>
<p>运行结果是：</p>
<div class="highlight"><pre><span></span><span class="m">0</span><span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="m">2</span><span class="w"> </span><span class="m">3</span>
<span class="m">0</span><span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="m">2</span><span class="w"> </span><span class="m">3</span>
<span class="m">0</span><span class="w">  </span><span class="m">1</span><span class="w">    </span><span class="m">2</span><span class="w">  </span><span class="m">3</span>
<span class="m">0</span><span class="w">  </span><span class="m">1</span><span class="w">    </span><span class="m">2</span><span class="w">  </span><span class="m">3</span>
first:<span class="w">  </span><span class="m">0</span><span class="w">  </span><span class="m">1</span><span class="w">    </span><span class="m">2</span><span class="w">  </span><span class="m">3</span>
second:<span class="w"> </span>
first:<span class="w">  </span><span class="m">0</span>
second:<span class="w">  </span><span class="m">1</span><span class="w">    </span><span class="m">2</span><span class="w">  </span><span class="m">3</span>
</pre></div>
<p>出现这样的结果是因为 <code>$*</code> 将所有参数组合成一个单一的字符串, <code>$@</code> 将重新对各个参数添加引用。这两者之间的另一个微妙的差异是如果变量 IFS(内部字段分隔符)被设置，那么这个变量将作为 <code>$*</code> 用来拼接各参数的分隔符。创建一个脚本 atvstar：</p>
<div class="highlight"><pre><span></span><span class="ch">#!/bin/bash</span>

<span class="nv">IFS</span><span class="o">=</span><span class="s2">&quot;,&quot;</span>

<span class="nb">echo</span><span class="w"> </span><span class="nv">$*</span>
<span class="nb">echo</span><span class="w"> </span><span class="nv">$@</span>

<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$*</span><span class="s2">&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$@</span><span class="s2">&quot;</span>
</pre></div>
<p>执行它：</p>
<div class="highlight"><pre><span></span>$<span class="w"> </span>bash<span class="w"> </span>atvstar<span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="m">2</span><span class="w"> </span><span class="m">3</span><span class="w"> </span>

<span class="c1"># to print:</span>
<span class="c1"># 1 2 3</span>
<span class="c1"># 1 2 3</span>
<span class="c1"># 1,2,3</span>
<span class="c1"># 1 2 3</span>
</pre></div>
<p>IFS 必须包含一个字符。使用数组作为参数传递给函数时，也会出现上面的现象：</p>
<div class="highlight"><pre><span></span><span class="nv">arr</span><span class="o">=(</span><span class="s2">&quot;a b&quot;</span><span class="w">  </span><span class="s2">&quot; c d    e&quot;</span><span class="o">)</span>

<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">arr</span><span class="p">[*]</span><span class="si">}</span><span class="w">            </span><span class="c1"># prints a b c d e</span>
<span class="nb">echo</span><span class="w"> </span><span class="si">${</span><span class="nv">arr</span><span class="p">[@]</span><span class="si">}</span><span class="w">            </span><span class="c1"># prints a b c d e</span>

<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">arr</span><span class="p">[*]</span><span class="si">}</span><span class="s2">&quot;</span><span class="w">          </span><span class="c1"># prints a b  c d    e</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">arr</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&quot;</span><span class="w">          </span><span class="c1"># prints a b  c d    e</span>

bash<span class="w"> </span>print12<span class="w"> </span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">arr</span><span class="p">[*]</span><span class="si">}</span><span class="s2">&quot;</span><span class="w">  </span>
<span class="c1"># prints:</span>
<span class="c1"># first:  a b  c d    e</span>
<span class="c1"># second:</span>

bash<span class="w"> </span>print12<span class="w"> </span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">arr</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="c1"># prints:</span>
<span class="c1"># first:  a b</span>
<span class="c1"># second:  c d    e</span>
</pre></div>
<h3>字符串</h3>
<p>字符串是字符序列，创建字符串使用单引号，创建插值字符串使用双引号：</p>
<div class="highlight"><pre><span></span><span class="nv">world</span><span class="o">=</span>Earth
<span class="nv">foo</span><span class="o">=</span><span class="s1">&#39;Hello, $world!&#39;</span>
<span class="nv">bar</span><span class="o">=</span><span class="s2">&quot;Hello, </span><span class="nv">$world</span><span class="s2">!&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="nv">$foo</span><span class="w">            </span><span class="c1"># prints Hello, $world!</span>
<span class="nb">echo</span><span class="w"> </span><span class="nv">$bar</span><span class="w">            </span><span class="c1"># prints Hello, Earth!</span>
</pre></div>
<p>在插值字符串中，变量会被替换为它的值。</p>
<h3>作用域</h3>
<p>在 Bash 中，变量的作用域是进程范围的：所有进程都拥有自有变量的副本。此外，变量只有显式的导入到子进程，才能被子进程所访问：</p>
<div class="highlight"><pre><span></span><span class="nv">foo</span><span class="o">=</span><span class="m">42</span>
bash<span class="w"> </span>somescript<span class="w">          </span><span class="c1"># somescript 里无法访问 foo</span>

<span class="nb">export</span><span class="w"> </span>foo
bash<span class="w"> </span>somescript<span class="w">          </span><span class="c1"># somescript 可以访问 foo</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;foo = &quot;</span><span class="w"> </span><span class="nv">$foo</span><span class="w">       </span><span class="c1"># 总是打印 foo = 42</span>
</pre></div>
<p>这里假设 somescript 如下所示:</p>
<div class="highlight"><pre><span></span><span class="ch">#!/bin/bash</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;old foo = </span><span class="nv">$foo</span><span class="s2">&quot;</span>
<span class="nv">foo</span><span class="o">=</span><span class="m">300</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;new foo = </span><span class="nv">$foo</span><span class="s2">&quot;</span>
</pre></div>
<p>运行上面程序得到的结果是:</p>
<div class="highlight"><pre><span></span>old<span class="w"> </span><span class="nv">foo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>
new<span class="w"> </span><span class="nv">foo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">300</span>
old<span class="w"> </span><span class="nv">foo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">42</span>
new<span class="w"> </span><span class="nv">foo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">300</span>
<span class="nv">foo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">42</span>
</pre></div>
<h3>表达式和运算</h3>
<p>在 Bash 中书写算术表达式必须的小心谨慎，<code>expr</code> 命令用来打印算术表达式的结果，当然，请谨慎对待：</p>
<div class="highlight"><pre><span></span>expr<span class="w"> </span><span class="m">3</span><span class="w"> </span>+<span class="w"> </span><span class="m">12</span><span class="w">      </span><span class="c1"># prints 15</span>
expr<span class="w"> </span><span class="m">3</span><span class="w"> </span>*<span class="w"> </span><span class="m">12</span><span class="w">      </span><span class="c1"># (可能) 出错: * 会扩展到所有文件</span>
expr<span class="w"> </span><span class="m">3</span><span class="w"> </span><span class="se">\*</span><span class="w"> </span><span class="m">12</span><span class="w">     </span><span class="c1"># prints 36</span>
</pre></div>
<p>你得小心对待空格或者 Bash 的展开，使用 <code>(( assignable = expression ))</code> 来求值表达式会让你轻松一点:</p>
<div class="highlight"><pre><span></span><span class="o">((</span><span class="w"> </span><span class="nv">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">3</span><span class="w"> </span>+<span class="w"> </span><span class="m">12</span><span class="w"> </span><span class="o">))</span><span class="p">;</span><span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="nv">$x</span><span class="w">    </span><span class="c1"># prints 15</span>
<span class="o">((</span><span class="w"> </span><span class="nv">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">3</span><span class="w"> </span>*<span class="w"> </span><span class="m">12</span><span class="w"> </span><span class="o">))</span><span class="p">;</span><span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="nv">$x</span><span class="w">    </span><span class="c1"># prints 36</span>
</pre></div>
<p>如果你不想申明一个临时变量来保存求值结果，可以使用 <code>$((expression))</code> 来完成：</p>
<div class="highlight"><pre><span></span><span class="nb">echo</span><span class="w"> </span><span class="k">$((</span><span class="w"> </span><span class="m">3</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="m">12</span><span class="w"> </span><span class="k">))</span><span class="w">   </span><span class="c1"># prints 15</span>
<span class="nb">echo</span><span class="w"> </span><span class="k">$((</span><span class="w"> </span><span class="m">3</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="m">12</span><span class="w"> </span><span class="k">))</span><span class="w">   </span><span class="c1"># prints 36</span>
</pre></div>
<p>Bash 中声明变量通常是隐式的（在定义时就默认已声明了变量），而且大部分时候是这样做的，不过也可以显式的声明变量，这样可以明确变量类型。使用 <code>declare -i variable</code> 来显式的创建一个整型变量：</p>
<div class="highlight"><pre><span></span><span class="nb">declare</span><span class="w"> </span>-i<span class="w"> </span>number
<span class="nv">number</span><span class="o">=</span><span class="m">2</span>+4*10
<span class="nb">echo</span><span class="w"> </span><span class="nv">$number</span><span class="w">        </span><span class="c1"># prints 42</span>

<span class="nv">another</span><span class="o">=</span><span class="m">2</span>+4*10
<span class="nb">echo</span><span class="w"> </span><span class="nv">$another</span><span class="w">       </span><span class="c1"># prints 2+4*10</span>

<span class="nv">number</span><span class="o">=</span><span class="s2">&quot;foobar&quot;</span>
<span class="nb">echo</span><span class="w"> </span><span class="nv">$number</span><span class="w">        </span><span class="c1"># prints 0</span>
</pre></div>
<p>赋值给整型变量将强制进行表达式的求值。</p>
<h3>文件和重定向</h3>
<p>任何 Unix 进程可以访问默认的三个输入/输出通道：STDIN(标准输入), STDOUT(标准输出), STDERR(标准出错)。</p>
<blockquote><ul>
<li>写入 STDOUT，输出默认显示在控制台</li>
<li>从 STDIN 读时，默认直接读取用户从控制台输入的内容</li>
<li>写入 STDERR，输出默认显示在控制台</li>
</ul>
</blockquote>
<p>上面的这三个通道都可以被重定向。例如，要将一个文件的内容作为标准输入（而不是用户输入），使用 <code>&lt;</code> 操作符即可：</p>
<div class="highlight"><pre><span></span><span class="c1"># 打印出文件中包含单词 foo 的行</span>
grep<span class="w"> </span>foo<span class="w"> </span>&lt;<span class="w"> </span>myfile
</pre></div>
<p>将命令的结果输出到文件（而不是控制台），使用 <code>&gt;</code> 操作符：</p>
<div class="highlight"><pre><span></span><span class="c1"># 将 file2 与 file1 合并，输出到 combined</span>
cat<span class="w"> </span>file1<span class="w"> </span>file2<span class="w"> </span>&gt;<span class="w"> </span>combined
</pre></div>
<p>如果只是添加新内容到输出文件结尾，使用 <code>&gt;&gt;</code> 操作符：</p>
<div class="highlight"><pre><span></span><span class="c1"># 将当前日期和时间添加到 log 文件末尾</span>
date<span class="w"> </span>&gt;&gt;<span class="w"> </span>log
</pre></div>
<p>如果要从 STDIN 读取内容，可以使用 <code>&lt;&lt;endmarker</code> 来规定何时结束读取，例如使用：</p>
<div class="highlight"><pre><span></span>cat<span class="w"> </span>&lt;&lt;UNTILHERE
</pre></div>
<p>这将提示进行输入，并显示输入内容，当输入 <code>UNTILHERE</code> 时，将结束读取。通常这种格式被称为 <a href="http://zh.wikipedia.org/wiki/Here文档">Here 文档</a>，在 shell 中它通常用于给命令提供输入内容。重定向出错输出(STDERR)，使用 <code>2&gt;</code> 操作符：</p>
<div class="highlight"><pre><span></span><span class="c1"># 从 httpd 进程启动时将错误写入 error.log</span>
httpd<span class="w"> </span><span class="m">2</span>&gt;<span class="w"> </span>error.log
</pre></div>
<p>事实上，所有 I/O 通道都可以使用一个数字来描述，所以 <code>&gt;</code> 其实就是 <code>1&gt;</code>。STDIN 是通道0，STDOUT 是1，STDERR 是2。<code>M&gt;&amp;N</code> 则表示将通道 M 输出重定向到 N。所以下面是将错误输出显示到 STDOUT：</p>
<div class="highlight"><pre><span></span>grep<span class="w"> </span>foo<span class="w"> </span>nofile<span class="w"> </span><span class="m">2</span>&gt;<span class="p">&amp;</span><span class="m">1</span><span class="w"> </span><span class="c1"># 错误会出现在 STDOUT</span>
</pre></div>
<p>可以捕获使用反引号包裹的命令的标准输出：</p>
<div class="highlight"><pre><span></span><span class="c1"># 将 date 和 whoami 命令的标准输出添加到 log</span>
<span class="nb">echo</span><span class="w"> </span><span class="sb">`</span>date<span class="sb">`</span><span class="w"> </span><span class="sb">`</span>whoami<span class="sb">`</span><span class="w"> </span>&gt;&gt;<span class="w"> </span>log
</pre></div>
<p>使用 <code>$(command)</code> 符号可以达到相同的目的:</p>
<div class="highlight"><pre><span></span><span class="c1"># 将 date 和 whoami 命令的标准输出添加到 log</span>
<span class="nb">echo</span><span class="w"> </span><span class="k">$(</span>date<span class="k">)</span><span class="w"> </span><span class="k">$(</span>whoami<span class="k">)</span><span class="w"> </span>&gt;&gt;<span class="w"> </span>log
</pre></div>
<p>从文件中读入内容经常使用 <code>cat path-to-file</code>, 不过Bash 拥有一个相同功能的内建方法 <code>&lt;path-to-file</code>：</p>
<div class="highlight"><pre><span></span><span class="nb">echo</span><span class="w"> </span>user:<span class="w"> </span><span class="sb">`</span>&lt;config/USER<span class="sb">`</span><span class="w"> </span><span class="c1"># 打印 config/USER 的内容</span>
</pre></div>
<p>Bash 中一个特殊的命令 <code>exec</code> 可以在命令范围内操作通道。</p>
<div class="highlight"><pre><span></span><span class="nb">exec</span><span class="w"> </span>&lt;<span class="w"> </span>file<span class="w"> </span><span class="c1"># 标准输入现在是 file</span>
<span class="nb">exec</span><span class="w"> </span>&gt;<span class="w"> </span>file<span class="w"> </span><span class="c1"># 标准输出现在是 file</span>
</pre></div>
<p>你可能需要备份一下 STDIN 和 STDOUT，这样后来需要的时候可以恢复回来：</p>
<div class="highlight"><pre><span></span><span class="nb">exec</span><span class="w"> </span><span class="m">7</span>&lt;<span class="p">&amp;</span><span class="m">0</span><span class="w"> </span><span class="c1"># 将 STDIN 保存在通道 7</span>
<span class="nb">exec</span><span class="w"> </span><span class="m">6</span>&gt;<span class="p">&amp;</span><span class="m">1</span><span class="w"> </span><span class="c1"># 将 STDOUT 保存在通道 6</span>
</pre></div>
<p>比如你想将脚本中一段代码的输出输出到文件，你可以这样做:</p>
<div class="highlight"><pre><span></span><span class="nb">exec</span><span class="w"> </span><span class="m">6</span>&gt;<span class="p">&amp;</span><span class="m">1</span><span class="w">       </span><span class="c1"># 将 STDOUT 保存在通道 6</span>
<span class="nb">exec</span><span class="w"> </span>&gt;<span class="w"> </span>LOGFILE<span class="w">  </span><span class="c1"># 好了，现在标准输出变成 LOGFILE 了</span>

<span class="c1"># 其它命令</span>

<span class="nb">exec</span><span class="w"> </span><span class="m">1</span>&gt;<span class="p">&amp;</span><span class="m">6</span><span class="w">       </span><span class="c1"># 重新将标准输出变为 STDOUT</span>
</pre></div>
<h3>管道</h3>
<p>如果想将一个进程的 STDOUT 作为另一进程的 STDIN，则可以使用 <code>|</code> 管道操作符：</p>
<div class="highlight"><pre><span></span><span class="c1"># 从 passwd 文件中输出 root 的条目</span>
cat<span class="w"> </span>/etc/passwd<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>root
</pre></div>
<p>管道的使用一般形式是：<code>outputing-command | inputing-command</code>，并且它可以将多个命令连接起来，就像流水线一样：</p>
<div class="highlight"><pre><span></span><span class="c1"># 一行命令找出当前目录占用空间大小的前10个文件(包括目录)</span>

<span class="c1"># du -cks *  # 打印当前目录文件占用空间大小</span>

<span class="c1"># sort -rn   # 依据 STDIN 输入的第一列，以数字作为标准进行排序，并倒序输出</span>

<span class="c1"># head       # 打印 STDIN 输入的前十行</span>

du<span class="w"> </span>-cks<span class="w"> </span>*<span class="w"> </span><span class="p">|</span><span class="w"> </span>sort<span class="w"> </span>-rn<span class="w"> </span><span class="p">|</span><span class="w"> </span>head
</pre></div>
<p>有些程序接受一个文件名，并从对应文件中读取，而不是从 STDIN 读。对于这些程序，或接受多个文件名的程序，有一种方法可以用来创建一个临时文件，其中包含一个命令的输出，即 <code>&lt;(command)</code> 形式。 </p>
<div class="highlight"><pre><span></span><span class="c1"># 将 uptime 与 date 的输出， event.log 最后一行添加到 main.log</span>
cat<span class="w"> </span>&lt;<span class="o">(</span>uptime<span class="o">)</span><span class="w"> </span>&lt;<span class="o">(</span>date<span class="o">)</span><span class="w"> </span>&lt;<span class="o">(</span>tail<span class="w"> </span>-1<span class="w"> </span>event.log<span class="o">)</span><span class="w"> </span>&gt;&gt;<span class="w"> </span>main.log
</pre></div>
<h3>进程</h3>
<p>Bash 的过人之处正是在于协调进程，管道将多个进程连接在一起，进行流水线似的作业，也可以并行的运行进程。在后台执行命令，可以使用 <code>&amp;</code> 后缀符号。</p>
<div class="highlight"><pre><span></span>time-consuming-command<span class="w"> </span><span class="p">&amp;</span>
</pre></div>
<p>通过 <code>$!</code> 这个特殊变量来获取刚派生的进程 ID：</p>
<div class="highlight"><pre><span></span>time-consuming-command<span class="w"> </span><span class="p">&amp;</span>
<span class="nv">pid</span><span class="o">=</span><span class="nv">$!</span>
</pre></div>
<p>使用 <code>wait</code> 命令来等待一个进程的结束：</p>
<div class="highlight"><pre><span></span>time-consuming-command<span class="w"> </span><span class="p">&amp;</span>
<span class="nv">pid</span><span class="o">=</span><span class="nv">$!</span>
<span class="nb">wait</span><span class="w"> </span><span class="nv">$pid</span>
<span class="nb">echo</span><span class="w"> </span>Process<span class="w"> </span><span class="nv">$pid</span><span class="w"> </span>finished.
</pre></div>
<p>如果 <code>wait</code> 后面没有进程 ID，将等待所有子进程结束。下面将一个文件中的所有 JPEG 文件转换为 PNG 文件：</p>
<div class="highlight"><pre><span></span><span class="k">for</span><span class="w"> </span>f<span class="w"> </span><span class="k">in</span><span class="w"> </span>*.jpg
<span class="k">do</span><span class="w"> </span>
<span class="w">  </span>convert<span class="w"> </span><span class="nv">$f</span><span class="w"> </span><span class="si">${</span><span class="nv">f</span><span class="p">%.jpg</span><span class="si">}</span>.png<span class="w"> </span><span class="p">&amp;</span>
<span class="k">done</span><span class="w"> </span>
<span class="nb">wait</span>
<span class="nb">echo</span><span class="w"> </span>All<span class="w"> </span>images<span class="w"> </span>have<span class="w"> </span>been<span class="w"> </span>converted.
</pre></div>
<h3>Glob 模式</h3>
<p>Bash 可以使用 glob 符号来匹配字符串和文件名(glob 类似于通配符，只是扩展到可以匹配到多个文件或路径)。在大多数情况下，一个 glob 模式会自动扩展到包含所有匹配文件名的数组：</p>
<div class="highlight"><pre><span></span><span class="nb">echo</span><span class="w"> </span>*.txt<span class="w">        </span><span class="c1"># 打印所有 txt 文件名</span>
<span class="nb">echo</span><span class="w"> </span>*.<span class="o">{</span>jpg,jpeg<span class="o">}</span><span class="w"> </span><span class="c1"># 打印所有 JPEG 文件名</span>
</pre></div>
<p>Glob 模式有以下几种特殊形式:</p>
<blockquote><ol>
<li><ul>
<li>匹配任意字符串</li>
</ul>
</li>
<li>? 匹配单个字符</li>
<li>[chars] 匹配在 chars 中的任意字符</li>
<li>[a-b] 匹配在 a 和 b 间的所有字符(包括 a 和 b)</li>
</ol>
</blockquote>
<p>使用这些模式，可以非常轻松的删除所有文件名类似于 <code>fileNNN</code> 的文件，其中 <code>NNN</code> 是三个数字：</p>
<div class="highlight"><pre><span></span>rm<span class="w"> </span>file<span class="o">[</span><span class="m">0</span>-9<span class="o">][</span><span class="m">0</span>-9<span class="o">][</span><span class="m">0</span>-9<span class="o">]</span>
</pre></div>
<p>还有一种大括号形式，它类似于进行模式的组合，比如 `{str1，str2，...，strN} 将扩展 str1 或者 str2 等等，下面这个例子可以清晰的表现这种形式的作用：</p>
<div class="highlight"><pre><span></span><span class="nb">echo</span><span class="w"> </span><span class="o">{</span><span class="m">0</span>,1<span class="o">}</span><span class="w">              </span><span class="c1"># prints 0 1</span>
<span class="nb">echo</span><span class="w"> </span><span class="o">{</span><span class="m">0</span>,1<span class="o">}{</span><span class="m">0</span>,1<span class="o">}</span><span class="w">         </span><span class="c1"># prints 00 01 10 11</span>
<span class="nb">echo</span><span class="w"> </span><span class="o">{</span><span class="m">0</span>,1<span class="o">}{</span><span class="m">0</span>,1<span class="o">}{</span><span class="m">0</span>,1<span class="o">}</span><span class="w">    </span><span class="c1"># prints 000 001 010 011 100 101 110 111</span>
</pre></div>
<h3>控制结构</h3>
<p>像很多程序语言一样，Bash 支持条件，迭代，子程序等控制结构。类似于 If-then-else-style 的条件结构在 Bash 中也存在，但是在 Bash 中，条件是一个命令，这个命令成功退出的状态是 0，表示 "true"，否则表示失败，退出状态为非 0，表示 "false"：</p>
<div class="highlight"><pre><span></span><span class="c1"># this will print:</span>
<span class="k">if</span><span class="w"> </span><span class="nb">true</span>
<span class="k">then</span><span class="w">  </span>
<span class="w">  </span><span class="nb">echo</span><span class="w"> </span>printed
<span class="k">fi</span>

<span class="c1"># this will not print:</span>
<span class="k">if</span><span class="w"> </span><span class="nb">false</span>
<span class="k">then</span><span class="w">  </span>
<span class="w">  </span><span class="nb">echo</span><span class="w"> </span>not<span class="w"> </span>printed
<span class="k">fi</span>
</pre></div>
<p>Bash 中可以通过程序的状态来采取不同的操作：</p>
<div class="highlight"><pre><span></span><span class="k">if</span><span class="w"> </span>httpd<span class="w"> </span>-k<span class="w"> </span>start
<span class="k">then</span>
<span class="w">  </span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;httpd started OK&quot;</span>
<span class="k">else</span>
<span class="w">  </span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;httpd failed to start&quot;</span>
<span class="k">fi</span>
</pre></div>
<p>在 Bash 中，很多条件都是来自于特殊命令进行的测试，这些测试需要使用标志来确定采用何种测试，一些比较常用的标志包括：</p>
<blockquote><ul>
<li>-e file: 当指定的文件或者目录存在时为真</li>
<li>-z string: 指定的字符串为空时为真</li>
<li>string1 = string2: 两个字符串相同时为真</li>
</ul>
</blockquote>
<p>可以使用 <code>[ args ]</code> 来进行测试:</p>
<div class="highlight"><pre><span></span><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;-v&quot;</span><span class="w"> </span><span class="o">]</span>
<span class="k">then</span>
<span class="w">  </span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;switching to verbose output&quot;</span>
<span class="w">  </span><span class="nv">VERBOSE</span><span class="o">=</span><span class="m">1</span>
<span class="k">fi</span>
</pre></div>
<p>在使用迭代时，<code>while</code>，<code>do</code> 会在测试命令返回非0退出状态时结束：</p>
<div class="highlight"><pre><span></span><span class="c1"># 如果 httpd 程序崩溃，将自动重启它</span>
<span class="k">while</span><span class="w"> </span><span class="nb">true</span>
<span class="k">do</span>
<span class="w">   </span>httpd
<span class="k">done</span>
</pre></div>
<p>在 For-In 循环中使用 <code>do...done</code> 可以遍历完所有元素：</p>
<div class="highlight"><pre><span></span><span class="c1"># 编译当前目录下的所有 C 源程序</span>
<span class="k">for</span><span class="w"> </span>f<span class="w"> </span><span class="k">in</span><span class="w"> </span>*.c
<span class="k">do</span>
<span class="w">  </span>gcc<span class="w"> </span>-o<span class="w"> </span><span class="si">${</span><span class="nv">f</span><span class="p">%.c</span><span class="si">}</span><span class="w"> </span><span class="nv">$f</span>
<span class="k">done</span>
</pre></div>
<p>Bash 中的函数有点像独立的脚本，有两种形式来定义一个函数：</p>
<div class="highlight"><pre><span></span><span class="k">function</span><span class="w"> </span>name<span class="w"> </span><span class="o">{</span>
<span class="w">  </span>commands
<span class="o">}</span>

<span class="c1"># and</span>

name<span class="w"> </span><span class="o">()</span><span class="w"> </span><span class="o">{</span>
<span class="w">  </span>commands
<span class="o">}</span>
</pre></div>
<p>一旦声明，函数的行为几乎像一个单独的脚本：函数的自变量也采用 <code>$n</code> 这种形式来获得。一个主要的不同之处在于函数可以查看和修改在其外部脚本中定义的变量：</p>
<div class="highlight"><pre><span></span><span class="nv">count</span><span class="o">=</span><span class="m">20</span>

<span class="k">function</span><span class="w"> </span>showcount<span class="w"> </span><span class="o">{</span>
<span class="w">  </span><span class="nb">echo</span><span class="w"> </span><span class="nv">$count</span>
<span class="w">  </span><span class="nv">count</span><span class="o">=</span><span class="m">30</span>
<span class="o">}</span>

showcount<span class="w">    </span><span class="c1"># prints 20</span>
<span class="nb">echo</span><span class="w"> </span><span class="nv">$count</span><span class="w">  </span><span class="c1"># prints 30</span>
</pre></div>
<h3>一些例子</h3>
<p>终于，学完上面的一些内容，可以使用 Bash 完成一些简单的程序了。下面是一个求阶乘的函数:</p>
<div class="highlight"><pre><span></span><span class="k">function</span><span class="w"> </span>fact<span class="w"> </span><span class="o">{</span>
<span class="w">  </span><span class="nv">result</span><span class="o">=</span><span class="m">1</span>
<span class="w">  </span><span class="nv">n</span><span class="o">=</span><span class="nv">$1</span>
<span class="w">  </span><span class="k">while</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$n</span><span class="s2">&quot;</span><span class="w"> </span>-ge<span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="o">]</span>
<span class="w">  </span><span class="k">do</span>
<span class="w">    </span><span class="nv">result</span><span class="o">=</span><span class="k">$(</span>expr<span class="w"> </span><span class="nv">$n</span><span class="w"> </span><span class="se">\*</span><span class="w"> </span><span class="nv">$result</span><span class="k">)</span>
<span class="w">    </span><span class="nv">n</span><span class="o">=</span><span class="k">$(</span>expr<span class="w"> </span><span class="nv">$n</span><span class="w"> </span>-<span class="w"> </span><span class="m">1</span><span class="k">)</span>
<span class="w">  </span><span class="k">done</span>
<span class="w">  </span><span class="nb">echo</span><span class="w"> </span><span class="nv">$result</span>
<span class="o">}</span>
</pre></div>
<p>或者采用另一种形式的算术运算：</p>
<div class="highlight"><pre><span></span><span class="k">function</span><span class="w"> </span>facter<span class="w"> </span><span class="o">{</span>
<span class="w">  </span><span class="nv">result</span><span class="o">=</span><span class="m">1</span>
<span class="w">  </span><span class="nv">n</span><span class="o">=</span><span class="nv">$1</span>
<span class="w">  </span><span class="k">while</span><span class="w"> </span><span class="o">((</span><span class="w"> </span>n<span class="w"> </span>&gt;<span class="o">=</span><span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="o">))</span>
<span class="w">  </span><span class="k">do</span>
<span class="w">    </span><span class="o">((</span><span class="w"> </span><span class="nv">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>n<span class="w"> </span>*<span class="w"> </span>result<span class="w"> </span><span class="o">))</span>
<span class="w">    </span><span class="o">((</span><span class="w"> </span><span class="nv">n</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>n<span class="w"> </span>-<span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="o">))</span>
<span class="w">  </span><span class="k">done</span>
<span class="w">  </span><span class="nb">echo</span><span class="w"> </span><span class="nv">$result</span>
<span class="o">}</span>
</pre></div>
<p>或者直接声明整型变量：</p>
<div class="highlight"><pre><span></span>factered<span class="w"> </span><span class="o">()</span><span class="w"> </span><span class="o">{</span>
<span class="w">  </span><span class="nb">declare</span><span class="w"> </span>-i<span class="w"> </span>result
<span class="w">  </span><span class="nb">declare</span><span class="w"> </span>-i<span class="w"> </span>n

<span class="w">  </span><span class="nv">n</span><span class="o">=</span><span class="nv">$1</span>
<span class="w">  </span><span class="nv">result</span><span class="o">=</span><span class="m">1</span>

<span class="w">  </span><span class="k">while</span><span class="w"> </span><span class="o">((</span><span class="w"> </span>n<span class="w"> </span>&gt;<span class="o">=</span><span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="o">))</span>
<span class="w">  </span><span class="k">do</span>
<span class="w">    </span><span class="nv">result</span><span class="o">=</span>n*result
<span class="w">    </span><span class="nv">n</span><span class="o">=</span>n-1
<span class="w">  </span><span class="k">done</span>

<span class="w">  </span><span class="nb">echo</span><span class="w"> </span><span class="nv">$result</span>
<span class="o">}</span>
</pre></div>
<h3>后话</h3>
<p>总体说来，这篇文章介绍的只是比较浅显的内容，后面介绍控制结构时比较粗略，这里推荐看以下这篇文章：<a href="http://learn.akae.cn/media/ch31s05.html">Shell脚本语法</a>。总的说来，Bash 真印证了其是”未经设计“的，太多的陷阱和奇怪的命令，让人不知所措，这只能靠多用和多看来解决了。当然，Bash 的精髓在于发挥胶水作用，将 Unix 下的各种强大命令组合在一起，要达到 Bash 达人的境界，学习和掌握常见或者和你工作领域相关的命令就是必不可少的，如果真的达到那一步，那么限制你的只能是想象力了。</p>
]]>
      </content>
    </entry>
</feed>