<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Hi! 上天不?</title>
  
  <subtitle>新一的个人博客</subtitle>
  <link href="https://a-wing.top/feed.xml" rel="self"/>
  
  <link href="https://a-wing.top/"/>
  <updated>2026-09-06T04:00:00.000Z</updated>
  <id>https://a-wing.top/</id>
  
  <author>
    <name>Metal A-wing</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>论「结构性失恋」</title>
    <link href="https://a-wing.top/self/2026/08/31/on-structural-heartbreak"/>
    <id>https://a-wing.top/self/2026/08/31/on-structural-heartbreak</id>
    <published>2026-08-31T04:00:00.000Z</published>
    <updated>2026-09-06T04:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近失恋了，也没什么可以隐瞒的，想复盘一下，但却发现只要一回想就会悲伤得不能自己</p><p>失恋分两种：</p><ul><li>「操作性失恋」死于失误——错误的表白时机、错误的礼物、没接住的情绪，这些本可避免</li><li>「结构性失恋」死于不可调和的矛盾——即使每一步都做对，关系依然无法成立</li></ul><p>复盘的意义就在于分清两者：前者教人长记性，后者教人认命</p><p>我这次是一次看起来像「操作性失恋」的「结构性失恋」，人是怎么可以这样同时犯下这么多错误</p><p><strong>某次旅行相识然后经历的表白被拒然后被全平台彻底拉黑</strong></p><p>本来这件事情就彻底过去了，也没什么需要讨论的。不过我内心一直有些疑问没想清楚。这些问题如果不想清楚，下一段关系应该也可能出现相同的问题。另外就是感觉自己忽略了什么很重要的东西，但又不知道是什么</p><p>当一个问题出现，应该复盘，并总结相关的问题通用解法</p><p>写这篇文章的目的就是：「我尝试去理解一切」</p><p>类似我这种失恋后科学化复盘的行为，德波顿在《爱情笔记》里称之为「爱情实证主义」</p><p>最近看到的这些书，大部分的作者都以失恋为契机系统研究「亲密关系」</p><p><img src="/assets/img/2026/img_9742.jpeg" alt="img_9742.jpeg"></p><p>另一件事就是：最近特别喜欢《小王子》。到处引用《小王子》的话</p><p>这里值得推荐的：</p><ul><li>安托万·德·圣埃克苏佩里《小王子》当然是最推荐的，只是单纯的喜欢这本</li><li>罗兰·米勒《亲密关系》这本书作为这里最重要参考资料</li><li>米兰·昆德拉《不能承受的生命之轻》这是一本哲学小说，虽然情节露骨，晦涩，难懂。轻与重便是我们的人生课题。没有重负的生命不是解脱，而是失重后的虚无。</li><li>西蒙娜·德·波伏娃《第二性》女性主义重要著作</li><li>阿兰·德波顿《爱情笔记》内容深度相对比较浅一些</li></ul><p>还有就是最近喜欢看「三毛」的作品：《撒哈拉的故事》《雨季不再来》《梦里花落知多少》《稻草人手记》</p><p>大部分推荐的书都和哲学有关，或许人在伤心的时候喜欢看哲学吧</p><h2 id="推论基础"><a href="#推论基础" class="headerlink" title="推论基础"></a>推论基础</h2><p>试图用第一性原理出发，利用思想实验来重新思考和验证这些问题</p><p>这里做了一些基本假设：</p><ol><li>人类一定需要亲密关系（我们在这里需要把这个设置为一个绝对可靠的推论起点，不然全部的推论都不成立。归属需要是人类的基本动机之一（Baumeister &amp; Leary, 1995）。本文假设它对「我和大多数人」成立）</li><li>众所周知，社会科学，尤其是心理学的可信度都比较低。这里主要使用 罗兰·米勒《亲密关系》的理论。可信度大概可以到 80%。这目前是我能找到的相关研究里可信度最高的资料了</li><li>需要考虑一般大众对这件事情的认知，所以参考了非常多的大众心理读物（不过这类可信度大概可以到 50%，这里可以认为没什么用）不过引用大众认知是为了对比和解释现实存在的一些情况</li></ol><p>注：本文使用了 AI 辅助做「思维实验」。并且人工复核过「思维实验」正确性<br>注：本文使用了 AI 的数据源做了标注（数据源基本可靠）</p><h2 id="如何面对悲伤"><a href="#如何面对悲伤" class="headerlink" title="如何面对悲伤"></a>如何面对悲伤</h2><p>有很多方法可以使用，最简单方法就是给对方随便扣个帽子，然后把这个放到最讨厌的里面</p><p>只要欺骗自己认为一切都是对方的错，那就不需要悲伤，因为我终于离开了那个「垃圾」</p><p>或者就是当她死了：比如我有两个指向，好朋友的指向是「她」，喜欢的人的指向也是「她」。把喜欢的人的指向「已死亡」，这样就可以完成关系上的切割，又同时不会损伤友情。可以减轻痛苦，可以不再联系</p><p>但我选择了最痛苦的方式「复盘」。这个也许和商业项目似乎没有什么本质区别，失败了需要复盘，投入的时间要考虑投入产出比。但我很快发现，这样复盘会令我非常痛苦</p><p>我终于明白，原来谈恋爱和谈项目是不一样，不需要签合同，不需要一个明确的开始，也不需要确定任何事。只是像朋友一样相处，最糟糕的事情来了：我似乎没有朋友。我把和她约会那天记在了 Calendar 里，就像预约客户一样，虽然总觉得哪里不对，但又不知道是哪里不对。但项目谈崩了也就崩了，恋爱如果谈崩了，原来心会痛。原来这两件事情有着本质区别</p><p>果然，原来这个世界上很少有人复盘恋爱，是有原因的</p><h2 id="论「磨合」"><a href="#论「磨合」" class="headerlink" title="论「磨合」"></a>论「磨合」</h2><p>我俩有个相同点，不约而同地反对相互「磨合」。为了相处磨去了棱角，这听起来太糟糕</p><p>心理学上，亲密关系初期冲突不断的阶段，正统术语叫「<strong>权力争夺期」（Power Struggle）</strong></p><p>但实际上「权力争夺期」翻译成了「磨合期」，「权力争夺期」本质上是在描述这一时期的问题，但「磨合期」这个词却换成了一个解决方法。这是一种「问题即解法」的包装</p><p>「互相磨平棱角」（那本质是要求其中一方或双方让渡自我），应该是「重新签合同」</p><p>我们承认这里有冲突，然后我们像两个独立的国家一样，谈判、划界、签订互不侵犯条约</p><p>把这个叫「磨合」很容易把因果关系倒置：把「本质冲突」伪造成「沟通不畅」</p><p>另一个问题是如果看成「重新签合同」会非常容易陷入「零和博弈」的陷阱</p><blockquote><p>谁是敌人？谁是朋友？</p></blockquote><p>在这个问题中，伴侣是朋友，冲突才是敌人。需要和伴侣共同解决「冲突这个敌人」（但这里太容易把伴侣看成敌人从而开始「零和博弈」）</p><h3 id="权力不对等"><a href="#权力不对等" class="headerlink" title="权力不对等"></a>权力不对等</h3><p>不知道你有没注意到，一方非常强势，另一方非常弱势。婚姻会稳定，但这并不代表亲密关系的稳定</p><p>权力不对等，确实更容易促成「关系的开始」和「表面的稳定」，但几乎无法通向「健康的、深度的、长久的稳定」</p><p>「模范夫妻」的关系，可能维持着一种「僵化平衡」</p><ul><li>回避冲突：低位者为了避免失去关系，会压抑自己的需求。高位者为了维持权威，会拒绝沟通。双方形成默契——不谈矛盾，矛盾就不存在</li><li>分工固化：像 「雇主-雇员」或「家长-孩子」那样各司其职，这种结构非常清晰，不容易因「谁说了算」而争吵。但这种稳定是「零摩擦」的稳定，就像没有生命的雕塑，而不是活生生的河流。</li></ul><p>当然权力并不只有社会资源，比如「舔狗类型」是「最少兴趣原则」（Principle of Least Interest，Waller, 1938）的一种体现：对维持关系兴趣更小、依赖更少的一方，拥有更大的权力</p><p>这样的一种结构很容易造成一个人把另一个人「磨没了」</p><h3 id="「希望你变得更好」"><a href="#「希望你变得更好」" class="headerlink" title="「希望你变得更好」"></a>「希望你变得更好」</h3><p>我们本能地会把心中的理想伴侣投射到对方身上，其实本质上喜欢的不是对方，而是那个理想中的伴侣。</p><p>以「爱的名义」，以「希望你变得更好」而改造对方，把对方变成理想中的伴侣</p><p>「希望你变得更好」几乎是优秀个体的标志性气质——但它同时也是他们在亲密关系里 <strong>最隐蔽的陷阱</strong></p><p>优秀的人长期生活在一个「永远可以更好」的反馈回路里：业绩可以更高、排名可以更前、技能可以更精。他们的神经系统被训练成了 <strong>持续扫描-发现问题-提出改进方案</strong> 的模式。</p><p>当这种模式被带到亲密关系里，伴侣就变成了下一个「优化对象」——不是出于恶意，而是因为他们真的看不到「不优化」这个选项。</p><p>优秀的人对自己极其苛刻，这种苛刻会自动溢出到亲近的人身上。他们潜意识里认为：「我都对自己这么严格了，我对你严格是因为我把你当自己人」</p><p>但他们没意识到的是：<strong>对自己的苛刻是自己选的，对伴侣的苛刻是强加的</strong></p><p>很多优秀者信奉「成长型思维」（growth mindset，Dweck 提出），相信人可以通过努力改变。这本身没问题，但问题是他们常常把「愿不愿意改变」等同于「在不在乎这段关系」。</p><p>于是「我希望你变得更好」变成了一种忠诚测试：如果你爱我，你就会接受我的建议，变成更好的自己。</p><p>但「希望你更好」有两种完全不同的面孔。这是最关键的分辨：</p><table><thead><tr><th></th><th><strong>A：作为爱的延伸</strong></th><th><strong>B：作为爱的条件</strong></th></tr></thead><tbody><tr><td><strong>标准来源</strong></td><td>「你想成为什么样的人？我支持你」</td><td>「你应该成为我期待的样子」</td></tr><tr><td><strong>对「现在」的态度</strong></td><td>我接纳现在的你，同时相信你有潜力</td><td>现在的你不够好，需要被修正</td></tr><tr><td><strong>当对方拒绝时</strong></td><td>尊重你的节奏，甚至欣赏你的固执</td><td>感到挫败、失望，觉得对方「不上进」</td></tr><tr><td><strong>底层恐惧</strong></td><td>怕你不能成为<strong>你想成为的人</strong></td><td>怕你不能成为<strong>我想让你成为的人</strong></td></tr><tr><td><strong>本质</strong></td><td>陪伴</td><td>改造</td></tr></tbody></table><p>优秀个体的问题在于：<strong>他们太擅长把 B 包装成 A。</strong></p><p>因为 B 的措辞可以很动人：「我希望你发挥潜力」「我不想看你浪费天赋」 「我相信你可以更好」——这些话听起来像爱，但内核是 <strong>「你现在的样子，我不满意」</strong></p><p>当对方明确说「我不想变，我现在这样就很好」的时候，你是感到失望，还是感到释然？</p><ul><li>如果你感到失望，那你的「希望」本质上是控制。</li><li>如果你感到释然，那你的「希望」才可能是真爱。</li></ul><p>因为真正的爱，不是把对方推向一个你设定的未来，而是与对方一起站在现在，允许未来不确定</p><h3 id="双优困境"><a href="#双优困境" class="headerlink" title="双优困境"></a>双优困境</h3><blockquote><p>这个现象在学界没有对应术语。最接近的研究传统是「双事业伴侣」（dual-career couples）</p><p>—— INSEAD 教授 Jennifer Petriglieri 的《Couples That Work》（2019）访谈了 32 个国家 113 对双事业伴侣，她同样承认该领域存在文献空白。</p></blockquote><p>但她讨论的是两份职业如何协调，这里要说的是另一个更隐蔽的东西：两套以成就为底的心理防御系统如何互相激活。姑且称之为「双优困境」。</p><p>不过「困境」的根源不是「优秀」本身，人口学数据恰好指向反方向：过去几十年，离婚率的下降集中在高学历群体（Martin, 2006），教育程度相当的婚姻相对稳定性也在持续上升（Schwartz &amp; Han, 2014）。</p><p>真正被研究证实伤关系的，是常与优秀「伴生」、却并非其必然产物的另一组东西：</p><ul><li><strong>指向伴侣的完美主义</strong>：把高标准架在对方头上，直接拉低关系满意度（Stoeber, 2012）</li><li><strong>强迫性的工作投入</strong>（workaholism）：沿「溢出-交叉」路径侵蚀关系质量（Bakker, Demerouti &amp; Burke, 2009）</li><li><strong>绑定在成就上的自我价值</strong>：赢了就安全，输了就一文不值（Crocker &amp; Wolfe, 2001；Crocker &amp; Park, 2004）</li></ul><p>高标准但不苛求的人心理功能反而更好（Stoeber &amp; Otto, 2006）——<strong>伤人的从来不是标准，是恐惧。</strong>「双优困境」不是「优秀」的代价，而是「用优秀来防御」的代价。</p><p><strong>防御机制的「共振」导致「镜像战争」</strong></p><blockquote><p>很多「优秀」本身就是一种防御机制——用成就来掩盖对失控的恐惧、对被抛弃的恐惧、对「我不够好」的恐惧</p><p>当两个人的防御撞在一起，他们握着同一套武器：理性、逻辑、自我反思、表达能力</p><ul><li>普通人吵架：情绪对抗，很快有一方妥协</li><li>双优吵架：论文级辩论——「你的需求不合理，因为…」「我的逻辑更自洽，证据是…」</li></ul><p>结果：战争无限升级，因为没有「弱者」来终止冲突。两个人都太擅长论证自己、太不擅长认输</p></blockquote><p>爱情的战场上，赢就是输。当你用理性证明了对方「错」，你并没有赢得关系，你只是赢得了辩论——而对方在心里默默扣了一分。更致命的是，两个优秀者都太习惯「赢」了，认输这个选项根本不在他们的操作系统里</p><p>优秀者要走出这个困境，需要的不是更多的努力、更好的沟通技巧、或更高的情商，而是一种完全陌生的体验——在一段关系里，不赢，也能安全。而这，恰恰是优秀最难以学会的事</p><h2 id="论灵魂伴侣"><a href="#论灵魂伴侣" class="headerlink" title="论灵魂伴侣"></a>论灵魂伴侣</h2><p>这个词其实有非常多的解释含义，对于这个词我们需要分类讨论</p><h3 id="完美型灵魂伴侣"><a href="#完美型灵魂伴侣" class="headerlink" title="完美型灵魂伴侣"></a>完美型灵魂伴侣</h3><p>我们每个人都有对自己另一半的想象，事实上那个完美的另一半就是性转版的自己（包括截然相反的互补性，是人内心的投射，并不是实际指的外在表现）</p><p>但是从概率上讲，理论上确实存在这种匹配的可能</p><blockquote><p>xkcd 作者、前 NASA 机器人工程师 Randall Munroe 在《What If》的〈Soul Mates〉一章里算过一个更极端的版本：<br>假设每人有且仅有一个随机分配的灵魂伴侣，对视即认。<br>不考虑性别、语言和地域，在世且年龄相近的候选池约 5 亿人，而一生大约只与 5 万个陌生人目光接触——这意味着一万辈子里才有一次遇到真爱（0.01%）</p><p>如果把历史上存在过的约 1000 亿人都算进去，90% 的灵魂伴侣早就死了</p><p>如果不要求「命中注定的那一个」，只要求「十个维度全部匹配」，这样的人存在几个？</p><p>下面这张表是 Kimi K3 辅助构建的示意模型：</p><table><thead><tr><th><strong>筛选条件</strong></th><th><strong>剩余比例</strong></th></tr></thead><tbody><tr><td>性别合适</td><td>50%</td></tr><tr><td>年龄合适（±10岁）</td><td>15%</td></tr><tr><td>语言相通</td><td>10%</td></tr><tr><td>地理位置可及</td><td>5%</td></tr><tr><td>教育水平相当</td><td>20%</td></tr><tr><td>性格兼容</td><td>10%</td></tr><tr><td>价值观匹配</td><td>10%</td></tr><tr><td>外貌互认</td><td>10%</td></tr><tr><td>生活方式兼容</td><td>10%</td></tr><tr><td>幽默感匹配</td><td>5%</td></tr></tbody></table><p>联合概率：0.5×0.15×0.10×…≈3.75×10⁻¹⁰<br>按世界 80 亿人口估算：</p><ul><li>潜在匹配人数：约 3 人</li><li>一生约遇见 80,000 人，期望遇到 0.00003 人</li><li>遇到至少一个的概率：约 0.003%</li></ul><p>注：各筛选条件并非相互独立（教育程度与价值观相关、地理位置与语言相关），连乘会低估联合概率，真实候选人数应高于表内结果；「一生遇见 8 万人」为流传估算值而非研究数据。</p><p>即使放宽一个数量级，结论方向不变：<strong>完美匹配在现实中几乎不可遇</strong></p></blockquote><p>从现实主义角度分析，这几乎是不可能的，你可以骗自己是完美匹配的</p><p>不过我们目前所属的信息时代，可以大数据来完成这种操作，比如婚恋网站用 OkCupid 算法</p><p>不过值得一提的是：<strong>婚恋网站的盈利模式会更倾向让用户一直在找而不是快速找到另一半。</strong><br>这是核心商业模式与用户目标之间存在的一种结构性矛盾。不过这不是重点，所以不展开讨论。</p><p>把人抽象成一个复杂的向量，然后对两个向量计算匹配率。只要数据量足够多，就可以找到最匹配的人</p><p>不过这听起来可一点也不浪漫，当然人不是机器，人是复杂的，人是不会满足机器的算法的</p><h3 id="「AI-恋人」和「完美型灵魂伴侣」"><a href="#「AI-恋人」和「完美型灵魂伴侣」" class="headerlink" title="「AI 恋人」和「完美型灵魂伴侣」"></a>「AI 恋人」和「完美型灵魂伴侣」</h3><p>被拉黑后我非常痛苦，脑海里突然冒出了一个想法</p><blockquote><p>如果它走起路来像鸭子，叫起来也像鸭子，那么它就是鸭子</p><p>—— 鸭子测试（Duck Test）</p></blockquote><p>如果我把她的人格数据进行蒸馏，聊天记录作为她数字分身的记忆，用 AI 来创造一个数字分身，她就可以一直陪着我了</p><p>如果考虑需求，只能通过手机交互，就如同永远不能见面的异地恋一样，那 AI 来模拟她是一个更好的选择吗？按照目前的 AI 可以做到这种程度。甚至可做出一个比她更符合我的「AI 恋人」，她甚至可以是「完美型灵魂伴侣」</p><p>我们来做个思维实验：</p><ol><li>假设对恋爱的需求可以被 AI 满足（目前似乎只能满足异地恋情况，随着科技水平提高，一定可以满足全部需求）</li><li>AI 恋人会被拟合到与你同步率极高的水平，甚至 100%。但当 100% 出现时，它和「你自己」没有任何区别——你事实上仍然没有一个伴侣（同步率不满 100% 的会被替换掉；而如果允许异化的冲突，它在行为上和人已经没有区别）</li><li>就算允许冲突：当你获得了 Reset 的权限的时候，她对你来说已经不具有「主体性」了</li><li>假设「AI 恋人」获得了「主体性」，这意味着你仍然还会失恋</li></ol><p>我们可以得出一个结论：<strong>正因为会有他者不可被完全消解的主体性，才造就了亲密关系的美好</strong></p><h3 id="姻缘型灵魂伴侣"><a href="#姻缘型灵魂伴侣" class="headerlink" title="姻缘型灵魂伴侣"></a>姻缘型灵魂伴侣</h3><p>这类人有个共同特点，在恋爱确认期的时候非常甜。对各种特质进行计算然后依次匹配，一旦发现不匹配，判断不是合适，然后迅速撤离</p><p>「姻缘灵魂伴侣」和「完美灵魂伴侣」看似一样。完美型是出于完全基于概率计算。<br>姻缘型会有因为发生一些事件然后进行自我欺骗，比如由于某些事件认为「上天注定」我们在一起<br>非常不幸，这样的一种概念已经被各种影视作品用烂了，导致非常多的人认为这就是「灵魂伴侣」</p><p>这本质上是一种自我欺骗</p><h3 id="「宿命信念」和「成长信念」"><a href="#「宿命信念」和「成长信念」" class="headerlink" title="「宿命信念」和「成长信念」"></a>「宿命信念」和「成长信念」</h3><ul><li>她要寻找「灵魂伴侣」</li><li>我要寻找「合适的人」</li></ul><p>本质上我们都在寻找「姻缘型灵魂伴侣」</p><blockquote><p>持有成长信念的人在伴侣犯错或发生冲突时，更忠于关系、更乐观地相信伤害可以得到平复，并且愿意心平气和地讨论问题；而持有宿命信念的人一谈到伴侣缺陷就充满敌意，甚至选择结束关系</p><p>—— 罗兰·米勒《亲密关系》</p></blockquote><p>姻缘论（宿命信念） 和 成长论（成长信念）</p><ul><li>姻缘论者认为只要找的那个对的人，就可以了</li><li>成长论者认为需要互相经营和成长，好的关系是经营出来的</li></ul><blockquote><p>这套东西的学名叫 <strong>「内隐关系理论」（implicit theories of relationships，ITRs）</strong>，主要开创者是休斯顿大学的 C. Raymond Knee，另一支是 Renae Franiuk。<br>「缘分论／成长论」就是他们的 destiny beliefs／growth beliefs（Franiuk 叫 soulmate theory／work-it-out theory）。</p></blockquote><p>成长论的基本假设：只要双方付出足够的努力，那么任何关系都能获得成功。不过极端的成长论并不可取</p><p>不幸的是，有非常多的人过于相信「宿命信念」而忽略了成长——「宿命信念」支撑着他们不去成长，本质上是一种自我欺骗。可「相信命运自会安排」，期待关系无需经营就会好起来，这又何尝不是一种扭曲的「成长信念」？</p><p>人是一个动态过程，随着时间流逝，当时对于匹配率的判断只能说明当时的情况，如果两个成长速率不匹配，匹配率也会逐渐发生漂移。</p><blockquote><p>关系的成功更依赖于双方的成长信念和共同努力，而非一开始就找到 「完美匹配」 的人。</p><p>—— 罗兰·米勒《亲密关系》</p></blockquote><p>所以所谓「灵魂伴侣」不是找到了「灵魂伴侣」，而是你们互相让对方成长为彼此的「灵魂伴侣」</p><h3 id="论「三观」和「排雷」"><a href="#论「三观」和「排雷」" class="headerlink" title="论「三观」和「排雷」"></a>论「三观」和「排雷」</h3><p>相信你一定听过这样的营销号论述，问各种问题来判断三观，和父母的关系，和朋友的关系来提前「排雷」</p><p>先说结论：这两个都不靠谱。恋爱博主本身就是生意，这就和算命先生一样，只要去问就是「凶」，然后有「逢凶化吉」的可能性。恋爱博主给的方法就是测出来都不合适，排雷会导致全部泛雷化</p><p>其实恋爱开始前真正需要验证的是「是否存在结构上无法调和的矛盾」，因为剩下的都是可以被解决的问题</p><p>试图在几次见面中判断「三观」是非常不靠谱的事情，事实上大部分人都没法自己判断自己的「三观」。通过引导式提问更是没法准确判断。而正确判断「三观」需要长期的接触</p><p>从概率上来说，我们同样可以对「三观匹配」做一个示意估算。先把「三观」操作化：它不是模糊的一团，而是少数几个高利害决策域——要不要孩子、定居哪里、金钱怎么管、宗教政治立场、与原生家庭的边界。全量的价值观框架远不止这些（Schwartz 基本价值观理论为 19 维，原始世界信念清单为 26 维），但真正构成结构性矛盾的只有这几个。</p><table><thead><tr><th>标准</th><th>模型事件</th><th>示意概率</th><th>现实含义</th></tr></thead><tbody><tr><td><strong>严格一致</strong>（所有观点相同）</td><td>全量 19 维全部同向：(1&#x2F;3)¹⁹</td><td>≈ 0%（10⁻⁹ 量级）</td><td>不存在，也不必追求</td></tr><tr><td><strong>核心一致</strong>（高利害维度全部相容）</td><td>5 维全中：(1&#x2F;3)⁵</td><td>≈ 0.4%</td><td>需要主动筛选，不能靠随机</td></tr><tr><td><strong>可兼容</strong>（允许 1–2 个维度失配，能沟通、愿妥协）</td><td>≥4&#x2F;5 维 ≈ 4.5%；≥3&#x2F;5 维 ≈ 21%</td><td>~5–20%</td><td>这是现实中「合适」的标准</td></tr></tbody></table><p>注：与上一节的估算一样，各维度并非相互独立（政治立场与宗教态度相关、生育意愿与原生家庭边界相关），且「每个维度三种立场等概率」只是保守示意——本表只用来说明数量级。</p><p>追求「三观相同」的人，不如追求「三观可兼容」的人。前者是数学上的不可能事件，后者即使在最保守的假设下也有 5–20%——这才是关系经营的可达成目标。</p><blockquote><p>临床和研究表明，真正的「雷」（red flag）有严格定义——它们是行为模式，而非孤立事件；它们信号的是对方无法或不愿维持健康互惠关系；涉及权力失衡、需求被无视、或自我感被侵蚀；且倾向于升级而非自行改善 。</p><p>举个例子：对方偶尔情绪失控（人都会），和长期用蔑视（contempt）作为沟通方式——后者被 Gottman 的研究证实为关系解体的最强预测因子，准确率极高 。前者是瑕疵，后者是「雷」</p></blockquote><h3 id="家庭观念和男女差异"><a href="#家庭观念和男女差异" class="headerlink" title="家庭观念和男女差异"></a>家庭观念和男女差异</h3><p>如果试图用引导式提问来判断三观，部分指标（比如家庭）常被假定存在男女差别：</p><blockquote><p>女性普遍比男性更重视家庭——社会角色理论认为，这类差异主要来自社会化的角色分工，而非生物决定</p><p>—— Eagly (1987) 提出，Eagly &amp; Wood (1999) 综述</p></blockquote><p>另一个是关于资金流向的数据：女性掌控的收入更多流向子女的健康和教育，而男性更倾向于保护自己的经济未来（房产、创业）（Duflo, 2012）</p><p>这里家庭观念分成「原生家庭」和「新家庭」。说到原生家庭，一个流传很广的说法是：女性感知到更高的父母参与度、自主支持与情感温暖，而男性感知到更高的心理控制与行为规范。刻板印象和大众心理学都这么认为，但并没有严格的数据能证明这样的差距——两个大型元分析显示，父母在温暖和控制维度上对儿子女儿几乎一视同仁（Lytton &amp; Romney, 1991；Endendijk et al., 2016）。细分来看：「女性感知更高的父母参与度」在南欧大学生样本里复现过，但效应很小（García-Mendoza et al., 2022）；「男性感知更高的心理控制」连同一团队的跨国复现都没能重复；「男性被管得更多」在青少年阶段方向甚至是反的（Kerr &amp; Stattin, 2000）。「同一行为被两性解读成不同的东西」是个很有诱惑力的解释，但这个机制至今没有被直接检验过。</p><p>证据最实的性别差异其实在别处——成年后的支持网络结构：女性更多承担「亲属维系」的角色，与父母的联系和情感支持多于儿子（Rosenthal, 1985；Fingerman et al., 2020）；男性的首要情感倾诉对象则高度集中在伴侣身上（Umberson et al., 1996）。</p><p>推荐：小透明明TM 的视频分享</p><p><a href="https://www.bilibili.com/video/BV1y5Gt6SEtK">18岁就体验十几次离婚，我为什么还是结婚了？_哔哩哔哩_bilibili</a></p><p><a href="https://www.bilibili.com/video/BV1wPjc67EoU">免费保姆、七年之痒、失去自由，为什么我们恐惧婚姻？_哔哩哔哩_bilibili</a></p><p>通常，我们会认为和「原生家庭」好的「新家庭」也会很好。但实际上相关性只有 r ≈ 0.1–0.3——这是家庭代际传递研究的常见量级（如 Conger et al., 2000）。非常意外，比想象中的低好多。按 Rosenthal &amp; Rubin 的二项效应量换算（BESD：成功率 ≈ 0.5 + r&#x2F;2）折成概率约 55%-65%，对于个体来说就比抛硬币猜正反高一点</p><p>三观虽然包括「新家庭」观念，但是在恋爱开始前判断「新家庭」的权重非常不靠谱的。在恋爱之前都不存在这个东西，为什么有人可以预先排好三观的权重顺序？恋爱前「新家庭」都不存在，那怎么给它排序？</p><p>这正是「排雷」最荒谬的地方：</p><ul><li>被测者无法诚实回答——因为问题本身指向一个不存在的情境</li><li>答案高度不稳定——同一个问题，恋爱前和恋爱后的回答可能完全相反</li><li>标准答案陷阱——「伴侣第一」是政治正确，但现实中能做到的人极少，且「做到」的定义在关系深入后会被重新定义</li></ul><p>一个经典例子：很多女生问男生「我和你妈同时掉水里你救谁」。这个问题在恋爱前问，得到的只是表演性答案。只有在真实经历了「母亲反对这门婚事」或「妻子产后抑郁需要全天候照顾」后，这个排序才会被行动揭示出来。</p><blockquote><p>为什么人们会「假装」能回答？这里有几个心理学机制：</p><ol><li><strong>代理变量谬误</strong>：人们用原生家庭的经验来代理回答。「我爸妈感情很好，所以家庭对我很重要」是在说原生家庭，不是说未来伴侣；「我从小缺爱，所以渴望有自己的家」是创伤驱动的渴望，不是真实的优先级。原生家庭的相处模式 ≠ 未来新家庭的相处模式——用前者推断后者，就像用「你和你爸的关系」来预测「你和未来儿子的关系」，变量都不一样。</li><li><strong>叙事自我</strong>（Narrative Self）：心理学家 Dan McAdams 提出，人通过讲故事来构建自我认同。当被问「家庭排第几」时，人们不是在报告真实偏好，而是在表演理想化的自我形象：说「家庭第一」是扮演传统&#x2F;负责的人设，说「事业第一」是扮演独立&#x2F;上进的人设，说「伴侣第一」是扮演浪漫&#x2F;现代的人设。这些答案与社会期望高度相关，与真实行为几乎无关。</li><li><strong>范畴错误</strong>（Category Mistake）：哲学家 Gilbert Ryle 的概念，把不同范畴的事物放在同一个比较维度上。「伴侣、朋友、事业、家庭」——在恋爱前，伴侣和家庭是空集，朋友是具体的人，事业是具体的活动。把空集和实集一起排序，就像问「独角兽和马云你更喜欢谁」：语法上成立，语义上荒谬。</li></ol></blockquote><p>虽然大家都知道对方想要「标准答案」，但答出「标准答案」的人明显更危险：缺爱心理创伤会更容易导致极端情况。或者干脆就是投其所好的欺骗</p><h3 id="灵魂共鸣"><a href="#灵魂共鸣" class="headerlink" title="灵魂共鸣"></a>灵魂共鸣</h3><blockquote><p>在哈萨克文化里，人与人之间，产生友情或者爱情，是由于被看见。所以在哈萨克语中，「我清楚地看见你」意思是「我喜欢你」。</p><p>——《我的阿勒泰》</p></blockquote><p>每个人都渴望被理解，被需要，被尊重。我喜欢你，根本不是说出来，而是看见对方，用心去感受。我清楚地看见你</p><p>但「被看见」不等于「被复制」。如果两个人完全一样，那不过是照镜子；如果完全无法沟通，那不过是两个孤岛。</p><p>灵魂共鸣的本质，是两个独立的个体在各自的孤独中，触碰到了一种「普遍性」——你们发现，原来人类深处那些无法言说的颤抖，是相通的。它无法被追求，只能被遇见；无法被制造，只能被辨认。</p><p>这些东西都太抽象了，换成非常通俗和科学的来解释：「共情准确性」</p><p>心理学家区分了两种现象：</p><ul><li>实际相似性（Actual similarity）：你们客观上有多像——对关系满意度预测力很弱。</li><li>共情准确性（Empathic accuracy）：你能多准确地感知伴侣当下的情绪——与满意度有小但稳定的正相关</li></ul><p>「实际相似性」并不等于「共情准确性」，两者是不同的构念。但「实际相似性」高的两个人可以通过「假设对方和我想的一样」（投射）来间接实现「共情准确性」</p><blockquote><p>关于「伴侣到底多像才幸福」，学术界有一些反直觉的发现：</p><ol><li><p>「三观」确实相似：2023 年 Nature Human Behaviour 的一项元分析（Horwitz et al.；其中 UK Biobank 验证样本涵盖近 8 万对伴侣）发现，伴侣在政治价值观、宗教态度、教育程度上的相关性中等（r ≈ 0.5），但这与「灵魂共鸣」无关——它测量的是社会背景的同质性。</p></li><li><p>「性格」并不相似：同一项研究发现，人格特质（如外向性、神经质）的伴侣相关性极低（r ≈ 0.08–0.21）。另有对 Kelly 纵向研究（Kelly Longitudinal Study，1930 年代订婚夫妇样本）20 年追踪数据的分析发现，人格相似性不会随时间增加（Caspi et al., 1992）</p></li><li><p>相似性不一定带来幸福：2008 年一项元分析（Montoya et al.）发现，在已有关系中，实际人格相似性对吸引力的影响不显著。2010 年一项涉及英、德、澳三国的大型研究（Dyrenforth et al.）也发现，人格相似性与婚姻满意度无关。</p></li><li><p>「理解」比「相似」更重要：有研究区分了「实际相似性」和「共情准确性」（Kouros et al., 2019）；元分析发现，能准确感知伴侣情绪的能力与关系质量的相关性虽然小但稳定（r ≈ .13，Sened et al., 2017）——而「假设对方和我想的一样」（投射）则是另一回事。</p></li></ol></blockquote><p>这个描述过于抽象，那么如何实现灵魂共鸣，我觉得应该就是：</p><p>用「心」去感受对方，然后让对方感受你的「心」</p><p>原来一个人的一生很短，如果在人生中的某些时刻和其他人有灵魂共鸣这是件非常幸福的事情</p><blockquote><p>实质性的东西，用眼睛是看不见的。</p><p>——《小王子》Chapter 21</p></blockquote><h2 id="相处模式"><a href="#相处模式" class="headerlink" title="相处模式"></a>相处模式</h2><blockquote><p>一般来说会把关系分成几个阶段<br>阶段1：浪漫期 &#x2F; 蜜月期（融合）——特征、心理状态、行为。<br>阶段2：权力争夺期 &#x2F; 幻灭期（差异化）——冲突、真相浮现、常见的陷阱。<br>阶段3：稳定期 &#x2F; 接纳期（整合）——建立边界、深度接纳、解决问题。<br>阶段4：承诺期 &#x2F; 主动的爱（承诺）——有意识的选择、合作、安全。<br>阶段5：共同创造期 &#x2F; 协同（丰盛）——共同目标、滋养他人、成长。</p><p>一个重要补充是，这五个阶段并非严格的线性过程。<br>伴侣可能在某些阶段反复循环，或在阶段间来回摆动。<br>度过「共同创造期」后，也可能开启新的「浪漫期」，开始下一轮循环</p><p>—— Susan M. Campbell《The Couple’s Journey》（1980）提出的五阶段模型</p></blockquote><p>几乎所有的书或理论都有这几个时期的对应词，有些也许会分成四个时期或六个时期，本质上都是在说同一件事。随着时间的增长，维持关系的需要的各种因素权重会发生变化</p><p>不过要注意：缺乏大规模、严格的量化研究来精确验证这五个阶段</p><p>虽然主流观点是这样但这并不代表是对的。我并不是非常认同这种阶段划分。各阶段本质对应三要素随着时间的推移的权重占比。阶段化会导致问题浮于表面（给了一种问题即解法的包装），从而忽略内在的关联性</p><blockquote><p>罗兰·米勒（Rowland Miller）的经典著作《亲密关系》（Intimate Relationships）详细阐述了斯滕伯格（Sternberg, 1986）提出的「爱情三因理论」<br><strong>亲密（Intimacy）、激情（Passion）和承诺（Commitment）</strong></p></blockquote><p>在一段长期的亲密关系中，随着时间的推移 亲密、激情和承诺占的权重不同。有一张经典的图</p><p><img src="/assets/img/2026/intimacy-passion-commitment.jpeg" alt="intimacy-passion-commitment.jpeg"></p><p>亲密关系的核心逻辑是：因为「激情」相识而约会，在不断交流中增加「亲密」（约会的核心目标是增加「亲密值」）。因为「激情」会逐渐降低，要在「激情」降低前把「亲密值」拉起来。当「亲密性」达到非常最高点时作出承诺。关系会逐渐转化为「承诺」驱动</p><p>如果激情发生，直接做出承诺，然后再增加亲密是否可行？事实上没有亲密的承诺毫无意义。正如同没有筹码的合同一样是一张废纸</p><p>我现在已经不能理解为什么日本动漫经常一上来就表白，没有「亲密」和「激情」就开始「承诺」。。。我想不清楚这件事的底层逻辑在哪，甚至质疑合理性或是完全属于艺术的加工行为（也有可能是日本独特的文化内核）</p><p>不过要注意：恋爱中的「时间」确实不能等同于现实世界的物理时间（钟表时间）。可以区分「日历时间」（Chronological Time）和「关系时间」（Relational Time &#x2F; 互动密度）：「每天在一起」、「每天一小时」和「每周一小时」，会导致斯滕伯格爱情三角的曲线发生「拉伸」或「压缩」</p><p>谈恋爱是可以有固定模板和套路的，但也可以完全不按照套路来。</p><p>你们的相处模式完全可以由自己进行定义</p><h3 id="冲突提前和贝叶斯定理"><a href="#冲突提前和贝叶斯定理" class="headerlink" title="冲突提前和贝叶斯定理"></a>冲突提前和贝叶斯定理</h3><blockquote><p>两个「主体性」之间的冲突是客观存在的</p></blockquote><p>权力争夺期本质是：激情消失，激素回归正常水平，冲突被暴露了出来。激情会把暂时把冲突屏蔽掉</p><p>那么我们能不能把「权力争夺期」的判定放到「浪漫期 &#x2F; 蜜月期」前面。这样也可以未卜先知地避免未来关系消亡带来的悲伤</p><p>直观的感觉，这不会有任何问题，毕竟不会影响最终结果。我们可以用贝叶斯定理来模拟人判断关系是否可以走下去的过程</p><p>我们可以做一个思维实验：使用贝叶斯定理来论证在亲密关系中，把「权力争夺期」放到「浪漫期 &#x2F; 蜜月期」前面是非常糟糕的做法</p><blockquote><p>我们不做数值模拟，只做定性推演：如果先经历权力争夺、后经历蜜月，会发生什么？</p><ol><li><strong>先验被污染，甜蜜被重新解释</strong>。先经历冲突，会让判断者带着极低的信任进入蜜月期。此时同样的甜蜜行为，证据含义已经改变：在高信任状态下，甜蜜被读作「爱的证据」；在低信任状态下，它更容易被读作「对方在补偿、在表演」——也就是假性亲密。证据的解读方式取决于你已有的信任，所以顺序不是中性的：先坏后好，「好」的那部分会被「坏」的先验打折，甚至反转成减分项。</li><li><strong>逆火效应</strong>。在低信任语境里突然出现的甜蜜，会被识别为「操纵」（love bombing）的信号而非修复的诚意——它不但不加分，反而可能加速解体。</li></ol><p>印象形成中的首因效应（先接收的信息主导最终判断，Asch, 1946）和负面偏差（坏比好更强，Baumeister et al., 2001）。</p><p>把权力争夺期前置，不是「提前排雷」，而是主动污染数据源——它让判断系统在证据不足、解读扭曲的状态下，过早收敛到「分手」。</p></blockquote><p>还有两个问题：</p><ol><li>「冲突」不等同于「关系的置信度下降」，冲突是普遍的、必然的。我们只能在冲突发生时协商解决办法</li><li>「姻缘型灵魂伴侣」认为冲突<strong>确实</strong>等同于置信度下降。因为「天造地设」隐含了「天然契合」、「顺遂无碍」的先验信念。一旦出现剧烈冲突，就相当于观测到了极强的反面证据，后验概率必然断崖式下跌。</li></ol><p><strong>这个视角本身，是一个完美的「贝叶斯陷阱」</strong>。它逻辑正确，但会导致你在关系博弈中必输无疑。</p><p>这种思路和做法就是把解决冲突问题换成开始前的「排雷」。但它计算出来的结果却是所有人都是「雷」</p><p><strong>如果强行把「权力争夺」前置，计算出的「人人皆雷」不仅不是模型的失败，反而是对「纯理性择偶」的终极讽刺——它用模型推演证明了一个人文真理：</strong></p><blockquote><p><strong>亲密关系不是「零和的安全排查」，而是一场「非理性的先信后爱」。</strong></p></blockquote><h3 id="知识的诅咒"><a href="#知识的诅咒" class="headerlink" title="知识的诅咒"></a>知识的诅咒</h3><blockquote><p>一旦你知道了某件事，就再也无法想象「不知道这件事」是什么感觉，从而在沟通中下意识地高估对方的理解能力</p><p>——「知识的诅咒」（Curse of Knowledge），Camerer, Loewenstein &amp; Weber (1989) 提出，平克在《风格感觉》一书中有详细讨论</p></blockquote><p>你在学英语的时候，会区分 <strong>人人都会的日常词</strong> 和 <strong>只有特定圈子才懂的行话&#x2F;黑话</strong>。</p><p>中文里也一样，有 <strong>公共常识词</strong> 和 <strong>圈层黑话</strong> 之分。</p><p>对于和异性交流经验少的直男，最大的错觉就是：<strong>误把自己每天都使用的「圈层黑话」，当成了人人都懂的「日常词」。</strong> 除非对方和他同属一个圈子，否则对方其实根本看不懂。</p><p>比如你说「我今天又编译了一遍 Linux 内核，把 <code>dmesg</code> 报错调好了。所以今天非常开心，要去吃顿好的」</p><p>而对方听到的是「#￥%……&amp;*，今天非常开心，要去吃顿好的」。想象一下有人突然给你发了这个，会是什么感觉。内心 OS：「这人有毛病吧」？</p><p>特别提醒：维持亲密性是分享经历，情绪，看法。不是去传授专业知识。<br>你要把「调好内核报错」翻译成「搞了个超难的技术问题终于搞定了」（分享成就感），而不是直接把那个名词甩给对方</p><h2 id="关于她"><a href="#关于她" class="headerlink" title="关于她"></a>关于她</h2><p>她非常优秀，这点必须承认。虽然我之前从来都没有夸过她</p><h3 id="喜欢哪里"><a href="#喜欢哪里" class="headerlink" title="喜欢哪里"></a>喜欢哪里</h3><p>这个问题有很多所谓的「情感博主」给出的回答方法，虽然很多对自己更有利的回答方法。这些都非常套路并有失坦诚</p><p>到底喜欢她哪里，这个问题我想了很久。我觉得我可能是智性恋，喜欢聪明的，但我又觉得这个和有人说喜欢胸大的一样的低俗。<br>然后就是那种为自己想要的东西奋斗的样子，感觉好美（感觉我好变态啊）。或者当你开始在意在她心目中的形象的时候，也许就很难坦诚回答这个问题了吧</p><p>还有就是相似性，后来才发现我俩「相似度」原来这么高。换做是我在她那个情况也会做相同选择的，我也会拒绝，我也会拉黑，觉得她和我是同一类人，我如果没有出现那些契机去创业的话，是不是和她现在一样</p><p>她喜欢我什么？「经历」。但和我类似的创业者经历不都是这样吗？或许只是因为她生活圈子里面没有这类人罢了</p><p>如果她问我，应该回答「唯一性」的特质吧。可是任何一种特质都有一群人拥有。哪里有人什么「唯一性」的特质啊</p><p>对于「唯一性」的这个问题我思考了好久，直到看到《小王子》才明白</p><blockquote><p>对我来说，你无非是个孩子，和其他成千上万个孩子没有什么区别。<br>我不需要你。你也不需要我。<br>对你来说，我无非是只狐狸，和其他成千上万只狐狸没有什么不同。<br>但如果你驯化了我，那我们就会彼此需要。<br>你对我来说是独一无二的，我对你来说也是独一无二的……</p><p>我的生活很单调。<br>我猎杀鸡，人猎杀我。<br>所有的鸡都是相同的，所有的人也是相同的。我已经有点厌倦。<br>但如果你驯化我，我的生活将会充满阳光。<br>我将能够够辨别一种与众不同的脚步声。<br>别人的脚步声会让给我躲到地下。<br>而你的脚步声就像音乐般美好，会让我走出洞穴。<br>还有，你看。<br>你看到那片麦田吗？<br>我不吃面包。小麦对我来说没有用。<br>麦田不会让我想起什么，这是很悲哀的！<br>但你的头发是金色的，所以你来驯化我是很美好的事情！<br>小麦也是金色的，到时他将会让我想起你。<br>我喜欢风吹过麦穗的声音。……</p><p>——《小王子》Chapter 21</p></blockquote><h3 id="和前同事的复盘"><a href="#和前同事的复盘" class="headerlink" title="和前同事的复盘"></a>和前同事的复盘</h3><blockquote><p>「人家可是女生唉，人家都这么主动了，你在干什么，写代码？整天就知道写代码，现在好了，人家不理你了」</p><p>「你喜欢她吧，那为什么不去追」</p></blockquote><p>前同事建议我「买了草莓熊玩偶准备送给她」然后就和好了（她喜欢草莓熊）<br>按照我的理解，这样一定会被拉黑（逻辑前提推论方案完全不成立，难道无脑舔就可以解决全部问题？这不合逻辑）<br>但已婚人士的建议应该更靠谱吧，我忽略了内心的疑问，丢掉了脑子。买了两只草莓熊玩偶准备送给她。然后就被全平台拉黑了</p><p>女生和女生并不能一概而论，成功的经验也并不能随意模仿<br>就连感受被爱都有五种类型，这个也应该是分成不同类型的</p><blockquote><p>注：「爱的五种语言」（Five Love Languages）是美国婚姻辅导专家 Gary Chapman 于 1992 年提出的通俗心理学概念，基于其咨询实务观察，尚未获得关系科学领域的充分实证支持；2024 年的综述（Impett, Park &amp; Muise, 2024）也未发现支持其核心假设（存在固定的「主要爱语」、语言匹配改善关系）的证据。</p></blockquote><p>不过为什么喜欢就要表白呢，喜欢并不意味着要在一起，也没有人有义务回应你的期待</p><p>我们会成为好朋友，彼此互相欣赏，但不能在一起。当存在结构性问题的时候，试图继续发展只会变得更糟糕</p><p>对于这件事情，沟通才是第一重要的，事实上当你失去了沟通渠道，也就宣告着彻底的终结</p><h3 id="「军师」和「AI-作为外置大脑」"><a href="#「军师」和「AI-作为外置大脑」" class="headerlink" title="「军师」和「AI 作为外置大脑」"></a>「军师」和「AI 作为外置大脑」</h3><p>受限于语言带宽限制，你没有办法把信息都同步给「军师」。这样一种特性导致「军师」没有办法做出正确判断。<br>AI 或许可以成为更强的军师，但事实上没有把全部上下文都给 AI，你给的上下文是你认为的权重高的地方。这样的一种信息传递有着极强的偏见性<br>所以「军师」只能帮助提供具体事项的参考。比如处理男女偏好这类问题。还有就是更大层面的方法论。恋爱的充分必要性的论证，双优困境的解法或者提前排雷的导致泛雷化论证</p><p>「军师」也好「AI 作为外置大脑」也好，建议仅供参考</p><h3 id="可能的误会"><a href="#可能的误会" class="headerlink" title="可能的误会"></a>可能的误会</h3><p>那天我第一次知道「亲密关系」这个词。我觉得和「处对象」，「确认恋爱关系」是一个意思。直到某天我发现「亲密关系」可以特指「性关系」。<br>我整个人都不好了。我原本不是这个意思。。。当时应该没有误会吧，下次不懂的词要提前确认</p><h2 id="关于我"><a href="#关于我" class="headerlink" title="关于我"></a>关于我</h2><h3 id="为什么表白"><a href="#为什么表白" class="headerlink" title="为什么表白"></a>为什么表白</h3><p>我觉得这是我的问题其实有几个原因混合在一起：</p><ul><li>接下来的日子会非常忙，要快速解决这个问题（什么鬼？对这件事情完全没有正确的认知，很忙就不应该开始，亲密性需要花时间来维持）</li><li>我怕她知道最真实的我之后再也不理我（内心的恐惧）</li><li>我妈看到她一定会非常开心了（她简直就是我妈希望我变成的样子）</li><li>发个推炫耀一下（非常的虚荣。「秀恩爱，死得快」）</li><li>受到日本动漫的影响，应该存在这样的仪式（也许这个对樱花妹用）</li><li>强迫自己在重要事情上冷静后再决定（我应该在第二次见面的时候确认这件事，但我强制自己冷静后拖到下一次见面，但现在回想其实她已经在第二次刚见面尝试隐式确认）</li></ul><p>表白也好，确认恋爱关系也好，就是被日本动漫带偏的错误认知</p><p>《不能承受的生命之轻》中有个词叫「媚俗」非常符合我的这种行为</p><h3 id="母亲的话"><a href="#母亲的话" class="headerlink" title="母亲的话"></a>母亲的话</h3><p>我妈说你得赶快结婚，让你老婆管你，这样我就不管你了</p><p>当我听到「我需要改造你，但这样我会非常累，所以我们不合适」。这太可怕了，有人竟然试图改造我</p><p>我好不容易脱离了我妈的掌控，结果又来了一个和我妈一样的。我到底图啥啊。。。</p><blockquote><p>如果打算爱一个人，你要想清楚，是否愿意为了他，放弃如上帝般自由的心灵，从此心甘情愿有了羁绊。</p><p>——《了不起的盖茨比》第六章（网络上流传的改写版本）</p></blockquote><h3 id="自我检讨"><a href="#自我检讨" class="headerlink" title="自我检讨"></a>自我检讨</h3><p>该数数我到底犯了多少错误了</p><ol><li>最重要的错误其实就是无知（对于恋爱，亲密关系完全处于无知状态）</li><li>不坦诚（因为无知，所以并不坦诚）</li><li>虚荣（我觉得我已经完全消灭了虚荣，一不小心又出来了）</li><li>「恃宠而骄」（这个已经刻在我的心里了）</li><li>迷信了权威（已婚人士的建议）</li><li>我的情绪还是不够稳定</li><li>我没接住她的情绪（事后复盘才发现的，不过这个很难在极短时间内学会吧）</li></ol><h2 id="那天后"><a href="#那天后" class="headerlink" title="那天后"></a>那天后</h2><p>提出交往被拒的那天，我似乎并没有很伤心，我甚至怀疑我喜欢的不是她，而是那个一起去旅行的幻影</p><p>被拒前几个小时，我突然在脑海里想到一个被卡了好久的项目上的思路。</p><p>被拒绝的第二天，我实现了这个，效果非常棒，特别想找人分享我的喜悦，然后突然想到了她说的</p><blockquote><p>「当你成功时，你难道不想和家人分享喜悦吗？」</p></blockquote><p>我突然感到迷茫，在没有遇到她之前是怎么做的来着？</p><p>随后的几天一直在忙工作。工作忙完后突然感觉极度的空虚，总感觉少了点东西，很奇怪。我之前不都是自己一个人吗？</p><p><strong>我只是回归了之前的生活，我分明什么都没有失去，但却感觉失去了很重要的东西</strong></p><blockquote><p>自我扩展理论：分手后自我概念清晰度下降（Slotter et al. 2010, “Who Am I Without You?”）</p></blockquote><p>骑上自行车出去散散心，却发现这辆自行车是和她一起去买的。感觉好难受。决定出门走走，然后去了菲律宾。临出门那天，我甚至都不想出门了，票都买好了，还是去吧</p><p>听说那里治安不太好，我每天都跑到马尼拉的贫民窟里闲逛，非常希望能碰到「恶人」，这样我就可以和他打架。他最好带着枪，要么他把我打死，要么我把他打死。很遗憾，马尼拉生活非常平静，这个世界上没有那么多所谓的「恶人」</p><p>在菲律宾的最后一天，公园的长椅上，遇到了一个美国人，她的妻子是菲律宾人，还有快乐的小孩。这个美国人充满阳光和自信。我感受到了他的开心，他的幸福</p><p>回国后，我试图约她出来解释，后来在我前同事的建议下买了两只草莓熊准备送过去，然后就被全平台拉黑了</p><p><img src="/assets/img/2026/img_9598.jpeg" alt="img_9598.jpeg"></p><h2 id="事件复盘"><a href="#事件复盘" class="headerlink" title="事件复盘"></a>事件复盘</h2><p>复盘这种事情简直就是在虐待自己，每次回忆思考都会感觉心里难受</p><p>我大概花了近一个月的时间去学习和思考这些问题，我很感激目前的状态可以让我能拿出这么多时间来思考。我觉得搞清楚这些问题远比赚了多少钱，开发多少新功能更有意义。希望我的思考能对别人有所帮助吧</p><p>人生是旅途。在这段关系中可能唯一后悔的事情就是没有好好享受当下吧。每天只想着项目进度，事实上快点慢点影响不了任何事情</p><h3 id="女人是不可理解的"><a href="#女人是不可理解的" class="headerlink" title="女人是不可理解的"></a>女人是不可理解的</h3><p>相信你一定听说过这句话：「女人是不可理解的」</p><p>其实是可以被理解，如果我被拉黑后想不清楚原因。最好也只能感慨一句「女人是不可理解的」</p><p>我在不断的反思中，逐渐发现原来是可以理解的。或是说获得了「看见别人」的能力</p><h3 id="男女差异"><a href="#男女差异" class="headerlink" title="男女差异"></a>男女差异</h3><p>罗兰·米勒《亲密关系》中认为两性差异被大众文化严重夸大，实际差异的效应量普遍很小，同一性别内部的差异远大于两性之间的平均差异（性别相似性假说，Hyde, 2005）。<br>《第二性》的作者波伏娃认为：「女人不是天生的，而是后天形成的」（郑克鲁译本）。男女并没有生物决定论差距，主要为社会化差距</p><p>但大众心里读物，比如《男人来自火星，女人来自金星》认为差距非常大。但没有任何有效证明可以佐证观点。罗兰·米勒《亲密关系》中批判过这种夸大差距的观点</p><p>男性的情感连接和感知应该是和女性相同的，男性对于「情绪感知」被社会化「关掉了」。而女性被更早的被社会化引导启用这种「情绪感知」。我觉得男性是可以理解女性的，女性是可以理解男性的。社会化让男性默认关闭这些功能，可通过激励和训练让男性和女性持平（但需要考虑个体差异）</p><h3 id="依恋类型的恶性循环"><a href="#依恋类型的恶性循环" class="headerlink" title="依恋类型的恶性循环"></a>依恋类型的恶性循环</h3><p>我试图约她出来解释，然后发现每次发消息都压力巨大，就算是她回了消息我也压力巨大，原来压力巨大的是只要看到这个头像就已经压力巨大了。我不知道这个压力是怎么回事，原来给人发条消息也会变得非常煎熬<br>用依恋理论来解读：看到头像就压力巨大（焦虑激活）、对方全平台拉黑（回避策略）、「我怕她知道真实的我后不理我」（被拒绝恐惧）</p><p>焦虑和回避配对最大的风险「在关系确立之前，循环会把关系直接掐死」<br>我可能是「焦虑型」，她可能是「回避型」<br>这个因为目前交互数据太少，而且本身这个配对模型可信度也不高，作为一种可能性参考</p><p>依恋理论里「焦虑型」和「回避型」是两套方向相反的「自我保护程序」（Hazan &amp; Shaver, 1987；Mikulincer &amp; Shaver, 2007）：</p><ul><li>焦虑方用「过度激活」策略：对距离信号过敏，追逐、求证、反复确认（多发消息、试探、送东西求和好）</li><li>回避方用「去激活」策略：对亲密需求感到被吞没，后撤、降温、切断联系</li></ul><p>循环是这样的：焦虑方追 → 回避方退 → 撤退恰好证实焦虑方最深的恐惧（我果然会被抛弃）→ 于是更激烈地追 → 这又恰好证实回避方最深的信念（亲密关系果然是索取和窒息）→ 于是更彻底地退<br>最致命的地方在于：双方都没有恶意，都在自我保护，但各自的保护方式恰好是对方的触发器</p><p>只要知道这个模式原理就可以对自我行为进行约束，然后逆着这个模式来走，这样可以避免恶性循环的产生</p><h3 id="一切取决于感受"><a href="#一切取决于感受" class="headerlink" title="一切取决于感受"></a>一切取决于感受</h3><p>从事实来看，实际上就犯了两次错误，一次是「表白」，一次是「要送礼物」<br>仔细想想都是鸡毛蒜皮的小事，就算被全平台拉黑其实也是小事。但却感觉非常伤心<br>什么都没有发生，但好像又发生了很多事情</p><p>当我被全平台拉黑时，我觉得只要我想给她发信息，我一定有办法让她看到<br>我觉得这个世界上没有什么是不可能的，肯定有办法可以继续发展，但这样似乎并不好，然后就「继续前进」和「放手」的想法之间来回摇摆<br>最后还是选择了「放手」，因为一切都过去了。我能做的只有记下这些错误，然后下次注意</p><p>原来一切都取决于感受</p><h3 id="结构性失恋"><a href="#结构性失恋" class="headerlink" title="结构性失恋"></a>结构性失恋</h3><blockquote><p>如果想和别人建立关系，那就要冒着流泪的风险</p><p>——《小王子》</p></blockquote><p>她是一个「姻缘型灵魂伴侣」主义者。我们有很高的相似度（可能是滤镜的误判）。三观是不完全匹配，但可以兼容。而且不匹配的地方就是对于家庭的地方，而且没有办法去判断一个目前不存在的事情，用这个来判断关系能否走下去，是完全不靠谱的。真正有用的结构性矛盾判断，是「一定要和父母住」还是「一定不要和父母住」这种断言（这是经历了什么才能做出这种断言）</p><p>需要注意：关于「她」全部推断是基于事实和理论框架里的「她」，并不能代表「她」就是这么想的。也可能实际的想法是：「未来一年内非常忙，现在不适合发展，从「不匹配库」里面随便捞几条信息作为不匹配的证据」</p><p>「相似性」和「共同记忆」都是非常容易提高好感的。还有就是我们能「聊得来」。但能「聊得来」的人实在太少了，失去一个朋友会令人感觉非常的伤心</p><p>不过我们之间存在一个「结构性上无法调和的矛盾」：她试图改变我，因为她喜欢的其实是她心目中的理想伴侣，而我恰好有些特质符合这个理想伴侣。如果继续发展就要把我改造成她的「理想伴侣」。但我不会同意这种事情，因为我就是「我」，我要做我自己，而不是去做别人心目中的人</p><p>即使零操作失误，即使事先知道家庭观权重判定不合理，但也仍然没法改变结局</p><ul><li>表白（操作失误，本可修复）</li><li>送礼（操作失误，本可修复）</li><li>家庭观权重（无法预先判断，属存疑，但可兼容，不存在结构性矛盾）</li><li>改造 vs 自我（结构性矛盾，不可调和）</li></ul><p>我们因为碰巧的相遇，又由于碰巧有时间互相了解，但又由于碰巧分离。<br>我时常思考我们为什么要相遇，或许「相遇的意义」那便是：</p><blockquote><p><strong>被你改变的那部分我，代替了你永远地与我站立在一起</strong></p></blockquote><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p><strong>写于 2026.09.06（正文的写完的一周后）</strong></p><p>后来，我唯一想做的事情就道歉，对轻信前同事建议的事道歉。但这个消息已经无法发送过去了</p><p>事实上，写正文的时候我已经计划好了如何恢复普通朋友关系了<br>已经设计好了剧本，我们预计可以在明年上半年恢复普通朋友身份，消除掉前同事的错误建议带来的负面影响<br>然后普通朋友相处，其他的事之后再说</p><p>然而需要沟通渠道才能完成<br>她甚至把那个帮我转发消息的阿姨也屏蔽了，她就这么不想再看见我吗<br>我真的有这么差吗，我是不是非常的糟糕，然后又莫名其妙的自信<br>事情为什么会发展成这样，拼命想抓住的东西却越来越远</p><p>我可以做到任何事，白的不行还有灰的，实在不行还有黑的<br>只要我想给她发消息，她一定能收到。我总会有办法找到她的<br>我很强大，那次我在非洲悟到：当你愿意放弃一切去做某件事的时候，就会发现所有的一切困难都不是困难，仿佛一切都在为你开路<br>我突然意识到：也许我悟到的这个不是这样随便用的，应该是要去守护别人的</p><p>如果去挖出新的联系方式，必然又会打扰到她<br>那天讨论的类似这个问题，她说：「相信你不会做」。我竟自己把自己锁死了<br>已经是彻底不想再见我了，如果继续，已经是对她的不尊重<br>或许，如果一切都按照我的想法，那对方其实已经丧失了主体性</p><p>够了，到此为止，再往前一步就越界了</p><p>即使我有通天的本领，也没有办法在尊重对方主体性的情况下完成这些<br>这件事情已经不可解了</p><p>或者，对我来说「停下」比「继续」更困难<br>我已经分不清我现在是对我错误的不甘还是对她的不舍了</p><p>我知道，我当然知道，她想要什么，从推荐《给阿么的情书》观后感的时候就已经开始暗示我了。我只是一直假装没看懂，然后已读乱回</p><p>也许未来再也不会见面了吧，如果靠纯概率，基本上不太可能了</p><p>伪概率相遇？算了，不要再向前一步了</p><p>写下这段文字的时候，意味着我彻底放弃了一切挣扎，这件事已经变得完全不可解了<br>我没有办法在同时满足这些条件的情况下找出可以恢复普通朋友的方案</p><p>原来一个错误就可以让你失去一切</p><p>我最近只感觉自己轻飘飘，似乎缺少实感<br>突然意识到《不可承受的生命之轻》中的「轻与重」的描述</p><blockquote><p>负担越重，我们的生命越贴近大地，它就越真切实在。</p><p>相反，当负担完全缺失，人就会变得比空气还轻，就会飘起来，远离大地和地上的生命，人就只是一个半真的存在，其运动也会变得自由而没有意义。</p></blockquote><p><strong>没有重负的生命不是解脱，而是失重后的虚无</strong></p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><h3 id="研究文献"><a href="#研究文献" class="headerlink" title="研究文献"></a>研究文献</h3><ul><li>Baumeister, R. F., &amp; Leary, M. R. (1995). <a href="https://pubmed.ncbi.nlm.nih.gov/7777651/">The need to belong: Desire for interpersonal attachments as a fundamental human motivation</a>. <em>Psychological Bulletin</em>, 117(3), 497–529.</li><li>Crocker, J., &amp; Wolfe, C. T. (2001). Contingencies of self-worth. <em>Psychological Review</em>, 108(3), 593–623.</li><li>Crocker, J., &amp; Park, L. E. (2004). <a href="https://pubmed.ncbi.nlm.nih.gov/15122925/">The costly pursuit of self-esteem</a>. <em>Psychological Bulletin</em>, 130(3), 392–414.</li><li>Stoeber, J., &amp; Otto, K. (2006). <a href="https://psycnet.apa.org/record/2006-10811-002">Positive conceptions of perfectionism: Approaches, evidence, challenges</a>. <em>Personality and Social Psychology Review</em>, 10(4), 295–319.</li><li>Stoeber, J. (2012). <a href="https://doi.org/10.1016/j.paid.2012.04.002">Dyadic perfectionism in romantic relationships: Predicting relationship satisfaction and longterm commitment</a>. <em>Personality and Individual Differences</em>, 53(3), 300–305.</li><li>Bakker, A. B., Demerouti, E., &amp; Burke, R. (2009). <a href="https://doi.org/10.1037/a0013290">Workaholism and relationship quality: A spillover-crossover perspective</a>. <em>Journal of Occupational Health Psychology</em>, 14(1), 23–33.</li><li>Martin, S. P. (2006). <a href="https://www.demographic-research.org/articles/volume/15/20">Trends in marital dissolution by women’s education in the United States</a>. <em>Demographic Research</em>, 15, 537–560.</li><li>Schwartz, C. R., &amp; Han, H. (2014). <a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC4212646/">The reversal of the gender gap in education and trends in marital dissolution</a>. <em>American Sociological Review</em>, 79(4), 605–629.</li><li>Knee, C. R. (1998). Implicit theories of relationships: Assessment and prediction of romantic relationship initiation, coping, and longevity. <em>Journal of Personality and Social Psychology</em>, 74(2), 360–370.</li><li>Franiuk, R., Cohen, D., &amp; Pomerantz, E. M. (2002). Implicit theories of relationships: Implications for relationship satisfaction and longevity. <em>Personal Relationships</em>, 9(4), 345–367.</li><li>Schwartz, S. H., et al. (2012). Refining the theory of basic individual values. <em>Journal of Personality and Social Psychology</em>, 103(4), 663–688.</li><li>Clifton, J. D. W., et al. (2019). Primal world beliefs. <em>Psychological Assessment</em>, 31(1), 82–99.</li><li>Carrère, S., &amp; Gottman, J. M. (1999). <a href="https://pubmed.ncbi.nlm.nih.gov/10526767/">Predicting divorce among newlyweds from the first three minutes of a marital conflict discussion</a>. <em>Family Process</em>, 38(3), 293–301.</li><li>Eagly, A. H. (1987). <em>Sex differences in social behavior: A social-role interpretation</em>. Lawrence Erlbaum.</li><li>Eagly, A. H., &amp; Wood, W. (1999). The origins of sex differences in human behavior: Evolved dispositions versus social roles. <em>American Psychologist</em>, 54(6), 408–423.</li><li>Duflo, E. (2012). <a href="https://www.aeaweb.org/articles?id=10.1257/jel.50.4.1051">Women empowerment and economic development</a>. <em>Journal of Economic Literature</em>, 50(4), 1051–1079.</li><li>Lytton, H., &amp; Romney, D. M. (1991). Parents’ differential socialization of boys and girls: A meta-analysis. <em>Psychological Bulletin</em>, 109(2), 267–296.</li><li>Endendijk, J. J., Groeneveld, M. G., Bakermans-Kranenburg, M. J., &amp; Mesman, J. (2016). <a href="https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0159193">Gender-differentiated parenting revisited: Meta-analysis reveals very few differences in parental control of boys and girls</a>. <em>PLoS ONE</em>, 11(4), e0159193.</li><li>García-Mendoza, M. C., Parra, Á., Sánchez-Queija, I., Oliveira, P., &amp; Coimbra, S. (2022). <a href="https://link.springer.com/article/10.1007/s10826-021-02122-y">Gender differences in perceived family involvement and perceived family control during emerging adulthood: A cross-country comparison in Southern Europe</a>. <em>Journal of Child and Family Studies</em>, 31(4), 1081–1096.</li><li>Kerr, M., &amp; Stattin, H. (2000). What parents know, how they know it, and several forms of adolescent adjustment: Further support for a reinterpretation of monitoring. <em>Developmental Psychology</em>, 36(3), 366–380.</li><li>Rosenthal, C. J. (1985). <a href="https://eric.ed.gov/?id=EJ329274">Kinkeeping in the familial division of labor</a>. <em>Journal of Marriage and Family</em>, 47(4), 965–974.</li><li>Fingerman, K. L., Huo, M., &amp; Birditt, K. S. (2020). <a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC10795962/">Mothers, fathers, daughters, and sons: Gender differences in adults’ intergenerational ties</a>. <em>Journal of Family Issues</em>, 41(9), 1597–1625.</li><li>Umberson, D., Chen, M. D., House, J. S., Hopkins, K., &amp; Slaten, E. (1996). The effect of social relationships on psychological well-being: Are men and women really so different? <em>American Sociological Review</em>, 61(5), 837–857.</li><li>Conger, R. D., Cui, M., Bryant, C. M., &amp; Elder, G. H. (2000). <a href="https://pubmed.ncbi.nlm.nih.gov/10948976/">Competence in early adult romantic relationships: A developmental perspective on family influences</a>. <em>Journal of Personality and Social Psychology</em>, 79(2), 224–237.</li><li>Rosenthal, R., &amp; Rubin, D. B. (1982). A simple, general purpose display of magnitude of experimental effect. <em>Journal of Educational Psychology</em>, 74(2), 166–169.</li><li>Horwitz, T. B., Balbona, J. V., Paulich, K. N., &amp; Keller, M. C. (2023). <a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC10967253/">Evidence of correlations between human partners based on systematic reviews and meta-analyses of 22 traits and UK Biobank analysis of 133 traits</a>. <em>Nature Human Behaviour</em>, 7(9), 1568–1583.</li><li>Caspi, A., Herbener, E. S., &amp; Ozer, D. J. (1992). <a href="https://pubmed.ncbi.nlm.nih.gov/1556660/">Shared experiences and the similarity of personalities: A longitudinal study of married couples</a>. <em>Journal of Personality and Social Psychology</em>, 62(2), 281–291.（基于 Kelly 纵向研究 20 年追踪数据）</li><li>Montoya, R. M., Horton, R. S., &amp; Kirchner, J. (2008). <a href="https://doi.org/10.1177/0265407508096700">Is actual similarity necessary for attraction? A meta-analysis of actual and perceived similarity</a>. <em>Journal of Social and Personal Relationships</em>, 25(6), 889–922.</li><li>Dyrenforth, P. S., Kashy, D. A., Donnellan, M. B., &amp; Lucas, R. E. (2010). <a href="https://pubmed.ncbi.nlm.nih.gov/20718544/">Predicting relationship and life satisfaction from personality in nationally representative samples from three countries: The relative importance of actor, partner, and similarity effects</a>. <em>Journal of Personality and Social Psychology</em>, 99(4), 690–702.</li><li>Kouros, C. D., &amp; Papp, L. M. (2019). <a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC6512343/">Couples’ perceptions of each other’s daily affect: Empathic accuracy, assumed similarity, and indirect accuracy</a>. <em>Family Process</em>, 58(1), 179–196.</li><li>Sened, H., Lavidor, M., Lazarus, G., Bar-Kalifa, E., Rafaeli, E., &amp; Ickes, W. (2017). <a href="https://pubmed.ncbi.nlm.nih.gov/28394141/">Empathic accuracy and relationship satisfaction: A meta-analytic review</a>. <em>Journal of Family Psychology</em>, 31(6), 742–752.</li><li>Sternberg, R. J. (1986). A triangular theory of love. <em>Psychological Review</em>, 93(2), 119–135.</li><li>Asch, S. E. (1946). Forming impressions of personality. <em>Journal of Abnormal and Social Psychology</em>, 41(3), 258–290.</li><li>Baumeister, R. F., Bratslavsky, E., Finkenauer, C., &amp; Vohs, K. D. (2001). Bad is stronger than good. <em>Review of General Psychology</em>, 5(4), 323–370.</li><li>Camerer, C., Loewenstein, G., &amp; Weber, M. (1989). The curse of knowledge in economic settings: An experimental analysis. <em>Journal of Political Economy</em>, 97(5), 1232–1254.</li><li>Slotter, E. B., Gardner, W. L., &amp; Finkel, E. J. (2010). <a href="https://doi.org/10.1177/0146167209352250">Who am I without you? The influence of romantic breakup on the self-concept</a>. <em>Personality and Social Psychology Bulletin</em>, 36(2), 147–160.</li><li>Hyde, J. S. (2005). The gender similarities hypothesis. <em>American Psychologist</em>, 60(6), 581–592.</li><li>Impett, E. A., Park, H. G., &amp; Muise, A. (2024). <a href="https://www.psychologicalscience.org/journals/current-directions/09637214231217663/">Popular psychology through a scientific lens: Evaluating love languages from a relationship science perspective</a>. <em>Current Directions in Psychological Science</em>, 33(2), 87–92.</li><li>Hazan, C., &amp; Shaver, P. (1987). <a href="https://doi.org/10.1037/0022-3514.52.3.511">Romantic love conceptualized as an attachment process</a>. <em>Journal of Personality and Social Psychology</em>, 52(3), 511–524.</li><li>Mikulincer, M., &amp; Shaver, P. R. (2007). <em>Attachment in adulthood: Structure, dynamics, and change</em>. Guilford Press.</li></ul><h3 id="书籍与影视"><a href="#书籍与影视" class="headerlink" title="书籍与影视"></a>书籍与影视</h3><ul><li>罗兰·米勒（Rowland S. Miller）：《亲密关系》（Intimate Relationships），王伟平译，人民邮电出版社</li><li>Alain de Botton：《爱情笔记》（Essays in Love, 1993），上海译文出版社</li><li>Jennifer Petriglieri：<em>Couples That Work: How Dual-Career Couples Can Thrive in Love and Work</em>, Harvard Business Review Press, 2019</li><li>Susan M. Campbell：<em>The Couple’s Journey: Intimacy as a Path to Wholeness</em>, Impact Publishers, 1980</li><li>Willard Waller：<em>The Family: A Dynamic Interpretation</em>, 1938（1951 年由 Reuben Hill 修订再版）</li><li>Carol S. Dweck：<em>Mindset: The New Psychology of Success</em>, 2006（中译本《终身成长》）</li><li>Steven Pinker：<em>The Sense of Style</em>, 2014（中译本《风格感觉》）</li><li>Gary Chapman：<em>The Five Love Languages</em>, Northfield Publishing, 1992</li><li>John Gray：<em>Men Are from Mars, Women Are from Venus</em>, 1992（中译本《男人来自火星，女人来自金星》）</li><li>西蒙娜·德·波伏娃：《第二性》，郑克鲁译，上海译文出版社</li><li>米兰·昆德拉：《不能承受的生命之轻》</li><li>圣埃克苏佩里：《小王子》</li><li>滕丛丛（导演）：剧集《我的阿勒泰》，2024</li></ul><h3 id="网络资源"><a href="#网络资源" class="headerlink" title="网络资源"></a>网络资源</h3><ul><li><a href="https://what-if.xkcd.com/9/">Soul Mates | https://what-if.xkcd.com/9/</a></li><li><a href="https://www.simplypsychology.com/articles/relationship-red-flags-guide">Relationship Red Flags: Psychological Warning Signs to Know</a></li><li><a href="https://www.gottman.com/blog/the-research-predicting-divorce-among-newlyweds-from-the-first-three-minutes-of-a-marital-conflict-discussion/">Predicting Divorce From The First 3 Minutes of Conflict Discussion</a></li><li><a href="https://zh.wikipedia.org/zh-cn/%E7%88%B1%E6%83%85%E4%B8%89%E5%9B%A0%E8%AE%BA">爱情三因论 - 维基百科，自由的百科全书</a></li><li><a href="https://zh.wikipedia.org/zh-cn/%E4%BA%B2%E5%AF%86%E5%85%B3%E7%B3%BB">亲密关系 - 维基百科，自由的百科全书</a></li></ul>]]></content>
    
    
    <summary type="html">最近失恋了，也没什么可以隐瞒的，想复盘一下，但却发现只要一回想就会悲伤得不能自己</summary>
    
    
    
    <category term="self" scheme="https://a-wing.top/categories/self/"/>
    
    
    <category term="self" scheme="https://a-wing.top/tags/self/"/>
    
    <category term="love" scheme="https://a-wing.top/tags/love/"/>
    
  </entry>
  
  <entry>
    <title>修复汽车地毯泡水和顶棚脱落</title>
    <link href="https://a-wing.top/diy/2025/11/02/car-repair"/>
    <id>https://a-wing.top/diy/2025/11/02/car-repair</id>
    <published>2025-11-02T04:00:00.000Z</published>
    <updated>2025-11-02T04:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>很长时间以来，我的车只要停几天就会开始发霉，一开始我觉得是因为广东的天气太湿了</p><p>后来车顶棚脱落也脱落了</p><p>再后来，我去修复一个转弯时空调异响的奇怪问题时，偶然发现是由于空调排水孔堵了。导致风扇打到水的响声。当然，疏通空调排水孔是非常容易的，但是我的地毯全被水泡了，当然车顶棚脱落大概也有很大关系，毕竟长时间在高湿度环境里</p><p>原本计划去修车店，但是顶棚报价 700+，地毯报价 1000+。我看了看，这玩意似乎很简单，我决定自己动手丰衣足食</p><h2 id="拆除"><a href="#拆除" class="headerlink" title="拆除"></a>拆除</h2><p>拆顶棚顺序是：顶棚拉手，前阅读灯，后阅读灯，遮阳板，然后 A，B，C 柱上护板，车门框密封条</p><p>拆地毯顺序是：四个座位，门槛，扶手箱，全部的后备箱内饰，地毯，地毯下的海绵</p><h3 id="ABC-柱"><a href="#ABC-柱" class="headerlink" title="ABC 柱"></a>ABC 柱</h3><p>几乎全是卡扣，需要拆卡扣的工具，很容易拆坏，拆坏也没有关系，卡扣和内饰版是分开的，也很容易换</p><p>拆卡扣需要一点技巧，熟练后非常容易拆（技巧有点难以描述，把工具塞进去一翘就开了）</p><p>不知道卡扣位置可以去找网上找同型号的配件，看着配件找卡扣的位置</p><div class="gallery-container" data-type="data" data-button="" data-limit="10" data-first="10">    <div class="gallery-items">[{"url":"/assets/img/2025/car-repair/img_4904.jpeg","alt":"img_4904.jpeg","title":""},{"url":"/assets/img/2025/car-repair/img_4907.jpeg","alt":"img_4907.jpeg","title":""}]</div>  </div><p>都拆空了</p><p>地毯的卡扣和内饰不同，是钉在地毯上的，坏了很难更换。需要用最细的螺丝刀伸到下面翘侧面</p><p>后排座椅有个螺丝生锈严重，甚至买了个超长的加长扳手才拆下来。。。</p><h2 id="除锈"><a href="#除锈" class="headerlink" title="除锈"></a>除锈</h2><h3 id="扶手箱"><a href="#扶手箱" class="headerlink" title="扶手箱"></a>扶手箱</h3><p>扶手箱下面固定手刹的零件锈蚀非常严重</p><div class="gallery-container" data-type="data" data-button="" data-limit="10" data-first="10">    <div class="gallery-items">[{"url":"/assets/img/2025/car-repair/img_4909.jpeg","alt":"img_4909.jpeg","title":""},{"url":"/assets/img/2025/car-repair/img_4910.jpeg","alt":"img_4910.jpeg","title":""}]</div>  </div><p>拆下来，打磨重新喷漆，这张图是喷完环氧底漆的。</p><div class="gallery-container" data-type="data" data-button="" data-limit="10" data-first="10">    <div class="gallery-items">[{"url":"/assets/img/2025/car-repair/img_4914.jpeg","alt":"img_4914.jpeg","title":""},{"url":"/assets/img/2025/car-repair/img_4925.jpeg","alt":"img_4925.jpeg","title":""}]</div>  </div><p>实际工序是：用角磨机打磨除锈 → 铁锈转换剂 → 环氧底漆 → 底盘装甲</p><p>？底盘装甲？你问为什么要喷底盘装甲？</p><p>这个零件在最里面，完全看不到，所以没必要喷色漆和清漆，又不需要美观。喷到一半底盘装甲刚好用完了。</p><h3 id="底盘除锈"><a href="#底盘除锈" class="headerlink" title="底盘除锈"></a>底盘除锈</h3><p>后备箱里面有很多暗锈（指在锈在漆面下面）</p><p>按照相同的工序处理，只是这部分在车上，要用纸胶带遮蔽其他地方（很遗憾，这里忘记拍照了）</p><h2 id="修复顶棚"><a href="#修复顶棚" class="headerlink" title="修复顶棚"></a>修复顶棚</h2><p>顶棚核心是比较硬的「纸」做的，整体是一个海绵布用胶粘上的</p><p>篷布已经自己完全脱落了。海绵已经老化了，需要打磨除掉之前的胶和老化海绵。不过要注意，这个硬「纸」很薄，打磨过渡会磨穿</p><div class="gallery-container" data-type="data" data-button="" data-limit="10" data-first="10">    <div class="gallery-items">[{"url":"/assets/img/2025/car-repair/img_4916.jpeg","alt":"img_4916.jpeg","title":""},{"url":"/assets/img/2025/car-repair/img_4917.jpeg","alt":"img_4917.jpeg","title":""}]</div>  </div><p>胶粘也要注意，我买了两瓶胶，实际用下来不够，又补了一瓶胶，总共用了三瓶，有些地方喷过量了，已经成块了</p><p>另外，胶粘的时候不要用夹子固定（我专门 3D 打印了一堆夹子），夹子夹的坑无法复原（（（</p><p>我已经把周围的空间都占了</p><p><img src="/assets/img/2025/car-repair/img_4931.jpeg" alt="img_4931.jpeg"></p><h2 id="晒干"><a href="#晒干" class="headerlink" title="晒干"></a>晒干</h2><p>这个才是耗时间最长的，我先在屋里晾了一周，发现没干。又拿到外面晒了一周</p><h3 id="加装止震板和吸音棉"><a href="#加装止震板和吸音棉" class="headerlink" title="加装止震板和吸音棉"></a>加装止震板和吸音棉</h3><p>原车有止震板，我又加装了一些，止震板太沉了，要少装点，我买了两包，实际只装了一包</p><div class="gallery-container" data-type="data" data-button="" data-limit="10" data-first="10">    <div class="gallery-items">[{"url":"/assets/img/2025/car-repair/img_4932.jpeg","alt":"img_4932.jpeg","title":""},{"url":"/assets/img/2025/car-repair/img_5017.jpeg","alt":"img_5017.jpeg","title":""}]</div>  </div><p>吸音棉很轻，可以多装一点，底盘都铺满了，一共用了三包吸音棉</p><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p>装回去还是很容易的，就是装完发现多了点零件）））</p><p>我研究了好久这些多出来的零件是装在哪的，最后只有一个猜想，这些应该是之前修车的时候掉在缝隙中的，因为拆了地毯所以拿出来了</p><p>比如多了四个特殊卡扣，这个型号只有 C 柱下护板上用，左右一边两个，正好四个）））</p><p>还有一些通用的卡扣）））</p><div class="gallery-container" data-type="data" data-button="" data-limit="10" data-first="10">    <div class="gallery-items">[{"url":"/assets/img/2025/car-repair/img_4918.jpeg","alt":"img_4918.jpeg","title":""},{"url":"/assets/img/2025/car-repair/img_4920.jpeg","alt":"img_4920.jpeg","title":""}]</div>  </div><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>WD40 会导致环氧底漆附着力下降，所以需要重新喷漆的零件不要喷 WD40</p><p>万一喷了 WD40 可以等挥发（不过时间比较长）也可以用 IPA（isopropanol Alcohol&#x2F;IPA）清洗剂（异丙醇）</p><p>断断续续搞了快一个月，实际上每天晚上就搞了一两个小时。拆车一周，晾晒除锈两周，安装一周</p><p>以后我失业找不到工作说不定可以转行去修车（（（</p>]]></content>
    
    
    <summary type="html">原本计划去修车店，但是顶棚报价 700+，地毯报价 1000+。我看了看，这玩意似乎很简单，我决定自己动手丰衣足食</summary>
    
    
    
    <category term="diy" scheme="https://a-wing.top/categories/diy/"/>
    
    
    <category term="diy" scheme="https://a-wing.top/tags/diy/"/>
    
    <category term="car" scheme="https://a-wing.top/tags/car/"/>
    
  </entry>
  
  <entry>
    <title>Rust 的静态编译</title>
    <link href="https://a-wing.top/linux/2025/04/17/static-build"/>
    <id>https://a-wing.top/linux/2025/04/17/static-build</id>
    <published>2025-04-17T04:00:00.000Z</published>
    <updated>2025-04-17T04:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近在尝试把 Rust 写的应用交叉编译到 RISC-V 的机器上。</p><p>目标机器是 RISC-V 架构，系统使用 <code>buildroot</code>，<code>libc</code> 为 musl。</p><p>看起来我们可以直接编译一个 musl 版本的程序，就能解决，而且 rust 支持 musl 的 target</p><h2 id="标准的-C-库实现"><a href="#标准的-C-库实现" class="headerlink" title="标准的 C 库实现"></a>标准的 C 库实现</h2><blockquote><p>1973 年汤普逊和里奇用 C 语言重写了 Unix</p></blockquote><p>由于整个 Unix&#x2F;Linux 都是建立在 C 之上，几乎所有的程序都依赖了 <code>libc</code></p><p>在 Linux 下 <code>libc</code> 有四个实现：</p><p><code>glibc</code>, <code>musl libc</code>, <code>uClibc</code>&#x2F;<code>uClibc-ng</code>, <code>Bionic C</code></p><ul><li><code>glibc</code> 基本上就是事实的标准</li><li><code>musl libc</code> 容器或者嵌入式设备，静态编译用的比较多</li><li><code>uClibc</code>&#x2F;<code>uClibc-ng</code> （<code>uClibc</code>已经停止维护了， <code>uClibc-ng</code>是新的版本）主要用于无 <a href="https://en.wikipedia.org/wiki/Memory_management_unit">MMU (Memory Management Unit)</a> 的设备，也有非常少量了 Linux 用了 <code>uClibc</code>（比如旧版本的 OpenWRT 和一些特殊的硬件，新版本用了 <code>musl libc</code>）</li><li><code>Bionic C</code> 主要用于 Android，一般不把它算在 Linux 的生态里</li></ul><h2 id="如何判断是否为静态编译"><a href="#如何判断是否为静态编译" class="headerlink" title="如何判断是否为静态编译"></a>如何判断是否为静态编译</h2><h3 id="file"><a href="#file" class="headerlink" title="file"></a><code>file</code></h3><p>这是一个比较高级的命令，通过很多因素判断，还是非常准确的</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">% file $(<span class="built_in">which</span> gcc)</span><br><span class="line">/usr/bin/gcc: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=d68af36149454ddf51e22ad93db29f583c51f70b, <span class="keyword">for</span> GNU/Linux 4.4.0, stripped</span><br></pre></td></tr></table></figure><p>在 Linux 下的可执行文件时 ELF (Executable and Linkable Format)  格式，ELF 是一种用于可执行文件、目标代码、共享库和核心转储（core dump）的标准文件格式</p><p>我们先来写一个最简单的程序验证一下</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> &gt; hello.c &lt;&lt; <span class="string">EOF</span></span><br><span class="line"><span class="string">#include &lt;stdio.h&gt;</span></span><br><span class="line"><span class="string">int main() &#123;</span></span><br><span class="line"><span class="string">   printf(&quot;Hello, World!\r\n&quot;);</span></span><br><span class="line"><span class="string">   return 0;</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">EOF</span></span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">% gcc hello.c -o c-hello-gcc-gnu</span><br><span class="line">% ./c-hello-gcc-gnu</span><br><span class="line">Hello, World!</span><br><span class="line">% file -b c-hello-gcc-gnu</span><br><span class="line">ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=6169fdfc99a343c23b9d349da437b313e09254e3, <span class="keyword">for</span> GNU/Linux 4.4.0, not stripped</span><br></pre></td></tr></table></figure><p>通过 <code>file</code> 命令，我们可以看到 <code>dynamically linked</code> 标识它是动态链接，<code>interpreter /lib64/ld-linux-x86-64.so.2</code> 是动态链接的加载器</p><p>我们需要先了解一下，动态链接的工作流程，执行会去通过 <code>interpreter</code> 字段记录的位置找 <code>ld</code> （动态链接装载器），然后由 <code>ld</code> 加载 Dynamic section 里面记录动态链接</p><h3 id="ldd-命令"><a href="#ldd-命令" class="headerlink" title="ldd 命令"></a>ldd 命令</h3><p>我们可以通过 <code>ldd</code> （List Dynamic Dependencies）来找动态链接</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">% gcc hello.c -o c-hello-gcc-gnu</span><br><span class="line">% ./c-hello-gcc-gnu</span><br><span class="line">Hello, World!</span><br><span class="line">% file -b c-hello-gcc-gnu</span><br><span class="line">ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=6169fdfc99a343c23b9d349da437b313e09254e3, <span class="keyword">for</span> GNU/Linux 4.4.0, not stripped</span><br><span class="line">% ldd c-hello-gcc-gnu</span><br><span class="line">        linux-vdso.so.1 (0x00007b07aeba6000)</span><br><span class="line">        libc.so.6 =&gt; /usr/lib/libc.so.6 (0x00007b07ae999000)</span><br><span class="line">        /lib64/ld-linux-x86-64.so.2 =&gt; /usr/lib64/ld-linux-x86-64.so.2 (0x00007b07aeba8000)</span><br></pre></td></tr></table></figure><p>加上静态编译参数，<code>ldd</code> 没有发现动态链接</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">% gcc -static hello.c -o c-hello-gcc-gnu-static</span><br><span class="line">% ./c-hello-gcc-gnu-static</span><br><span class="line">Hello, World!</span><br><span class="line">% file -b c-hello-gcc-gnu-static</span><br><span class="line">ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=532829a15c17e744e340e6ab081ace670137dfd2, <span class="keyword">for</span> GNU/Linux 4.4.0, not stripped</span><br><span class="line">% ldd c-hello-gcc-gnu-static </span><br><span class="line">        not a dynamic executable</span><br></pre></td></tr></table></figure><p>当然 <code>ldd</code> 其实非常的不靠谱，比如就没法判断这个</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">% musl-gcc hello.c -o c-hello-gcc-musl</span><br><span class="line">% file -b c-hello-gcc-musl</span><br><span class="line">ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, not stripped</span><br><span class="line">% ldd c-hello-gcc-musl</span><br><span class="line">./c-hello-gcc-musl: error <span class="keyword">while</span> loading shared libraries: /usr/lib/libc.so: invalid ELF header</span><br></pre></td></tr></table></figure><p>当然这是由于 <code>ldd</code> 的工作原理决定的，<code>ldd</code> 要去执行程序，执行到 <code>ld</code> 的位置停下，输出动态链接的部分，如果这个程序本身是完全静态，不依赖于 <code>ld</code>，<code>ldd</code> 是完全无效的，对于找不到 <code>ld</code> 和跨架构的情况也是完全不可用</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">cat</span> $(<span class="built_in">which</span> ldd) | <span class="built_in">wc</span> -l</span><br><span class="line">191</span><br></pre></td></tr></table></figure><p><code>ldd</code> 本身就是个 shell 脚本，添加了几个环境变量</p><p>直接执行 <code>LD_TRACE_LOADED_OBJECTS=1 ./c-hello-gcc-gnu</code>  效果是一样的</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">% LD_TRACE_LOADED_OBJECTS=1 ./c-hello-gcc-gnu</span><br><span class="line">        linux-vdso.so.1 (0x00007783dc6cf000)</span><br><span class="line">        libc.so.6 =&gt; /usr/lib/libc.so.6 (0x00007783dc4c7000)</span><br><span class="line">        /lib64/ld-linux-x86-64.so.2 (0x00007783dc6d1000)</span><br></pre></td></tr></table></figure><p><code>ldd</code> 只用来判断当前系统可以执行的动态程序用了那些动态依赖</p><h3 id="readelf"><a href="#readelf" class="headerlink" title="readelf"></a>readelf</h3><p><code>readelf</code> 属于 <code>binutils</code> 提供的工具，可以读 ELF 格式里面的内容</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">% readelf -h c-hello-gcc-gnu </span><br><span class="line">ELF Header:</span><br><span class="line">  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 </span><br><span class="line">  Class:                             ELF64</span><br><span class="line">  Data:                              2<span class="string">&#x27;s complement, little endian</span></span><br><span class="line"><span class="string">  Version:                           1 (current)</span></span><br><span class="line"><span class="string">  OS/ABI:                            UNIX - System V</span></span><br><span class="line"><span class="string">  ABI Version:                       0</span></span><br><span class="line"><span class="string">  Type:                              DYN (Position-Independent Executable file)</span></span><br><span class="line"><span class="string">  Machine:                           Advanced Micro Devices X86-64</span></span><br><span class="line"><span class="string">  Version:                           0x1</span></span><br><span class="line"><span class="string">  Entry point address:               0x1040</span></span><br><span class="line"><span class="string">  Start of program headers:          64 (bytes into file)</span></span><br><span class="line"><span class="string">  Start of section headers:          13496 (bytes into file)</span></span><br><span class="line"><span class="string">  Flags:                             0x0</span></span><br><span class="line"><span class="string">  Size of this header:               64 (bytes)</span></span><br><span class="line"><span class="string">  Size of program headers:           56 (bytes)</span></span><br><span class="line"><span class="string">  Number of program headers:         14</span></span><br><span class="line"><span class="string">  Size of section headers:           64 (bytes)</span></span><br><span class="line"><span class="string">  Number of section headers:         30</span></span><br><span class="line"><span class="string">  Section header string table index: 29</span></span><br></pre></td></tr></table></figure><p>我们可以通过 <code>Type: DYN (Position-Independent Executable file)</code> 来判断是动态链接，但是这种判读也不准确</p><p>如果把 PIE 去掉，类型和静态链接完全一样，倒是可以判断是否有 PIE</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">% gcc hello.c -o c-hello-gcc-gnu</span><br><span class="line">% readelf -h c-hello-gcc-gnu | grep Type </span><br><span class="line">  Type:                              DYN (Position-Independent Executable file)</span><br><span class="line"></span><br><span class="line">% gcc -no-pie hello.c -o c-hello-gcc-gnu-nopie</span><br><span class="line">% readelf -h c-hello-gcc-gnu-nopie | grep Type</span><br><span class="line">  Type:                              EXEC (Executable file)</span><br><span class="line"></span><br><span class="line">% gcc -static hello.c -o c-hello-gcc-gnu-static</span><br><span class="line">% readelf -h c-hello-gcc-gnu-static | grep Type</span><br><span class="line">  Type:                              EXEC (Executable file)</span><br><span class="line"></span><br><span class="line">% gcc -static-pie hello.c -o c-hello-gcc-gnu-static-pie</span><br><span class="line">% readelf -h c-hello-gcc-gnu-static-pie | grep Type </span><br><span class="line">  Type:                              DYN (Position-Independent Executable file)</span><br></pre></td></tr></table></figure><p>ELF 格式有两个 header，<code>readelf -h</code> 是看文件的 header，<code>readelf -l</code> 是看程序的 header</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line">% readelf -l c-hello-gcc-gnu</span><br><span class="line"></span><br><span class="line">Elf file <span class="built_in">type</span> is DYN (Position-Independent Executable file)</span><br><span class="line">Entry point 0x1040</span><br><span class="line">There are 14 program headers, starting at offset 64</span><br><span class="line"></span><br><span class="line">Program Headers:</span><br><span class="line">  Type           Offset             VirtAddr           PhysAddr</span><br><span class="line">                 FileSiz            MemSiz              Flags  Align</span><br><span class="line">  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040</span><br><span class="line">                 0x0000000000000310 0x0000000000000310  R      0x8</span><br><span class="line">  INTERP         0x00000000000003b4 0x00000000000003b4 0x00000000000003b4</span><br><span class="line">                 0x000000000000001c 0x000000000000001c  R      0x1</span><br><span class="line">      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]</span><br><span class="line">  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000</span><br><span class="line">                 0x0000000000000640 0x0000000000000640  R      0x1000</span><br><span class="line">  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000</span><br><span class="line">                 0x0000000000000161 0x0000000000000161  R E    0x1000</span><br><span class="line">  LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000</span><br><span class="line">                 0x00000000000000d4 0x00000000000000d4  R      0x1000</span><br><span class="line">  LOAD           0x0000000000002dd0 0x0000000000003dd0 0x0000000000003dd0</span><br><span class="line">                 0x0000000000000248 0x0000000000000250  RW     0x1000</span><br><span class="line">  DYNAMIC        0x0000000000002de0 0x0000000000003de0 0x0000000000003de0</span><br><span class="line">                 0x00000000000001e0 0x00000000000001e0  RW     0x8</span><br><span class="line">  NOTE           0x0000000000000350 0x0000000000000350 0x0000000000000350</span><br><span class="line">                 0x0000000000000040 0x0000000000000040  R      0x8</span><br><span class="line">  NOTE           0x0000000000000390 0x0000000000000390 0x0000000000000390</span><br><span class="line">                 0x0000000000000024 0x0000000000000024  R      0x4</span><br><span class="line">  NOTE           0x00000000000020b4 0x00000000000020b4 0x00000000000020b4</span><br><span class="line">                 0x0000000000000020 0x0000000000000020  R      0x4</span><br><span class="line">  GNU_PROPERTY   0x0000000000000350 0x0000000000000350 0x0000000000000350</span><br><span class="line">                 0x0000000000000040 0x0000000000000040  R      0x8</span><br><span class="line">  GNU_EH_FRAME   0x0000000000002014 0x0000000000002014 0x0000000000002014</span><br><span class="line">                 0x0000000000000024 0x0000000000000024  R      0x4</span><br><span class="line">  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000</span><br><span class="line">                 0x0000000000000000 0x0000000000000000  RW     0x10</span><br><span class="line">  GNU_RELRO      0x0000000000002dd0 0x0000000000003dd0 0x0000000000003dd0</span><br><span class="line">                 0x0000000000000230 0x0000000000000230  R      0x1</span><br><span class="line"></span><br><span class="line"> Section to Segment mapping:</span><br><span class="line">  Segment Sections...</span><br><span class="line">   00     </span><br><span class="line">   01     .interp </span><br><span class="line">   02     .note.gnu.property .note.gnu.build-id .interp .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt </span><br><span class="line">   03     .init .plt .text .fini </span><br><span class="line">   04     .rodata .eh_frame_hdr .eh_frame .note.ABI-tag </span><br><span class="line">   05     .init_array .fini_array .dynamic .got .got.plt .data .bss </span><br><span class="line">   06     .dynamic </span><br><span class="line">   07     .note.gnu.property </span><br><span class="line">   08     .note.gnu.build-id </span><br><span class="line">   09     .note.ABI-tag </span><br><span class="line">   10     .note.gnu.property </span><br><span class="line">   11     .eh_frame_hdr </span><br><span class="line">   12     </span><br><span class="line">   13     .init_array .fini_array .dynamic .got </span><br></pre></td></tr></table></figure><p>我们可以通过 <code>INTERP</code> 的 <code>/lib64/ld-linux-x86-64.so.2</code> 来判断它使用了动态链接，具体那些做了动态链接，我们可以使用 <code>readelf -d</code> 来看 Dynamic section 里面的内容</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">% readelf -d c-hello-gcc-gnu </span><br><span class="line"></span><br><span class="line">Dynamic section at offset 0x2de0 contains 26 entries:</span><br><span class="line">  Tag        Type                         Name/Value</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]</span><br><span class="line"> 0x000000000000000c (INIT)               0x1000</span><br><span class="line"> 0x000000000000000d (FINI)               0x1154</span><br><span class="line"> 0x0000000000000019 (INIT_ARRAY)         0x3dd0</span><br><span class="line"> 0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)</span><br><span class="line"> 0x000000000000001a (FINI_ARRAY)         0x3dd8</span><br><span class="line"> 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)</span><br><span class="line"> 0x000000006ffffef5 (GNU_HASH)           0x3d0</span><br><span class="line"> 0x0000000000000005 (STRTAB)             0x498</span><br><span class="line"> 0x0000000000000006 (SYMTAB)             0x3f0</span><br><span class="line"> 0x000000000000000a (STRSZ)              141 (bytes)</span><br><span class="line"> 0x000000000000000b (SYMENT)             24 (bytes)</span><br><span class="line"> 0x0000000000000015 (DEBUG)              0x0</span><br><span class="line"> 0x0000000000000003 (PLTGOT)             0x3fe8</span><br><span class="line"> 0x0000000000000002 (PLTRELSZ)           24 (bytes)</span><br><span class="line"> 0x0000000000000014 (PLTREL)             RELA</span><br><span class="line"> 0x0000000000000017 (JMPREL)             0x628</span><br><span class="line"> 0x0000000000000007 (RELA)               0x568</span><br><span class="line"> 0x0000000000000008 (RELASZ)             192 (bytes)</span><br><span class="line"> 0x0000000000000009 (RELAENT)            24 (bytes)</span><br><span class="line"> 0x000000006ffffffb (FLAGS_1)            Flags: PIE</span><br><span class="line"> 0x000000006ffffffe (VERNEED)            0x538</span><br><span class="line"> 0x000000006fffffff (VERNEEDNUM)         1</span><br><span class="line"> 0x000000006ffffff0 (VERSYM)             0x526</span><br><span class="line"> 0x000000006ffffff9 (RELACOUNT)          3</span><br><span class="line"> 0x0000000000000000 (NULL)               0x0</span><br></pre></td></tr></table></figure><p>静态链接，没有 Dynamic section，所以里面没有内容，所以也可以通过这个来判断是否是动态链接？当然，这个不行，还有一种特殊情况</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">% readelf -d c-hello-gcc-gnu-static </span><br><span class="line"></span><br><span class="line">There is no dynamic section <span class="keyword">in</span> this file.</span><br></pre></td></tr></table></figure><h3 id="PIE"><a href="#PIE" class="headerlink" title="PIE"></a>PIE</h3><p>PIE（Position-Independent Executable，位置无关可执行文件）是一种安全技术，它使可执行程序在内存中的加载地址随机化，从而增强系统的安全性。运行时由动态链接器配合操作系统随机化加载地址，目前很多编译器默认会开 PIE。</p><p>PIE 本质上是利用 <code>ld</code> 完成的随机加载，静态编译没有 <code>ld</code> ，需要把 <code>ld</code> 的 PIE 相关的功能也打包进去。</p><p>另外 PIE 的部分符号信息是写在 Dynamic section 里面的，所以，静态链接 PIE 会有 Dynamic section 里会有一些符号信息</p><p><code>-static</code> 和 <code>-pie</code> 在一起时，有一些天然的互斥，不过目前的编译器都可以做到</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">% gcc -static-pie hello.c -o c-hello-gcc-gnu-static-pie</span><br><span class="line">% file -b c-hello-gcc-gnu-static-pie                      </span><br><span class="line">ELF 64-bit LSB pie executable, x86-64, version 1 (GNU/Linux), static-pie linked, BuildID[sha1]=2a89b311b755433561c90ed642fdb44756e5063b, <span class="keyword">for</span> GNU/Linux 4.4.0, not stripped</span><br><span class="line">% readelf -d c-hello-gcc-gnu-static-pie</span><br><span class="line"></span><br><span class="line">Dynamic section at offset 0xafd68 contains 22 entries:</span><br><span class="line">  Tag        Type                         Name/Value</span><br><span class="line"> 0x000000000000000c (INIT)               0x7000</span><br><span class="line"> 0x000000000000000d (FINI)               0x82790</span><br><span class="line"> 0x0000000000000019 (INIT_ARRAY)         0xabc60</span><br><span class="line"> 0x000000000000001b (INIT_ARRAYSZ)       16 (bytes)</span><br><span class="line"> 0x000000000000001a (FINI_ARRAY)         0xabc70</span><br><span class="line"> 0x000000000000001c (FINI_ARRAYSZ)       16 (bytes)</span><br><span class="line"> 0x000000006ffffef5 (GNU_HASH)           0x380</span><br><span class="line"> 0x0000000000000005 (STRTAB)             0x3b8</span><br><span class="line"> 0x0000000000000006 (SYMTAB)             0x3a0</span><br><span class="line"> 0x000000000000000a (STRSZ)              1 (bytes)</span><br><span class="line"> 0x000000000000000b (SYMENT)             24 (bytes)</span><br><span class="line"> 0x0000000000000015 (DEBUG)              0x0</span><br><span class="line"> 0x0000000000000003 (PLTGOT)             0xaffe8</span><br><span class="line"> 0x0000000000000002 (PLTRELSZ)           432 (bytes)</span><br><span class="line"> 0x0000000000000014 (PLTREL)             RELA</span><br><span class="line"> 0x0000000000000017 (JMPREL)             0x6ae0</span><br><span class="line"> 0x0000000000000007 (RELA)               0x3c0</span><br><span class="line"> 0x0000000000000008 (RELASZ)             26400 (bytes)</span><br><span class="line"> 0x0000000000000009 (RELAENT)            24 (bytes)</span><br><span class="line"> 0x000000006ffffffb (FLAGS_1)            Flags: PIE</span><br><span class="line"> 0x000000006ffffff9 (RELACOUNT)          1096</span><br><span class="line"> 0x0000000000000000 (NULL)               0x0</span><br></pre></td></tr></table></figure><h3 id="patchelf"><a href="#patchelf" class="headerlink" title="patchelf"></a><code>patchelf</code></h3><p>移花接木，使用 <code>patchelf</code> 工具去改变动态链接</p><p>你有没有想过，既然差距就在 <code>glibc</code> 和 <code>musl</code> 的 <code>ld</code> 和 <code>libc</code> 位置不同，那我能不能直接修改 ELF 的位置，来把原本是动态链接 <code>glibc</code> 的程序改成动态链接 <code>musl</code></p><p>但 <code>glibc</code> 和 <code>musl</code> 只有核心部分兼容，对于只用标准 c 和 posix c 的程序是可以兼容的，但 <code>musl</code>力求精简和标准 <code>glibc</code> 兼容更好也更为庞大，把动态链接 <code>musl</code> 改成动态链接 <code>glibc</code> 的成功率更高一些，不过这种改法没什么实际的应用价值</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">% gcc hello.c -o c-hello-gcc-gnu-elf</span><br><span class="line">% readelf -l c-hello-gcc-gnu-elf | grep interpreter</span><br><span class="line">      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]</span><br><span class="line">% readelf -d c-hello-gcc-gnu-elf | grep NEEDED</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]</span><br><span class="line"></span><br><span class="line"><span class="comment"># Patch LD 的路径和 libc 的路径</span></span><br><span class="line">% patchelf --set-interpreter /lib/ld-musl-x86_64.so.1 c-hello-gcc-gnu-elf</span><br><span class="line">% patchelf --replace-needed libc.so.6 libc.so c-hello-gcc-gnu-elf</span><br><span class="line"></span><br><span class="line">% ./c-hello-gcc-gnu-elf</span><br><span class="line">Hello, World!</span><br><span class="line">% readelf -l c-hello-gcc-gnu-elf | grep interpreter</span><br><span class="line">      [Requesting program interpreter: /lib/ld-musl-x86_64.so.1]</span><br><span class="line">% readelf -d c-hello-gcc-gnu-elf | grep NEEDED                   </span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libc.so]</span><br></pre></td></tr></table></figure><p>不过还是能看到好多有问题的地方的，但程序能跑（</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">% hexdump -C c-hello-gcc-gnu-elf</span><br><span class="line">*</span><br><span class="line">00005000  00 70 75 74 73 00 5f 5f  6c 69 62 63 5f 73 74 61  |.puts.__libc_sta|</span><br><span class="line">00005010  72 74 5f 6d 61 69 6e 00  5f 5f 63 78 61 5f 66 69  |rt_main.__cxa_fi|</span><br><span class="line">00005020  6e 61 6c 69 7a 65 00 6c  69 62 63 2e 73 6f 2e 36  |nalize.libc.so.6|</span><br><span class="line">00005030  00 47 4c 49 42 43 5f 32  2e 32 2e 35 00 47 4c 49  |.GLIBC_2.2.5.GLI|</span><br><span class="line">00005040  42 43 5f 32 2e 33 34 00  5f 49 54 4d 5f 64 65 72  |BC_2.34._ITM_der|</span><br><span class="line">00005050  65 67 69 73 74 65 72 54  4d 43 6c 6f 6e 65 54 61  |egisterTMCloneTa|</span><br><span class="line">00005060  62 6c 65 00 5f 5f 67 6d  6f 6e 5f 73 74 61 72 74  |ble.__gmon_start|</span><br><span class="line">00005070  5f 5f 00 5f 49 54 4d 5f  72 65 67 69 73 74 65 72  |__._ITM_register|</span><br><span class="line">00005080  54 4d 43 6c 6f 6e 65 54  61 62 6c 65 00 6c 69 62  |TMCloneTable.lib|</span><br><span class="line">00005090  63 2e 73 6f 00 00 00 00  00                       |c.so.....|</span><br><span class="line">00005099</span><br></pre></td></tr></table></figure><p>不要用 <code>patchelf</code> 来移植不同的 <code>libc</code>，本身 <code>glibc</code> 和 <code>musl</code> 就差距很大，<code>glibc</code> 功能非常庞大</p><h2 id="Go-的编译"><a href="#Go-的编译" class="headerlink" title="Go 的编译"></a>Go 的编译</h2><p>Golang 不依赖系统的 <code>libc</code>，通过两个变量直接交叉编译出其他平台的程序，非常的方便</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GOOS=linux GOARCH=amd64 go build</span><br></pre></td></tr></table></figure><p>但 <code>CGO</code> 的情况还是可能依赖 <code>libc</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> &gt; hello.go &lt;&lt; <span class="string">EOF</span></span><br><span class="line"><span class="string">package main</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">import &quot;fmt&quot;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">func main() &#123;</span></span><br><span class="line"><span class="string">fmt.Println(&quot;hello world!&quot;)</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">EOF</span></span><br></pre></td></tr></table></figure><p><code>CGO_ENABLED</code> 主要是用来禁用 <code>CGO</code> 的，过于简单的完全不依赖 <code>CGO</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">% CGO_ENABLED=0 go build -o go-hello-0 ./hello.go</span><br><span class="line">% file -b go-hello-0</span><br><span class="line">ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=b5b7d5fb91fa0881908a29fb762f71d1244b808a, with debug_info, not stripped</span><br><span class="line"></span><br><span class="line">% CGO_ENABLED=1 go build -o go-hello-1 ./hello.go</span><br><span class="line">% file -b go-hello-1</span><br><span class="line">ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=6acc631714b8e9f1ece35c8a2c0cafeee876ae30, with debug_info, not stripped</span><br></pre></td></tr></table></figure><p>比如我们使用 <code>std</code> 的 <code>net</code> 包，默认会使用 <code>CGO</code>，我们禁用 <code>CGO</code> 来静态编译</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> &gt; lookup.go &lt;&lt; <span class="string">EOF</span></span><br><span class="line"><span class="string">package main</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">import (</span></span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;net&quot;</span></span><br><span class="line"><span class="string">)</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">func main() &#123;</span></span><br><span class="line"><span class="string">addrs, err := net.LookupHost(&quot;baidu.com&quot;)</span></span><br><span class="line"><span class="string">if err != nil &#123;</span></span><br><span class="line"><span class="string">panic(err)</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">fmt.Println(&quot;Baidu IPs:&quot;, addrs)</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">EOF</span></span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">% CGO_ENABLED=0 go build -o go-lookup-0 ./lookup.go</span><br><span class="line">% file -b ./go-lookup-0</span><br><span class="line">ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=0c269da87124d8d01065f131f3527b4e8a0044fe, with debug_info, not stripped</span><br><span class="line"></span><br><span class="line">% CGO_ENABLED=1 go build -o go-lookup-1 ./lookup.go</span><br><span class="line">% file -b ./go-lookup-1</span><br><span class="line">ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=48cc551954f4de00d378dfac57065055a4f4a28c, with debug_info, not stripped</span><br><span class="line">% ldd go-lookup-1</span><br><span class="line">        linux-vdso.so.1 (0x000078aa44203000)</span><br><span class="line">        libresolv.so.2 =&gt; /usr/lib/libresolv.so.2 (0x000078aa441db000)</span><br><span class="line">        libc.so.6 =&gt; /usr/lib/libc.so.6 (0x000078aa43fe9000)</span><br><span class="line">        /lib64/ld-linux-x86-64.so.2 =&gt; /usr/lib64/ld-linux-x86-64.so.2 (0x000078aa44205000)</span><br></pre></td></tr></table></figure><h2 id="Zig-的编译"><a href="#Zig-的编译" class="headerlink" title="Zig 的编译"></a>Zig 的编译</h2><p>Zig 是一门开源的、静态类型的、跨平台的系统编程语言，甚至集成了 c&#x2F;c++ 的编译器。当然我们可以把它当成 c 的编译器来用</p><p><code>zig cc</code> 会在 <code>*-linux-gnu</code> 编译成动态链接，在 <code>*-linux-musl</code> 编译成静态链接</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">% zig cc -target x86_64-linux-gnu hello.c -o c-hello-zig-gnu</span><br><span class="line">% file -b c-hello-zig-gnu </span><br><span class="line">ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, <span class="keyword">for</span> GNU/Linux 2.0.0, with debug_info, not stripped</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">% zig cc -target x86_64-linux-musl hello.c -o c-hello-zig-musl</span><br><span class="line">% file -b c-hello-zig-musl </span><br><span class="line">ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, with debug_info, not stripped</span><br></pre></td></tr></table></figure><p>我们知道如果使用 gcc &#x2F; clang 交叉编译时，每个平台都需要单独的交叉编译工具链</p><p>比如 arm64 需要 <code>aarch64-linux-gnu-gc</code> ， Risc-V 需要 <code>riscv64-linux-gnu-gcc</code></p><p><code>zig</code> 和 <code>go</code> 一样可以编译所有支持的平台，非常的方便，不过 <code>zig</code> 还有内置 C 的编译工具链，交叉编译非常的方便</p><h2 id="Rust-的编译"><a href="#Rust-的编译" class="headerlink" title="Rust 的编译"></a>Rust 的编译</h2><p>rust 自己交叉编译，但 Rust 是依赖 libc 的。rust 交叉编译仍然需要对应平台的 C 编译器</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo new rust-hello</span><br></pre></td></tr></table></figure><p>对于每个平台我们都需要单独安装对应的 C 的工具链，更确切的说是，rust 目前自己没有 <code>ld</code>。rust 自己的编译器只能编译到 <code>.o</code> 文件，需要其他的 <code>ld</code> 来生成对应平台的 ELF</p><h3 id="zig-和-rust"><a href="#zig-和-rust" class="headerlink" title="zig 和 rust"></a>zig 和 rust</h3><p>rust 没法处理 <code>ld</code> 和 c 扩展的交叉编译问题，zig 有很好交叉编译工具链，集成 C 编译器</p><p>这时候我们可以考虑使用 <code>rust</code> 和 <code>zig</code> 一起用</p><p><code>zig cc</code> 是子命令，所以我们需要实现一个简单的包装器 <code>zig.sh</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/sh</span></span><br><span class="line">zig cc -target riscv64-linux-musl <span class="variable">$@</span></span><br></pre></td></tr></table></figure><p>我们可以直接指定环境变量使用这个 Linker</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">% CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_MUSL_LINKER=<span class="string">&quot;./zig.sh&quot;</span> \</span><br><span class="line">cargo build --release --target riscv64gc-unknown-linux-musl</span><br></pre></td></tr></table></figure><p>也可以修改一下编译配置 <code>.cargo/config.toml</code></p><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[target.riscv64gc-unknown-linux-musl]</span></span><br><span class="line"><span class="attr">linker</span> = <span class="string">&quot;./zig.sh&quot;</span></span><br></pre></td></tr></table></figure><p>很棒，完全的静态链接</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">file -b rust-hello</span><br><span class="line">ELF 64-bit LSB pie executable, UCB RISC-V, RVC, double-float ABI, version 1 (SYSV), static-pie linked, stripped</span><br></pre></td></tr></table></figure><p>这只是处理了 Linker，手动太麻烦了，而且有 C 扩展还需要其他配置</p><p>这里有个 <code>cargo</code> 的插件 <a href="https://github.com/rust-cross/cargo-zigbuild">cargo-zigbuild</a>，帮你把这些都处理好了</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">% cargo zigbuild --release --target riscv64gc-unknown-linux-musl</span><br><span class="line"></span><br><span class="line">% file -b rust-hello </span><br><span class="line">ELF 64-bit LSB pie executable, UCB RISC-V, RVC, double-float ABI, version 1 (SYSV), static-pie linked, stripped</span><br></pre></td></tr></table></figure><p>zig 虽然好用，兼容性也很好，但并不一定能处理全部的情况？</p><h3 id="cross-rs"><a href="#cross-rs" class="headerlink" title="cross-rs"></a>cross-rs</h3><p>rust 交叉编译神器 <code>cross-rs</code>，使用 Docker 构建交叉编译环境，在容器里面配好了 C 的交叉编译工具链</p><p>但是 cross 目前不支持 <code>riscv64gc-unknown-linux-musl</code> 我们来加一个，这个 PR 已经合了 <a href="https://github.com/cross-rs/cross/pull/1664">cross-rs#1664</a></p><p>顺便分享一下给 <code>cross-rs</code> 加新的 target 踩的坑。<code>cross-rs</code> 其实是个非常老的项目，目前有两套环境构建方法。一个是比较常规的编译安装 <code>gcc</code>，另一个用 <code>crosstool-ng</code> 来配环境</p><p>编译 <code>musl</code> 版本也用 <code>gcc</code> 来编译，但需要配置的东西比较多，官方提供了一个叫 <code>musl-gcc</code> 的 warp。当然 <code>binutils</code> 也 warp 过，<code>musl</code> 提供一个 make 脚本帮助你来编译 <code>musl-gcc</code>。但是 <code>musl-cross-make</code> 吸收了几个关于 <code>binutils</code> 的 patch，然后有 break change。这个 patch 非常庞大，很难迁移</p><p>这就导致 <code>binutils</code> 比较旧，它的 ld 没法适配新的 RISC-V 的格式。当然自己用可以直接去升级 <code>binutils</code>。那几个 patch 没有关于 RISC-V 相关的。推动这个 <code>musl-cross-make</code> 升级也很难，除非说服维护者放弃那几个 patch</p><p>最终还是选择和 <code>crosstool-ng</code> 来安装容器环境</p><h3 id="静态编译-libc"><a href="#静态编译-libc" class="headerlink" title="静态编译 libc"></a>静态编译 libc</h3><p>Rust 的程序是依赖 <code>libc</code> 的，官方提供了 <code>gnu</code> 和 <code>musl</code> 版本的编译器。<code>glibc</code> 和 <code>musl</code> 都是两种情况默认都是动态链接。对，<code>musl</code> 默认也是动态链接</p><p>我们需要传递一些 CRT (C runtime) 参数才能静态编译 libc</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">RUSTFLAGS=<span class="string">&quot;-C target-feature=+crt-static&quot;</span> cargo build --release</span><br></pre></td></tr></table></figure><p>也可以在 <code>.cargo/config.toml</code> 里设置</p><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[target.riscv64gc-unknown-linux-gnu]</span></span><br><span class="line"><span class="attr">rustflags</span> = [<span class="string">&quot;-C&quot;</span>, <span class="string">&quot;target-feature=+crt-static&quot;</span>]</span><br></pre></td></tr></table></figure><p>我们可以编译出 <code>glibc</code> 静态编译的版本，也可以有 <code>musl</code> 静态编译的版本。实际对比体积几乎无区别。但是，可别忘了 <code>glibc</code> 是 LGPL 许可证，对于静态链接是有法律上的限制</p><h3 id="体积对比"><a href="#体积对比" class="headerlink" title="体积对比"></a>体积对比</h3><p>各工具链编译出的体积的对比参考</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"> 16K c-hello-gcc-gnu</span><br><span class="line"> 21K c-hello-gcc-gnu-elf</span><br><span class="line"> 16K c-hello-gcc-gnu-nopie</span><br><span class="line"> 16K c-hello-gcc-gnu-pie</span><br><span class="line">760K c-hello-gcc-gnu-static</span><br><span class="line">801K c-hello-gcc-gnu-static-pie</span><br><span class="line"> 15K c-hello-gcc-musl</span><br><span class="line"> 18K c-hello-gcc-musl-static</span><br><span class="line"> 15K c-hello-gcc-musl-static-pie</span><br><span class="line">7.9K c-hello-zig-gnu</span><br><span class="line">9.4K c-hello-zig-gnu-aarch64</span><br><span class="line">111K c-hello-zig-musl</span><br><span class="line">1.8M c-hello-zig-musl-riscv64</span><br><span class="line">2.2M go-hello-0</span><br><span class="line">2.2M go-hello-1</span><br><span class="line">3.0M go-lookup-0</span><br><span class="line">3.1M go-lookup-1</span><br><span class="line">423K riscv64-rust-hello-gcc-gnu</span><br><span class="line">435K riscv64-rust-hello-gcc-gnu-elf</span><br><span class="line">421K riscv64-rust-hello-gcc-musl</span><br><span class="line">432K rust-hello-gcc-gnu</span><br><span class="line">1.5M rust-hello-gcc-gnu-static</span><br><span class="line">535K rust-hello-gcc-musl</span><br><span class="line">535K rust-hello-gcc-musl-static</span><br><span class="line">384K rust-hello-zig-gnu</span><br><span class="line">393K rust-hello-zig-musl</span><br><span class="line"> 24M rust-live777-gcc-gnu</span><br><span class="line"> 25M rust-live777-gcc-gnu-elf</span><br><span class="line"> 24M rust-live777-gcc-musl</span><br><span class="line">  30 sh-hello.sh</span><br></pre></td></tr></table></figure><h3 id="patchelf-rust-应用"><a href="#patchelf-rust-应用" class="headerlink" title="patchelf rust 应用"></a>patchelf rust 应用</h3><p>测试的 RISC-V 环境</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">% <span class="built_in">cat</span> /etc/os-release</span><br><span class="line">NAME=Buildroot</span><br><span class="line">VERSION=-g1fcc2fd40</span><br><span class="line">ID=buildroot</span><br><span class="line">VERSION_ID=2023.11.2</span><br><span class="line">PRETTY_NAME=<span class="string">&quot;Buildroot 2023.11.2&quot;</span></span><br><span class="line">% <span class="built_in">uname</span> -a</span><br><span class="line">Linux licheervnano-1d6b 5.10.4-tag- <span class="comment">#15 PREEMPT Fri Dec 20 17:26:44 CST 2024 riscv64 GNU/Linux</span></span><br><span class="line">% <span class="built_in">cat</span> /etc/os-release</span><br><span class="line">NAME=Buildroot</span><br><span class="line">VERSION=-g1fcc2fd40</span><br><span class="line">ID=buildroot</span><br><span class="line">VERSION_ID=2023.11.2</span><br><span class="line">PRETTY_NAME=<span class="string">&quot;Buildroot 2023.11.2&quot;</span></span><br><span class="line">% <span class="built_in">ls</span> /lib/ld-musl-*.so.1</span><br><span class="line">/lib/ld-musl-riscv64-sf.so.1          /lib/ld-musl-riscv64v0p7_xthead.so.1  /lib/ld-musl-riscv64xthead-sf.so.1</span><br><span class="line">/lib/ld-musl-riscv64.so.1             /lib/ld-musl-riscv64v_xthead.so.1     /lib/ld-musl-riscv64xthead.so.1</span><br></pre></td></tr></table></figure><p><code>patchelf</code> rust 的 hello world，把动态链接 <code>glibc</code> 的改成动态链接 <code>musl</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">% <span class="built_in">cp</span> target/riscv64gc-unknown-linux-gnu/release/build-static rust-hello-gcc-gnu-elf</span><br><span class="line">% <span class="built_in">cp</span> target/riscv64gc-unknown-linux-gnu/release/build-static rust-hello-gcc-gnu</span><br><span class="line">% <span class="built_in">cp</span> target/riscv64gc-unknown-linux-musl/release/build-static rust-hello-gcc-musl</span><br><span class="line"></span><br><span class="line">% readelf -d rust-hello-gcc-gnu-elf | grep NEEDED</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libc.so]</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [ld-linux-riscv64-lp64d.so.1]</span><br><span class="line"> </span><br><span class="line">% patchelf --set-interpreter /lib/ld-musl-riscv64.so.1 rust-hello-gcc-gnu-elf</span><br><span class="line"></span><br><span class="line">% patchelf --replace-needed libc.so.6 libc.so rust-hello-gcc-gnu-elf</span><br><span class="line">% patchelf --replace-needed ld-linux-riscv64-lp64d.so.1 ld-musl-riscv64.so.1 rust-hello-gcc-gnu-elf</span><br><span class="line"></span><br><span class="line">% readelf -d rust-hello-gcc-gnu-elf | grep NEEDED</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libc.so]</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [ld-musl-riscv64.so.1]</span><br></pre></td></tr></table></figure><p>移花接木大法搞出来的 <code>rust-hello-gcc-gnu-elf</code> 是可以正常运行的</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">% ./rust-hello-gcc-gnu</span><br><span class="line">-sh: ./rust-hello-gcc-gnu: not found</span><br><span class="line">% ./rust-hello-gcc-gnu-elf</span><br><span class="line">Hello, world!</span><br><span class="line">% ./rust-hello-gcc-musl</span><br><span class="line">Hello, world!</span><br></pre></td></tr></table></figure><p>我们拿一个实际的项目试试，用 <code>patchelf</code> 来修改 <code>glibc</code> 版本的 live777 试试</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">% <span class="built_in">cp</span> target/riscv64gc-unknown-linux-musl/release/live777 rust-live777-gcc-musl</span><br><span class="line">% <span class="built_in">cp</span> target/riscv64gc-unknown-linux-gnu/release/live777 rust-live777-gcc-gnu</span><br><span class="line">% <span class="built_in">cp</span> target/riscv64gc-unknown-linux-gnu/release/live777 rust-live777-gcc-gnu-elf</span><br><span class="line"></span><br><span class="line">% readelf -d rust-live777-gcc-gnu-elf | grep NEEDED</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [ld-linux-riscv64-lp64d.so.1]</span><br><span class="line"></span><br><span class="line">% patchelf --set-interpreter /lib/ld-musl-riscv64.so.1 rust-live777-gcc-gnu-elf</span><br><span class="line"></span><br><span class="line">% patchelf --replace-needed libc.so.6 libc.so rust-live777-gcc-gnu-elf</span><br><span class="line">% patchelf --replace-needed libm.so.6 libc.so rust-live777-gcc-gnu-elf</span><br><span class="line">% patchelf --replace-needed ld-linux-riscv64-lp64d.so.1 ld-musl-riscv64.so.1 rust-live777-gcc-gnu-elf</span><br><span class="line"></span><br><span class="line">% readelf -d rust-live777-gcc-gnu-elf | grep NEEDED                 </span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libc.so]</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [libc.so]</span><br><span class="line"> 0x0000000000000001 (NEEDED)             Shared library: [ld-musl-riscv64.so.1]</span><br></pre></td></tr></table></figure><p>移花接木大法搞出来的 live777 不能正常运行的，看起来用了 <code>glibc</code> 专有的函数</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">% ./rust-live777-gcc-gnu</span><br><span class="line">-sh: ./rust-live777-gcc-gnu: not found</span><br><span class="line">% ./rust-live777-gcc-gnu-elf </span><br><span class="line">Error relocating ./rust-live777-gcc-gnu-elf: gnu_get_libc_version: symbol not found</span><br><span class="line">Error relocating ./rust-live777-gcc-gnu-elf: __res_init: symbol not found</span><br><span class="line">Error relocating ./rust-live777-gcc-gnu-elf: __register_atfork: symbol not found</span><br><span class="line">Error relocating ./rust-live777-gcc-gnu-elf: gnu_get_libc_version: symbol not found</span><br><span class="line">Error relocating ./rust-live777-gcc-gnu-elf: __res_init: symbol not found</span><br><span class="line">Error relocating ./rust-live777-gcc-gnu-elf: __register_atfork: symbol not found</span><br><span class="line">% ./rust-live777-gcc-musl </span><br><span class="line">2025-04-16T22:03:47.280021Z  WARN ThreadId(01) live777: src/main.rs:25: <span class="built_in">set</span> <span class="built_in">log</span> level : info</span><br><span class="line">2025-04-16T22:03:47.280936Z  INFO ThreadId(01) live777: src/main.rs:31: Server listening on 0.0.0.0:7777</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>使用 file 命令判断是否为静态链接还是比较靠谱的</li><li><code>ldd</code> 依赖 <code>/lib/ld</code> ，全静态链接不走 <code>ld</code> ，而且没法跨 <code>libc</code> 和架构</li><li>纯 rust，默认 gnu 和 musl 的 libc 都是动态链接，需要 <code>RUSTFLAGS=&quot;-C target-feature=+crt-static&quot;</code> 来编译静态链接</li><li>zig 默认参数，zig 默认 gnu 为动态链接，musl 为静态链接</li><li>zig 配合 rust 处理交叉编译非常好用</li><li>cross-rs 也很好用</li><li>不要用 <code>patchelf</code> 来移植不同的 libc</li></ul><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="http://chuquan.me/2018/05/21/elf-introduce/">计算机那些事(4)——ELF文件结构</a></p><p><a href="http://chuquan.me/2018/06/03/linking-static-linking-dynamic-linking/">计算机那些事(5)——链接、静态链接、动态链接</a></p><p><a href="http://chuquan.me/2018/06/17/executable-file-load-and-execution/">计算机那些事(6)——可执行文件的装载与运行</a></p><p><a href="https://en.wikipedia.org/wiki/Executable_and_Linkable_Format">Executable and Linkable Format - Wikipedia</a></p><p><a href="https://en.wikipedia.org/wiki/Ldd_(Unix)">ldd (Unix) - Wikipedia</a></p><p><a href="https://aimuke.github.io/linux/2019/05/01/ldd/">ldd 命令</a></p><p><a href="https://blog.xco.moe/posts/rust_build_musl/">记一次Rust静态编译</a></p><p><a href="https://stackoverflow.com/questions/31770604/how-to-generate-statically-linked-executables#answer-44387312">How to generate statically linked executables?</a></p><p><a href="https://rust-lang.github.io/rfcs/1721-crt-static.html">1721-crt-static - The Rust RFC Book</a></p>]]></content>
    
    
    <summary type="html">聊聊 Rust，Zig，Go 的静态编译和各种编译参数有什么影响</summary>
    
    
    
    <category term="linux" scheme="https://a-wing.top/categories/linux/"/>
    
    
    <category term="linux" scheme="https://a-wing.top/tags/linux/"/>
    
    <category term="rust" scheme="https://a-wing.top/tags/rust/"/>
    
    <category term="golang" scheme="https://a-wing.top/tags/golang/"/>
    
    <category term="zig" scheme="https://a-wing.top/tags/zig/"/>
    
    <category term="libc" scheme="https://a-wing.top/tags/libc/"/>
    
  </entry>
  
  <entry>
    <title>2024 总结: 时代在变</title>
    <link href="https://a-wing.top/self/2024/12/31/2024"/>
    <id>https://a-wing.top/self/2024/12/31/2024</id>
    <published>2024-12-31T00:00:00.000Z</published>
    <updated>2024-12-31T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>我的生活现在过于简朴，除了旅行和生活必须品之外几乎没有其他开销</p><p>先简单回顾一下去年定的目标：</p><ul><li><input disabled="" type="checkbox"> 马拉松全程四个小时内跑完</li><li><input disabled="" type="checkbox"> 英语达到 CEFR 的 C1 水平</li><li><input disabled="" type="checkbox"> 滑雪教练证</li><li><input checked="" disabled="" type="checkbox"> 去阿勒泰滑雪</li><li><input disabled="" type="checkbox"> 去看万里长城</li><li><input checked="" disabled="" type="checkbox"> 项目能活下去</li><li><input disabled="" type="checkbox"> 发出市场价的薪水</li></ul><h1 id="个人生活"><a href="#个人生活" class="headerlink" title="个人生活"></a>个人生活</h1><p>我的生活现在过于简朴，除了旅行和生活必须品之外几乎没有其他开销</p><h2 id="旅行"><a href="#旅行" class="headerlink" title="旅行"></a>旅行</h2><p>今年只出去玩了五趟</p><ul><li>欧洲之行（40 天）</li><li>日本樱花之旅（4 天）</li><li>219 自驾（32 天）</li><li>张家界马拉松（5 天）</li><li>阿勒泰滑雪（12 天）</li></ul><p>不过我平时周六周日是不休息的，出去玩的时间基本上和双休差不多</p><h3 id="欧洲之行"><a href="#欧洲之行" class="headerlink" title="欧洲之行"></a>欧洲之行</h3><p>去趟欧洲，感觉滤镜破碎了，欧洲没那么美好</p><div class="gallery-container" data-type="data" data-button="" data-limit="10" data-first="10">    <div class="gallery-items">[{"url":"/assets/img/2024/img_6110.jpeg","alt":"img_6110","title":""},{"url":"/assets/img/2024/img_6341.jpeg","alt":"img_6341","title":""},{"url":"/assets/img/2024/img_7177.jpeg","alt":"img_7177","title":""},{"url":"/assets/img/2024/img_8274.jpeg","alt":"img_8274","title":""}]</div>  </div><h3 id="日本樱花之旅"><a href="#日本樱花之旅" class="headerlink" title="日本樱花之旅"></a>日本樱花之旅</h3><p>主要是上次去日本没玩够，刚回来就又申请了日本的签证。眼看签证马上要到期了，就去看看樱花吧</p><div class="gallery-container" data-type="data" data-button="" data-limit="10" data-first="10">    <div class="gallery-items">[{"url":"/assets/img/2024/img_9258.jpeg","alt":"img_9258","title":""},{"url":"/assets/img/2024/img_9202.jpeg","alt":"img_9202","title":""}]</div>  </div><h3 id="219-自驾去西藏"><a href="#219-自驾去西藏" class="headerlink" title="219 自驾去西藏"></a>219 自驾去西藏</h3><p>早在几年前我就有这个想法，这回终于实现了</p><div class="gallery-container" data-type="data" data-button="" data-limit="10" data-first="10">    <div class="gallery-items">[{"url":"/assets/img/2024/img_0856.jpeg","alt":"img_0856","title":""},{"url":"/assets/img/2024/img_1517.jpeg","alt":"img_1517","title":""},{"url":"/assets/img/2024/img_2433.jpeg","alt":"img_2433","title":""}]</div>  </div><h3 id="张家界马拉松"><a href="#张家界马拉松" class="headerlink" title="张家界马拉松"></a>张家界马拉松</h3><p>今年终于恢复跑马拉松了，报名张家界马拉松，景区门票免费！</p><div class="gallery-container" data-type="data" data-button="" data-limit="10" data-first="10">    <div class="gallery-items">[{"url":"/assets/img/2024/img_2891.jpeg","alt":"img_2891","title":""},{"url":"/assets/img/2024/img_3010.jpeg","alt":"img_3010","title":""}]</div>  </div><h3 id="阿勒泰滑雪"><a href="#阿勒泰滑雪" class="headerlink" title="阿勒泰滑雪"></a>阿勒泰滑雪</h3><p>终于去新疆滑雪了</p><div class="gallery-container" data-type="data" data-button="" data-limit="10" data-first="10">    <div class="gallery-items">[{"url":"/assets/img/2024/img_3620.jpeg","alt":"img_3620","title":""},{"url":"/assets/img/2024/img_3305.jpeg","alt":"img_3305","title":""},{"url":"/assets/img/2024/img_3648.jpeg","alt":"img_3648","title":""}]</div>  </div><h2 id="买的设备"><a href="#买的设备" class="headerlink" title="买的设备"></a>买的设备</h2><p>今年其实买了非常多的东西，清单可能会太长，只放一个最推荐的吧</p><p>Apple Watch 编织单圈表带，虽然很贵，但这个特别舒服。唯一的缺点就是不防水</p><p><img src="/assets/img/2024/img_9673.jpeg" alt="img_9673"></p><h2 id="看的书"><a href="#看的书" class="headerlink" title="看的书"></a>看的书</h2><p>基本上都集中在历史和地理上了</p><p>今年感觉看的书挺多的。然后拿尺一量，一共才 0.6m。也没看多少书啊，真就不学无术</p><p>比较推荐的书有：</p><ul><li>《技术为径》</li><li>《格鲁夫给经理人的第一课》</li><li>《透过地理看历史》<br>还有一本电子书《激荡三十年》，这个强烈推荐</li></ul><p><img src="/assets/img/2024/img_3875.jpeg" alt="img_3875"></p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>今年一共写了 47 篇周报（三种语言）。自驾去西藏那六篇还是没补回来</p><h3 id="明年计划："><a href="#明年计划：" class="headerlink" title="明年计划："></a>明年计划：</h3><ul><li><input disabled="" type="checkbox"> 今年一年一篇博客都没有更新，明年要更四篇。争取一个季度一篇</li><li><input disabled="" type="checkbox"> 周报可以继续坚持下去</li><li><input disabled="" type="checkbox"> 公司规模翻两翻</li><li><input disabled="" type="checkbox"> 跑两场马拉松</li><li><input disabled="" type="checkbox"> 去非洲</li></ul><h1 id="公司状况"><a href="#公司状况" class="headerlink" title="公司状况"></a>公司状况</h1><p>人员没有变化，非常稳定。还是两个兼职。实习生还是增加。</p><p>本质上还是初级和高级人员的比例严重不平衡，然后没有足够的利润去支持高级工程师的薪水</p><p>有一件事情很清楚，明年必须扩张，实习生太多导致人员比例严重不协调（初级人员太多，高级人员太少）。如果想发展，就必须扩张，不然明年和今年情况差不多</p><h2 id="培训"><a href="#培训" class="headerlink" title="培训"></a>培训</h2><p>我曾经有段时间非常不理解，为什么实习生效率这么低。刚开始觉得可能是 Push 力度不够，疯狂 Push。没有效果，然后加钱，发现也没有效果。最后发现培训，这回有效果了</p><p>《格鲁夫给经理人的第一课》有一段令我印象深刻：</p><p>效率低无非就两张情况「没动力」和「不会做」。我反复确认过了这群实习生就是「不会做」</p><p>那就加大培训力度，鉴于都是研究生了，自学能力应该都没啥问题。就迭代出了一个自学清单: <a href="https://neo-developer.binbat.com/">Neo Developer</a></p><p>什么？你说你学不会？要不要和你们导师解释一下你为什么学不会？</p><h2 id="项目"><a href="#项目" class="headerlink" title="项目"></a>项目</h2><p>大部份项目都不方便透露（保密协议），搞了很多项目。本质上我们就是搞外包赚的钱</p><h2 id="产品线"><a href="#产品线" class="headerlink" title="产品线"></a>产品线</h2><p>长期做的产品只保留了两个 Live777 和连一个稳定版本都没发的 WOOM（原因是因为年初准备砍掉，年底有客户买单了，可以接着搞）</p><p>明年预计有三条产品线，其实我特别想只留一条，这样精力太分散了。但又不能停，大量的实习生没事做。而且项目也严重不足，一条产品线根本消化不完这些人，而且两条线有客户，搞出来就有收益</p><h3 id="产品和项目的边界"><a href="#产品和项目的边界" class="headerlink" title="产品和项目的边界"></a>产品和项目的边界</h3><p>这个问题之前想的很明白，但现在好像又变得不明白了，</p><p>很多时候，我就在想产品和项目有什么区别</p><p>产品不是就是若干个小项目的合集吗？</p><h3 id="Live777"><a href="#Live777" class="headerlink" title="Live777"></a>Live777</h3><p>这算我们的核心产品吗，实际上这个一个巨亏的项目。我们应该放弃这个转而使用其他的开源实现吗？开源真的有意义吗？</p><h3 id="WOOM"><a href="#WOOM" class="headerlink" title="WOOM"></a>WOOM</h3><p>感觉似乎找到了一点方向，也在缓慢迭代产品</p><h3 id="？？？"><a href="#？？？" class="headerlink" title="？？？"></a>？？？</h3><p>先卖个关子吧，准备做点和硬件结合的产品。<del>然后融合 live777 和 WOOM 打通底层逻辑，形成生态闭环</del></p><h2 id="关于开公司的这件事"><a href="#关于开公司的这件事" class="headerlink" title="关于开公司的这件事"></a>关于开公司的这件事</h2><p>很有趣，和财务和销售和各种渠道打交道。对这个世界是如何运转的有了更深刻的理解</p><p>最近很明显的感觉，主将无能累死三军。正确的方向远胜于加班</p><p>以下都是个人一些浅薄的见解</p><h3 id="高标准的待遇来自超额的利润"><a href="#高标准的待遇来自超额的利润" class="headerlink" title="高标准的待遇来自超额的利润"></a>高标准的待遇来自超额的利润</h3><p>实际上我也想发出很高的工资，创造极客风范的公司，可实际上这些需要超额利润的支撑。并不是想做就能做出来的</p><p>或者说你融到了非常多的钱，打造这种团队。但当回归自由市场，超额利润是否能够维持才是这种氛围是否能维持下去的关键</p><h3 id="超额的利润"><a href="#超额的利润" class="headerlink" title="超额的利润"></a>超额的利润</h3><p>说起来很简单，本质上就只有垄断才能获取超额的利润，其他的都会很快被模仿和超越</p><p>这种情况无非只有两个路线可以走：特许经营 和 规模效益</p><p>在中国本质上国企就是特许经营。民营企业只剩下规模效益一条路径可以走。这样的话很自然就是融资然后快速拉大规模，降低边际成本</p><h3 id="融资？"><a href="#融资？" class="headerlink" title="融资？"></a>融资？</h3><p>这件事情挺可笑的，投资机构想听宏大的故事。但保障现金流才是一个公司活下去的根本。要么去编造一个专门针对 VC 优化过的故事，要么就不要去理 VC。</p><p>这里有一件事非常可笑的事是：一群没有开过公司的投资人来指导你如何开公司。创业圈里有一种怪像，有投资才能创业。这就好像是公式化给出的答案一样。事实上，这个开放世界是没有标准解的</p><p>有项目在，公司就能活下去。活下去，才有资格去谈发展。</p><h3 id="开公司很难"><a href="#开公司很难" class="headerlink" title="开公司很难"></a>开公司很难</h3><p>做一家很垃圾的小公司光活下去就已经非常难了，想做一家伟大的公司那个更是难上加难</p><h3 id="远程工作"><a href="#远程工作" class="headerlink" title="远程工作"></a>远程工作</h3><p>远程工作很酷，我也想做一家这么酷的公司，但事实上远程工作对团队要求非常高，管理成本其实非常大。或者是 80% 以上的人都不适合这种模式</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>这件事情太复杂，每次创业感受都不一样，我要不知道哪个才是对的，哪个是错的，或者根本没有对错。本质上还是：你到底想做什么？</p><p>但这就回到了一个非常抽象的事情：企业的愿景是什么？</p><h1 id="时代在变"><a href="#时代在变" class="headerlink" title="时代在变"></a>时代在变</h1><p>从美国封杀 TikTok 开始，我意识到这个时代变了。不要看媒体怎么说，要看实际是怎么做的（看立法和关税）</p><p>之后到企业发展会进入到下一个版本，谁能最先探索出这个版本的答案，谁就有机会成为独角兽</p><p>我注意到过去几年的科技企业的发展路径已经不在适用了（抄国外的产品靠 GFW 占领国内市场）</p><p>很多人的企业都在迷茫，但我不知道这个答案。甚至我不知道我的判断是否正确。因为创业本质上就是赌博</p>]]></content>
    
    
    <summary type="html">新一的 2024 个人年终总结</summary>
    
    
    
    <category term="self" scheme="https://a-wing.top/categories/self/"/>
    
    
    <category term="self" scheme="https://a-wing.top/tags/self/"/>
    
  </entry>
  
  <entry>
    <title>2023 总结：新的冒险</title>
    <link href="https://a-wing.top/self/2023/12/31/2023"/>
    <id>https://a-wing.top/self/2023/12/31/2023</id>
    <published>2023-12-31T00:00:00.000Z</published>
    <updated>2023-12-31T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>不管怎么样，今年我开始了新的冒险</p><h2 id="目标"><a href="#目标" class="headerlink" title="目标"></a>目标</h2><p>先来看一下今年的 OKR</p><ul><li><input disabled="" type="checkbox"> 英语达到 CEFR 的 C1 水平。没考，不过显然我目前没有这个水平</li><li><input disabled="" type="checkbox"> 日语 N2。考了，没出结果，感觉过不了</li><li><input disabled="" type="checkbox"> 出国工作。没有</li><li><input disabled="" type="checkbox"> Filegogo 10k star。目前 0.2k。已经准备弃坑了</li><li><input checked="" disabled="" type="checkbox"> 用 Rust 做个项目。（自己写或者用开源的产品来满足一些商业上的项目）。Live777</li><li><input disabled="" type="checkbox"> 马拉松全程四个小时内跑完。一场马拉松都没跑</li><li><input disabled="" type="checkbox"> 给「一些因为信仰而开源的仓库」找的其他的应用场景。找不到，删库吧</li></ul><h2 id="生活"><a href="#生活" class="headerlink" title="生活"></a>生活</h2><h3 id="滑雪"><a href="#滑雪" class="headerlink" title="滑雪"></a>滑雪</h3><p>买了滑雪板，雪鞋，还有一整套装备，年初基本上每天都在东北滑雪，因为我真的想滑一整个雪季。<br>如果要问有什么后悔的事情，就是没考滑雪教练证。和没有去欧洲滑雪，主要原因是因为当时不知道可以考滑雪教练证</p><h3 id="旅居"><a href="#旅居" class="headerlink" title="旅居"></a>旅居</h3><p>某天，我觉得生活不应该再这样了，每天搞点项目维持一些生活，学点英语，再学点日语，背背单词，再看点技术，好像每天都在前进，但这就是我想要的吗？目标能实现吗？</p><p>我决定开始开始旅居生活。突然发现生活原来不需要那么多东西。我只要一个背包就够了</p><h3 id="马拉松"><a href="#马拉松" class="headerlink" title="马拉松"></a>马拉松</h3><p>很遗憾今年一场马拉松都没有跑</p><p>今年我没有跑马拉松，但在余村组织了一次超小型的马拉松。不过，当天下了非常大的雨，甚至完全没法拍照</p><h3 id="生活态度"><a href="#生活态度" class="headerlink" title="生活态度"></a>生活态度</h3><p>旅居在泰国的时候又一次见了 706 的人，顺便参加他们的空房子活动。我觉得我应该走出去，多和人交流。然后我发青旅很有趣，尤其是欧美人扎堆的青旅。不过国内的青旅是另一种东西</p><h3 id="去的地方"><a href="#去的地方" class="headerlink" title="去的地方"></a>去的地方</h3><p>很惭愧，虽然今年不需要去办公室上班。但其实没去多少地方</p><ul><li>东北：长春，吉林，白山，抚松</li><li>华北：北京</li><li>华东：杭州，安吉，上海，昆山</li><li>中南：深圳，广州，清远，广西自驾游</li><li>泰国清迈</li><li>日本：大阪，奈良，京都，金泽，长野，轻井泽，高崎，东京，札幌</li></ul><h3 id="学习"><a href="#学习" class="headerlink" title="学习"></a>学习</h3><p>今年基本上在学的只有英语和日语，年初还打算考雅思考 N2。但现在已经对考试毫无兴趣了，只想语言把它当成工具，可以和外国人交流。毕竟在二世谷的滑雪场里，人均会三种的语言</p><p>专业领域我好像已经没什么可以学的东西了，只能学点 AI 相关的。另外，每年我都会学习一门新的编程语言，今年新学的编程语言是 Rust</p><h3 id="买买买"><a href="#买买买" class="headerlink" title="买买买"></a>买买买</h3><ul><li>买了两辆车，有点离谱，一辆在老家，一辆在广州，弄了清远的车牌</li><li>Apple Watch Series 9，该换块新表了，旧表已经带了四年了</li></ul><p>并不是我没写完，而是真的没买其他东西</p><h2 id="周报"><a href="#周报" class="headerlink" title="周报"></a>周报</h2><p>今年一共写了 52 期周报，一次都没落下。甚至开始用三语的周报（中文，英语，日语）</p><p>连续几周很忙的时候差点就落下了。还好不会来</p><h2 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h2><p>嘴上说着找海外的工作，实际上根本就没准备</p><p>今年一共只主动投了三份简历（我是不是有点太心高气傲了。。。根本就不像找工作的样子）</p><ul><li>PLCT</li><li>智元机器人</li><li>Toeverything</li></ul><h3 id="关于出国留学"><a href="#关于出国留学" class="headerlink" title="关于出国留学"></a>关于出国留学</h3><p>因为有一个问题，我一直想不明白，留完学之后还是要找工作，那为什么不现在找？</p><p>或者学点什么新方向？我也许会考虑去学载人飞行器设计之类的？之前做无人机，现在想做轻型的载人的飞行器，把我也送上天。但对于能在学校学到什么，我还是很怀疑</p><h3 id="方向"><a href="#方向" class="headerlink" title="方向"></a>方向</h3><p>今年大家都在做 AI。今年我最纠结的问题就是要不要 All in AI?</p><p>我对 AI 完全不了解，而且这个方向我也不擅长。和几年前开始研究 AI 人一比，毫无优势</p><h3 id="项目"><a href="#项目" class="headerlink" title="项目"></a>项目</h3><ul><li>Filegogo</li></ul><p>去年这个项目我给予厚望，但现实情况是实在很难做出一个很好用的的 P2P 和中继的融合模式</p><ul><li>JoyRTC</li></ul><p>这最开始只是一个很小的项目，小到不值得一提。根本没有考虑未来的规划， Demo 做出来之后，反馈有点出奇的好。后来这个也作为一个核心项目。如果不走融资路线的话，这就是最容易变现的项目</p><ul><li>Live777</li></ul><p>赌上未来要做的东西，边缘网络的 WebRTC SFU 这个品类目前处于空白阶段。也许这就是未来</p><ul><li>WOOM</li></ul><p>原本只规划做一个月，能用就行，最主要的用途来验证 Live777 的稳定性，这个产品未来几年都没法赚钱。但实际上做了快三个月了。仍然问题一堆，仍然拿不出一个好用的产品</p><ul><li>Refine</li></ul><p>这个原本打算部署 Live777 用的，Live777 是一个有 tcp，udp， http 的混合应用。同时也是 stateful 应用。主流的 DevOps 不太适合这种场景。我想要的大概是一个去掉了网络层的 Docker Swarm</p><h3 id="带团队"><a href="#带团队" class="headerlink" title="带团队"></a>带团队</h3><p>不管我怎么想的，现在已经开始这么做了。带着几个研究生做项目</p><p>也许会有这样的爽文故事，一个落单的将军去指挥一群农民和正规军战斗，然后打赢了。实际上只有去做才知道，带着一群外行做这件事是有多么的困难</p><p>然后发现团队配置极为不健康，全是最初级工程师的，甚至连初级都算不上。好像也很合理，能找到好工作谁还来读研啊）））实际上没有那么多初级的可以做的，但研发这种事情，人多真的没有用。花了好大力气拉了两个大佬，兼职帮我搞搞项目</p><p>我也请教过好多人，是不是因为我的问题，导致这群研究生基本上派不上用场。得到的回复都是我没问题，真的企业早把这群人开了，或者说有的项目上有适合这些人做的比较简单的东西，可以充分吸收这类人（初级，中级，高级人员配置合理，有能力培养什么都不会的人）。但目前我们项目的状态是：我已经不敢让他们写代码了</p><hr><p>我觉得我应该是更信仰英雄主义，不希望团队里有平庸的人。就像希腊神话的阿尔戈号，大家一起去做一件事情，而不是我带着一群人</p><h3 id="开公司了"><a href="#开公司了" class="headerlink" title="开公司了"></a>开公司了</h3><p>我现在的行为从定义上来说就是「创业」。但我其实不太喜欢创业这个词，因为总有人觉得创业就高人一等。创业就XXX之类的。。。</p><p>创业其实也就是一份工作而已，人应该再工作中追求自我实现。在公司做不到那就换一家。找不到这样的公司那就自己开一家！</p><p>市场会告诉你，你的想法是对的还剩错的，你的做的事情是有价值的还是没有价值的</p><hr><p>国内情况怎么样，很糟糕。未来会变好吗？不知道，但我下注了。就这样</p><p>大不了存款都亏没，或者欠个几十万？我似乎亏的起</p><p>再过几十年我也许会后悔，我没做某件事，但我一定不会后悔，我做了什么</p><hr><p>关于融资。说真的，原本我不太想融资。没有融资这东西也能做的出来，那为什么要融？但最近的想法有点改变了，得给一起干兄弟们的点交代</p><h2 id="财务情况"><a href="#财务情况" class="headerlink" title="财务情况"></a>财务情况</h2><p>我知道好像很多人都关心这个。这一年亏了多少钱？你还能撑多久？答案是亏了 30K。对，是三万人民币，可不是少写了个 0</p><p>年初我统计过我还剩多少钱，年底，账面上看少了 70K，实际上有 30K 是借给朋友了。我心真大，这么缺钱还敢把钱借出去。买车相关的支出 20K，车应该算固定资产，得算折旧。实际上，个人亏了 20K。对你没听错，只有 20K（这两万实际上是去日本花的 14K 加上去泰国的花费 6K）。只要节流就可以收支平衡。还有该收的钱，今天当然是还没到账的，如果再算上这个，可以算收支平衡了吧</p><p>这样算有点问题，实际上我今年没买新设备，按照我的 MBP 购买价 14K，计划用 5 年，每年折旧就要 3K，再加上手机，平板之类的。电子设备总计一年折旧 5K</p><p>还能撑多久？这个还真不好说。如果以最坏情况来看，算了不能以最坏情况。最坏情况可以亏到直接去卖屁股。那如果以最好情况那？那我觉得明年年底公帐上应该有 1000K，我这是在想屁吃。</p><h2 id="未来"><a href="#未来" class="headerlink" title="未来"></a>未来</h2><p>关于明年的打算。列个清单吧</p><ul><li>马拉松全程四个小时内跑完</li><li>英语达到 CEFR 的 C1 水平</li><li>滑雪教练证</li><li>去阿勒泰滑雪</li><li>去看万里长城</li><li>项目能活下去</li><li>发出市场价的薪水</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;不管怎么样，今年我开始了新的冒险&lt;/p&gt;
&lt;h2 id=&quot;目标&quot;&gt;&lt;a href=&quot;#目标&quot; class=&quot;headerlink&quot; title=&quot;目标&quot;&gt;&lt;/a&gt;目标&lt;/h2&gt;&lt;p&gt;先来看一下今年的 OKR&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; t</summary>
      
    
    
    
    <category term="self" scheme="https://a-wing.top/categories/self/"/>
    
    
    <category term="self" scheme="https://a-wing.top/tags/self/"/>
    
  </entry>
  
  <entry>
    <title>从零开始的 OpenWrt</title>
    <link href="https://a-wing.top/linux/2023/05/01/start-openwrt"/>
    <id>https://a-wing.top/linux/2023/05/01/start-openwrt</id>
    <published>2023-05-01T04:00:00.000Z</published>
    <updated>2023-05-08T04:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近接了一个定制路由器的项目，应该算是彻底把 OpenWrt 给玩明白了</p><h2 id="一个简单的基础入门"><a href="#一个简单的基础入门" class="headerlink" title="一个简单的基础入门"></a>一个简单的基础入门</h2><p>OpenWrt 项目是一个针对嵌入式设备的 Linux 操作系统。OpenWrt 不是一个单一且不可更改的固件，而是提供了具有软件包管理功能的完全可写的文件系统。</p><h3 id="OpenWrt-和其他的-Linux-有什么不同？"><a href="#OpenWrt-和其他的-Linux-有什么不同？" class="headerlink" title="OpenWrt 和其他的 Linux 有什么不同？"></a>OpenWrt 和其他的 Linux 有什么不同？</h3><ol><li>目前 OpenWrt 的 mainline libc 目前是 musl，一些旧版本里面是 ulibc。还有几个版本里面是 glibc</li><li>init 进程是 procd</li><li>默认 sh 是 ash</li><li>很多常用工具都是精简过的版本</li><li>OpenWrt 的配置是通过 uci 完成的，关于 uci 不再本文的讨论范围之内</li></ol><h3 id="路由器和串口"><a href="#路由器和串口" class="headerlink" title="路由器和串口"></a>路由器和串口</h3><p>串口接三根线 <code>TX</code>, <code>RX</code>, <code>GND</code></p><p>需要一个硬件来连接电脑：USB-to-TTL</p><p>这东西很便宜，常用的型号： <code>FT232</code>, <code>CH340</code>, <code>PL2303</code>, <code>CP2102</code> 反正功能都一样随便选</p><p>厂家可能会标准串口的位置，也可能不会标。不标的情况只能盲找，俗称「摸串口」，实际上也很简单，把可能的情况都试一遍就能找到</p><p><img src="https://openwrt.org/_media/media/dlink/dir-615/d-link.dir-615c2.serial.header.pinout.jpg" alt="https:&#x2F;&#x2F;openwrt.org&#x2F;_media&#x2F;media&#x2F;dlink&#x2F;dir-615&#x2F;d-link.dir-615c2.serial.header.pinout.jpg"></p><p>然后需要一个读串口的软件。为了颜值，然后我直接氪了一个付费软件</p><p><img src="/assets/img/start-openwrt/screenshot_serial_debug.png" alt="screenshot_serial_debug"></p><p>不过我还是觉得命令行版的 <code>minicom</code> 更好用一点。主要是串口 shell 支持更好用。不过值得注意的是 MacOS 下的 Meta 键默认是 <code>Esc</code></p><p><img src="/assets/img/start-openwrt/screenshot_minicom.png" alt="screenshot_minicom"></p><h3 id="文件系统原理"><a href="#文件系统原理" class="headerlink" title="文件系统原理"></a>文件系统原理</h3><p>这张图来自 OpenWrt 的官网，是 TP-Link TL-WR1043ND 型号的分区图，这是一个例子，但都差不多</p><p><img src="/assets/img/start-openwrt/screenshot_flash_layer.png" alt="screenshot_flash_layer"></p><p><a href="https://openwrt.org/docs/techref/flash.layout">The OpenWrt Flash Layout</a></p><p>上面那张图很重要，不过我们要真正的理解它。官方文档讲的比较抽象，我来重新描述一下</p><p>首先 <code>u-boot</code> 和 <code>art</code> 分区最简单，这两个分区是基本上不需要有改动的，<code>u-boot</code> 就是 bootloader。刷固件也要靠它。<code>art</code> 是无线的数据，和射频有关的数据。这个不要动，也不能动。改了你的无线可能会出现不稳定的情况</p><hr><p>接下来就是终点：所谓刷固件，刷的就是 <code>firmware</code> 。为什么 <code>firmware</code> 要分层？</p><p>回答这个问题之前，不如先思考另一个问题：路由器是如何实现 reset 功能的？OpenWRT 的这个设计非常的巧妙</p><p><code>SqashFS</code> 是一个经过压缩只读文件系统。可以提高非常高的压缩比，如果要改变里面的文件，需要重写整个分区</p><p><code>JFFS2</code> 是一个可以运行在 Flash 上的文件系统，非常适合于断电系统。也可以换成 <code>UBIFS</code>。适合用于 Nand Flash</p><p><code>OverlayFS</code> 这个相比大家都比较熟悉了，容器化高度依赖这个文件系统。不过我还是要从头说：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mount -t overlay overlay -o lowerdir=/lower,upperdir=/upper,workdir=/work /merged</span><br></pre></td></tr></table></figure><p><code>OverlayFS</code> 分成 lower 层和 upper 层。从 lower 层读数据，然后所有的改动都写到 upper 层里。删除就是在 upper 层的建了个特殊的同名文件</p><p><strong>然后把 <code>SqashFS</code> 作为 lower 层，<code>JFFS2</code> 作为 upper 层。所谓的 Rest 功能就是把 <code>JFFS2</code> 格式化</strong></p><h3 id="OpenWrt-的三种固件"><a href="#OpenWrt-的三种固件" class="headerlink" title="OpenWrt 的三种固件"></a>OpenWrt 的三种固件</h3><ul><li><code>factory</code> 这个是针对一些特定厂家的 OEM，就是 <code>sysupgrade</code> 加点东西，情况比较复杂，不做讨论。这个和具体型号有关</li><li><code>sysupgrade</code> 这就是 OpenWRT 本体，就是 <code>firmware</code></li><li><code>initramfs</code> 和 <code>sysupgrade</code> 一样，但所有东西都是写在内存了，<code>ramfs</code> 不支持持久化，可以跑在没有 Flash 的机器上，也可以用于 debug</li></ul><p>所谓刷固件就是把固件 <code>dd</code> 到 <code>firmware</code> 分区上。不过 Flash 的特殊性，要用 <code>mtd</code> 命令</p><h3 id="如何刷机"><a href="#如何刷机" class="headerlink" title="如何刷机"></a>如何刷机</h3><p>首先需要了解路由器的启动过程：上电启动，先启动 bootloader，然后 bootloader 去启动 Linux 内核。然后就和普通的 Linux 的启动过程一样了。重点是要经过一个 bootloader</p><p>bootloader 是 <code>u-boot</code> 也可以是其他的。<code>u-boot</code> 不止可以启动 Linux 还可以刷机。</p><p><code>u-boot</code> 可以驱动网卡通过 TFTP 协议去下载固件来刷机</p><p>所谓的「不死 boot」，因为用 <code>u-boot</code> 和 TFTP 要用串口。不死 boot 就是增加了一个 Web 刷机页面仅此而已。这样就可以不用串口了</p><p><code>u-boot</code> 分区一般不会动，因为刷死就变成「砖」了，只能把 Flash 拆下来，放到编程器上，Flash 有两种</p><p>nor-Flash 还好引脚比较少，八个引脚飞五根线出来就可以重新烧个 <code>u-boot</code>。nand-Flash 引脚很多，只能上热风枪把 Flash 吹下来，烧完了再吹上去</p><h2 id="移植固件在做什么？"><a href="#移植固件在做什么？" class="headerlink" title="移植固件在做什么？"></a>移植固件在做什么？</h2><p>事实上就是要调出一组参数，然后移植一些驱动（通常是无线驱动）</p><h3 id="Kernel"><a href="#Kernel" class="headerlink" title="Kernel"></a>Kernel</h3><p>就是编译 Linux 内核需要的参数，控制哪个功能编译到内核里面，那个功能编译成可加载到模块</p><p>但是在 OpenWRT 的编译系统里面，会对 Linux 内核打大量的 Patch。内核的参数是一个基本上不需要调的参数</p><p>在原本的 Linux 内核里用 <code>make menuconfig</code> 这个命令来配置。但是在 OpenWRT 里内核的参数的配置命令被改成了 <code>make kernel_menuconfig</code></p><p>不过，更推荐的一种做法是：修改这个文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">target/linux/&lt;Target System&gt;/&lt;Subtarget&gt;/config-&lt;Kernel Version&gt;</span><br></pre></td></tr></table></figure><h3 id="Package"><a href="#Package" class="headerlink" title="Package"></a>Package</h3><p>而且 <code>make menuconfig</code> 对应的是软件包的参数，来控制哪个软件包需要内置到固件里，哪个软件包需要做成需要安装到包（用 opkg 命令来安装）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">make menuconfig</span><br></pre></td></tr></table></figure><p><img src="/assets/img/start-openwrt/screenshot_openwrt_makeconfig.png" alt="screenshot_openwrt_makeconfig"></p><h3 id="Device-Tree"><a href="#Device-Tree" class="headerlink" title="Device Tree"></a><strong>Device Tree</strong></h3><p>操作系统要知道硬件的基本信息，但是在我们常用的 x86 的计算机里，硬件信息存储在 BIOS 里面的，然后通过 ACPI（Advanced Configuration and Power Interface）传递给 Linux 内核。内部的设备比如硬盘，pcie 设备都是有固件的。所有可以通过总线协议去拿到设备的基本信息。</p><p>在嵌入式 Linux 的硬件里为了节约成本，很多功能硬件只有一个芯片，根本没有地方放基本信息。所以这些信息只能硬编码到内核里面</p><p>为了解决这样的问题，Linux 使用了一种叫 DTS（Device Tree Specification）设备树描述的东西来解决这个问题。</p><p>当然，DTS 是一个纯文本。需要转换成二进制（或者说叫编译成二进制）的 DTB （Device Tree Blob）交给 Linux 内核</p><h2 id="逆向固件"><a href="#逆向固件" class="headerlink" title="逆向固件"></a>逆向固件</h2><p>如果这个硬件已经支持了 Linux ，这样的话就会有一个捷径，我们可以在不需要知道硬件具体信息的情况下拿到 DTB 给新的内核用。为了做到这一点，我们可以逆向固件来取得 DTB</p><h3 id="binwalk"><a href="#binwalk" class="headerlink" title="binwalk"></a>binwalk</h3><p>对于一个固件，可以用 <a href="https://github.com/ReFirmLabs/binwalk">binwalk</a> 这个工具可以把 linux 内核和 rootfs 提取出来</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">binwalk -Me openwrt.bin</span><br></pre></td></tr></table></figure><p>可以用这个工具看到类似这样的信息</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">DECIMAL       HEXADECIMAL     DESCRIPTION</span><br><span class="line">--------------------------------------------------------------------------------</span><br><span class="line">0             0x0             uImage header, header size: 64 bytes, header CRC: 0xD6325671, created: 2021-10-20 00:30:33, image size: 3202195 bytes, Data Address: 0x81001000, Entry Point: 0x81001000, data CRC: 0x8A066943, OS: Linux, CPU: MIPS, image <span class="built_in">type</span>: OS Kernel Image, compression <span class="built_in">type</span>: lzma, image name: <span class="string">&quot;MIPS OpenWrt Linux-3.10.108&quot;</span></span><br><span class="line">64            0x40            LZMA compressed data, properties: 0x6D, dictionary size: 8388608 bytes, uncompressed size: 8431488 bytes</span><br><span class="line">3202259       0x30DCD3        Squashfs filesystem, little endian, version 4.0, compression:xz, size: 5333278 bytes, 1410 inodes, blocksize: 262144 bytes, created: 2021-10-20 00:30:29</span><br></pre></td></tr></table></figure><h3 id="dtb-magic"><a href="#dtb-magic" class="headerlink" title="dtb magic"></a>dtb magic</h3><p>Linux 内核镜像里面有一段是记录的 dtb 信息的，通过 dtb magic（dtb 魔数）和其他数据分开。所以只要找到 dtb magic，就可以把 dtb 取出来了。这个工具可以找到两个版本的</p><ul><li><a href="https://github.com/PabloCastellano/extract-dtb/blob/master/extract_dtb/extract_dtb.py#L25">extract_dtb.py</a></li><li><a href="https://github.com/MoetaYuko/split-appended-dtb/blob/master/split-appended-dtb.c#L23-L26">split-appended-dtb</a></li></ul><p>然后再用 <code>dtc</code> 命令进行格式转换，转换成 DTS</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dtc -I dtb -O dts -o out.dts openwrt.dtb</span><br></pre></td></tr></table></figure><p>最后把 DTS 放到 <code>target/linux/&lt;Target&gt;/dts/&lt;Target Profile&gt;.dts</code> 里面就可以了</p><p>当然，以上只是理想情况，还有找不到 dtb 的情况，比如路由器厂商直接硬编码参数</p><h2 id="Flash"><a href="#Flash" class="headerlink" title="Flash"></a>Flash</h2><p>这个相当于硬盘，或者说叫 ROM。分为有控制器的和无控制器的。路由器上主要用无控制器的 nor-flash 或 nand-flash。注意：nor-flash 和 nand-flash 是存储介质的不同</p><p>但由于路由器要用更精简的结构，可没有额外的空间去放类似 x86 BIOS 一类的东西。所以 bootloader（U-Boot） 也是放在 Flash 里的，也就是说如果把 Flash 全清除了就彻底启动不了了</p><p>但还是有恢复的办法，一种是用 JTAG 接口直接读写读写 Flash 。把 U-Boot 烧进去。但只仅限于预留 JTAG 接口的情况。没有就只能把芯片拆下来</p><h3 id="MTD-和-FTL"><a href="#MTD-和-FTL" class="headerlink" title="MTD 和 FTL"></a>MTD 和 FTL</h3><p>硬盘就属于有控制器的，SD 卡也是有控制器的。说控制器可能有点抽象，但由于闪存特性，需要平衡的写入算法，还有坏块管理一类的功能。这个主控芯片做的事情有个更专业的名称来描述。叫FTL（Flash Translation Layer）</p><p>所以 Flash 分成两种情况一种是 rawFlash，另一种是带 FTL 的 Flash</p><p>Linux 内核实现有个功能的模块叫：MTD（Memory Technology Devices），可以直接控制 Flash 芯片的读写，但这远远不够，还需要一层逻辑地址的映射，来实现坏块管理一类的功能。MTD 里面还有个内核实现的 FTL。对于闪存，FTL 是必须的，如果 Flash 里面没有。当然这个功能可以由 Linux 内核来实现。</p><h3 id="Nor-Flash-与-CFI-和-SPI"><a href="#Nor-Flash-与-CFI-和-SPI" class="headerlink" title="Nor-Flash 与 CFI 和 SPI"></a>Nor-Flash 与 CFI 和 SPI</h3><p>Nor-flash 有实际上有两种接口：CFI（Common Flash Interface） 和 SPI （Serial Peripheral Interface）。虽然 CFI 和 SPI 接口最初是为了与 Nor-Flash 存储器兼容而设计的，但是它们并不仅仅适用于 Nor Flash 存储器，还可以用于其他类型的存储器。</p><p>我拿到这个路由器是 SPI-Flash。有 8 个引脚，有四根数据线，四根数据线有三种模式（只是传输速度的区别）：</p><ul><li>Standard SPI （接一根线）</li><li>Dual SPI （接两根线）</li><li>Quad SPI （接四根线）</li></ul><h3 id="Nand-Flash-和-eMMC"><a href="#Nand-Flash-和-eMMC" class="headerlink" title="Nand-Flash 和 eMMC"></a>Nand-Flash 和 eMMC</h3><p>Nand-Flash 和 Nor-Flash 都是由日本的富士雄发明的。Nand-Flash 的优点是容量大寿命长</p><p>eMMC（embedded Multi-MediaCard）是从 MMC（Multi-MediaCard）的基础上发展起来的然后变成了标准。但如果从内核视角，可以把 eMMC 看成协议</p><p>当然可以把无控制器的存储芯片（Raw Nand-Flash）加个控制器，比如  eMMC 就是 Nand-Flash 加个主控芯片</p><h2 id="移植无线驱动"><a href="#移植无线驱动" class="headerlink" title="移植无线驱动"></a>移植无线驱动</h2><p>因为这个驱动已经支持了 Linux 所以只需要把文件放到内核对应的目录下就可以了</p><p>比如，我是 mediatek 的 xxx 硬件的驱动。把这个驱动放到这里面</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">drivers/net/wireless/mediatek/xxx</span><br></pre></td></tr></table></figure><p>但实际上我移植完还没测试，就发现我的无线硬件已经有开源的驱动了。<del>都给用开源驱动，闭源驱动狗都不用</del></p><p>但还不够，还需要修改两个文件：</p><h3 id="Kconfig"><a href="#Kconfig" class="headerlink" title="Kconfig"></a>Kconfig</h3><p><code>Kconfig</code> 用于在 <code>make menuconfig</code> 时配置编译参数。</p><p>要修改这个文件 <code>drivers/net/wireless/mediatek/Kconfig</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">source</span> <span class="string">&quot;drivers/net/wireless/mediatek/xxx/Kconfig&quot;</span></span><br></pre></td></tr></table></figure><h3 id="Makefile"><a href="#Makefile" class="headerlink" title="Makefile"></a>Makefile</h3><p>还需要在 make 时找到代码对应的路径 <code>drivers/net/wireless/mediatek/Makefile</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">obj-$(CONFIG_MT76_xxx) += xxx/</span><br></pre></td></tr></table></figure><h2 id="不同的-SSID-后缀"><a href="#不同的-SSID-后缀" class="headerlink" title="不同的 SSID 后缀"></a>不同的 SSID 后缀</h2><p>作为一个企业级方案，我们需要每个 SSID 的后缀都是不同的。我们要自动生成一个随机的后缀。当然更常见的方法是使用网卡的 mac 地址的后几位来标记后缀</p><p>在这个目录里建一个文件 <code>/etc/uci-defaults/42-ssid</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">uci -q batch &lt;&lt; <span class="string">EOI</span></span><br><span class="line"><span class="string">set wireless.@wifi-device[0].disabled=&#x27;0&#x27;</span></span><br><span class="line"><span class="string">set wireless.@wifi-iface[0].ssid=OpenWrt_$(cat /dev/urandom | tr -dc A-Z | head -c 4)</span></span><br><span class="line"><span class="string">set wireless.@wifi-iface[0].mode=&#x27;ap&#x27;</span></span><br><span class="line"><span class="string">set wireless.@wifi-iface[0].network=&#x27;lan&#x27;</span></span><br><span class="line"><span class="string">commit wireless</span></span><br><span class="line"><span class="string">EOI</span></span><br></pre></td></tr></table></figure><h2 id="蜂窝网络（Cellular-Network）"><a href="#蜂窝网络（Cellular-Network）" class="headerlink" title="蜂窝网络（Cellular Network）"></a>蜂窝网络（Cellular Network）</h2><p>或者叫 LTE 网络或者说 4G 可能更熟悉一点。不过现在都已经是 5G 时代了</p><h3 id="移远-EC20"><a href="#移远-EC20" class="headerlink" title="移远 EC20"></a>移远 EC20</h3><p>这个模块可能很多人看到这个名字都觉得很亲切。这个模块用的实在是太多了</p><p>我手上的是一个 mini pcie 接口的模块。但实际上是 pcie 接口下面有个 USB-HUB 。然后连接了几个 USB 的网卡和串口设备。所以：同时需要 pcie, usb, serial 的驱动</p><p>串口发送 AT 指令来控制连接状态，或者切换供应商。然后通过 USB 网卡联网</p><p>可以使用这样的命令来查看状态</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> /sys/kernel/debug/usb/devices</span><br></pre></td></tr></table></figure><h3 id="协议"><a href="#协议" class="headerlink" title="协议"></a>协议</h3><p>实际上 USB 的网卡有这几种协议 <code>qmi</code>, <code>mbim</code>, <code>ncm</code>, <code>rndis</code></p><p>但这似乎是和你用的模块有关，但是我并没都测试过，也说不清楚具体区别。这方面资料也比较少，感觉好像是哪个能跑通，哪个效果好就用哪个。。。</p><ul><li><code>luci-proto-3g</code></li><li><code>luci-proto-qmi</code></li><li><code>luci-proto-ncm</code></li><li><code>luci-proto-modemmanager</code></li></ul><h2 id="多-Wan-口切换"><a href="#多-Wan-口切换" class="headerlink" title="多 Wan 口切换"></a>多 Wan 口切换</h2><p>我们现在有两个 Wan 口了。但实际工作是两个 Wan 口（有线的和 modem）会互相覆盖掉默认路由</p><p>我们有个需求，要在有有线的时候网络流量都要走 Wan 口，在 Wan 口没有插网线的时候要走蜂窝网络通信</p><p>需要实现这样一个切换功能，切换有三种实现思路：</p><h3 id="写个脚本挂在-cron"><a href="#写个脚本挂在-cron" class="headerlink" title="写个脚本挂在 cron"></a>写个脚本挂在 cron</h3><p>这是最容易想到的方式，也是最烂的实现方式，写个脚本定时检测网络状态，然后切换默认网关。不过很显然，这是网络路由没学好（</p><h3 id="使用负载均衡工具接管出口流量"><a href="#使用负载均衡工具接管出口流量" class="headerlink" title="使用负载均衡工具接管出口流量"></a>使用负载均衡工具接管出口流量</h3><p>比如 mwan3 来做负载均衡。控制流量出口，这原本是用在多 wan 口来提升网络带宽的方案，可以用它探测网络是否掉线，来控制流量出口</p><h3 id="metric-来控制"><a href="#metric-来控制" class="headerlink" title="metric 来控制"></a>metric 来控制</h3><p>多条默认路由。使用 metric 来控制。metric 可看成是路由的费用</p><p>比如像这样</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">default via 192.168.1.1 dev wan proto dhcp src 192.168.1.2 metric 10</span><br><span class="line">default via 10.10.10.1 dev modem proto dhcp src 10.10.10.2 metric 40</span><br></pre></td></tr></table></figure><p>让 wan 接口的 metric 小一点，拔掉 wan 口网线，wan 口默认路由会被删除</p><h2 id="编译的坑"><a href="#编译的坑" class="headerlink" title="编译的坑"></a>编译的坑</h2><p>你可能会在文档上见到这样的命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">make FILES=<span class="string">&quot;files&quot;</span> PACKAGES=<span class="string">&quot;nano shadow sudo&quot;</span></span><br></pre></td></tr></table></figure><h3 id="FILES"><a href="#FILES" class="headerlink" title="FILES"></a><code>FILES</code></h3><p>可以指定一个自定义的文件夹，来覆盖掉默认位置的文件。比如多网卡切换和默认 SSID 随机的后缀都要用这个功能来实现</p><h3 id="PACKAGES"><a href="#PACKAGES" class="headerlink" title="PACKAGES"></a><code>PACKAGES</code></h3><p>预置软件包，对于要支持蜂窝网络的情况当然要预置一些软件包，或者说对于一款定制的路由器来说，不需要有软件源，所有的用到的包都要预置到固件里</p><p>当然还有一个更好的办法比如更改 <code>DEVICE_PACKAGES</code></p><p>比如这个例子： <code>target/linux/ramips/image/mt7621.mk</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">define Device/mediatek_mt7621-xxx</span><br><span class="line">  $(Device/dsa-migration)</span><br><span class="line">  $(Device/uimage-lzma-loader)</span><br><span class="line">  IMAGE_SIZE := 7872k</span><br><span class="line">  DEVICE_VENDOR := Mediatek</span><br><span class="line">  DEVICE_MODEL := MT7621 AT XXX</span><br><span class="line">  DEVICE_PACKAGES := kmod-mt7603 kmod-mt7615e usb-modeswitch kmod-usb3 \</span><br><span class="line">                                         kmod-usb-core kmod-usb-net kmod-usb-net-cdc-ether \</span><br><span class="line">                                         kmod-usb-net-rndis kmod-usb-net-qmi-wwan kmod-usb-ohci-pci \</span><br><span class="line">                                         kmod-usb-uhci kmod-usb2-pci \</span><br><span class="line">                                         kmod-usb-serial kmod-usb-serial-option kmod-usb-serial-wwan \</span><br><span class="line">                                         luci luci-proto-3g luci-app-multiwan</span><br><span class="line">endef</span><br><span class="line">TARGET_DEVICES += mediatek_mt7621-xxx</span><br></pre></td></tr></table></figure><p>在这里更改 <code>DEVICE_PACKAGES</code> 只有在第一次生成 <code>.config</code> 时才生效。注意：是第一次生成，这里特指之前没有 <code>.config</code> 的情况。如果有会生成给 <code>DEFAULT_</code> 的选项，实际上这个包也会在固件里。<del>我觉得这个设计很有问题。</del></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这个项目前前后后忙了一个多月，有一半时间都在错误的方向上努力。实际上我并没有通过逆向拿到 dtb，自己编译的固件逆向能拿到 dtb，厂家给的拿不到。所有这个项目的 dtb 参数是自己写的。另一个花费时间很多的地方是 mtd。总是无法写</p><h3 id="绝望的开局——厂家的-SDK-有多坑"><a href="#绝望的开局——厂家的-SDK-有多坑" class="headerlink" title="绝望的开局——厂家的 SDK 有多坑"></a>绝望的开局——厂家的 SDK 有多坑</h3><p>我拿到了三个 G 的 SDK。。。打开 tar 包发现，所有的编译中间产物都在那里。但你不能执行 make clean 。。因为 clean 之后就没法编译了。。。</p><p>原因是厂家把驱动放在中间产物里了。。。</p><p>很多包的地址过于古老已经没法下载了</p><p>基于 openwrt 15 的 sdk。要知道 openwrt 17 有非常大的改动</p><p>没有版本管理，不知道是哪个版本。</p><p>只能找一相近的版本进行 diff 。但都是有上百的文件个改动。唯一能找到的就是无线驱动的路径</p><h3 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h3><p>我又学会一项新技能</p><p>OpenWrt 这个系统特别强，然后再配合 uci。不仅仅是路由器，用来做其他的产品也是个不错的选择</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://github.com/HeyGoda/Hello-Embedded-Linux/blob/27c2ee700f73b0f63b56a7346694a36bcdef343d/%E7%B3%BB%E7%BB%9F%E5%88%9D%E5%A7%8B%E5%8C%96.md#openwrt-procd">Hello-Embedded-Linux&#x2F;系统初始化</a></p><p><a href="https://fjkfwz.github.io/2014/12/04/Openwrt-File-System/">Openwrt 文件系统</a></p><p><a href="https://www.intuitibits.com/2019/06/01/transfer-tftp-server/">Transfer: Simple and reliable TFTP server for macOS - Intuitibits</a></p><p><a href="https://github.com/devicetree-org/devicetree-specification">GitHub - devicetree-org&#x2F;devicetree-specification: Devicetree Specification document source files</a></p><p><a href="https://elinux.org/Device_Tree_Reference">Device Tree Reference - eLinux.org</a></p><p><a href="https://www.right.com.cn/forum/thread-539091-1-1.html">从固件里反编译dtb为dts-OPENWRT专版-恩山无线论坛 -  Powered by Discuz!</a></p><p><a href="https://zhuanlan.zhihu.com/p/26944064">杂谈闪存三：FTL</a></p><p><a href="https://blog.csdn.net/weixin_35473679/article/details/116924669">linux ftl原理,Linuxflash文件系统剖析_GOLFING路上的博客-CSDN博客</a></p><p><a href="http://www.linux-mtd.infradead.org/doc/ubifs.html#L_raw_vs_ftl">Memory Technology Device (MTD) Subsystem for Linux.</a></p><p><a href="https://blog.csdn.net/zhejfl/article/details/78544796">搞清楚nand flash和 nor flash 以及 spi flash 和cfi flash 的区别_qspi flash，nor nand_书中倦客的博客-CSDN博客</a></p><p><a href="https://blog.csdn.net/wxh0000mm/article/details/85610542">第十七期 U-Boot norflash 操作原理分析 《路由器就是开发板》_boot on flash_子曰小玖的博客-CSDN博客</a></p><p><a href="https://zhuanlan.zhihu.com/p/74163577">ICMAX介绍 NOR、 NAND、Raw Flash和 Managed Flash的区别</a></p><p><a href="https://zhuanlan.zhihu.com/p/26745577">杂谈闪存二：NOR和NAND Flash</a></p><p><a href="http://blog.coderhuo.tech/2020/07/18/flash_basics/">NAND Flash基础知识简介</a></p><p><a href="https://openwrt.org/docs/guide-developer/uci-defaults#integrating_custom_settings">UCI defaults</a></p><p><a href="https://fjkfwz.github.io/2014/08/16/Openwrt-Compile-Pro/">Openwrt 编译进阶</a></p><p><a href="https://www.openwrt.pro/post-542.html">移远EC20（4G模块）通过openwrt路由器拨号上网 - OpenWrt开发者之家</a></p><p><a href="https://openwrt.org/docs/guide-developer/build-image-with-3g-dongle-support">Building image with support for 3g&#x2F;4g and usb tethering</a></p><p><a href="https://openwrt.org/docs/guide-user/storage/usb-installing">Installing and troubleshooting USB Drivers</a></p><p><a href="https://openwrt.org/docs/guide-user/network/wan/wwan/3gdongle">Use 3g&#x2F;UMTS USB Dongle for WAN connection</a></p><p><a href="https://openwrt.org/docs/guide-user/network/wan/wwan/ltedongle">How to use LTE modem in QMI mode for WAN connection</a></p><p><a href="https://zsien.cn/openwrt-ltedongle/">OpenWRT 使用 qmi 实现 4G 访问 - 二䖝</a></p><p><a href="https://teklager.se/en/knowledge-base/openwrt-4g-wwan-configuration/">OpenWRT 4G WWAN configuration</a></p><p><a href="https://zhuanlan.zhihu.com/p/352098418">基于openwrt的MWAN3实现多运营商负载均衡的一种方法</a></p><p><a href="https://openwrt.org/docs/guide-user/additional-software/imagebuilder#examples">Using the Image Builder</a></p><p><a href="https://openwrt.org/docs/techref/image.makefile">image&#x2F;Makefile Details</a></p>]]></content>
    
    
    <summary type="html">OpenWrt 刷机系统定制</summary>
    
    
    
    <category term="linux" scheme="https://a-wing.top/categories/linux/"/>
    
    
    <category term="linux" scheme="https://a-wing.top/tags/linux/"/>
    
    <category term="openwrt" scheme="https://a-wing.top/tags/openwrt/"/>
    
  </entry>
  
  <entry>
    <title>2022 年的总结</title>
    <link href="https://a-wing.top/self/2022/12/31/2022"/>
    <id>https://a-wing.top/self/2022/12/31/2022</id>
    <published>2022-12-31T00:00:00.000Z</published>
    <updated>2022-12-31T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>这是一件令人血压飙升到事情。最不想总结的就是工作。我今年炒掉的我全职工作的老板</p><h2 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h2><p>？为什么会血压飙升。大概原因应该是这样的：</p><blockquote><p>同事在做一件无意义的事情。它知道无意义。但理由是老板说的</p></blockquote><p>我当时就差点操起凳子砸过去了。。。。</p><p>但我是成熟的职场人，我是成熟的职场人，我是成熟的职场人。我忍住了 🥹。我做到了，我忍住了！！！</p><blockquote></blockquote><p>大概就这样吧。我觉得这应该是我今年做的最正确的一件事了</p><p>而且我感觉我的脾气越来越暴躁了。很难和人合作，经常想操起板凳砸过去。<del>做的都是什么垃圾</del></p><h2 id="生活"><a href="#生活" class="headerlink" title="生活"></a>生活</h2><p>尝试写周报，变成生活的一部分</p><h3 id="买买买"><a href="#买买买" class="headerlink" title="买买买"></a>买买买</h3><ul><li>iPhone 13 mini 很棒！</li><li>Macbook Pro 13 m2 那款。不推荐。。。因为祖传的屏幕和非整数倍缩放</li><li>iPad Air 5 除了续航，一切都好</li><li>滑雪板 Fischer worldcup rc4 sc 和 硬度 110 的雪鞋。总记花了八千大洋。有点贵</li></ul><h3 id="酒吧"><a href="#酒吧" class="headerlink" title="酒吧"></a>酒吧</h3><p>今年第一次去酒吧，不对应该是个第二次。第一次只是去尝试，对于品酒完全没上道</p><p>大概就是这件事，有点轻小说标题：</p><blockquote><p>《关于我在酒吧和第一次见面的网友妹子喝酒差点被灌醉的这件事》</p></blockquote><p>好像今年一共和这妹子喝了五次，大概</p><blockquote></blockquote><h3 id="马拉松"><a href="#马拉松" class="headerlink" title="马拉松"></a>马拉松</h3><p>竟然真的成功举办了，参加了厦门马拉松。疫情这几年参加的唯一的一次马拉松</p><h3 id="游泳"><a href="#游泳" class="headerlink" title="游泳"></a>游泳</h3><p>这年几乎刷遍的深圳各大游泳馆，深水证都集齐了。深圳的游泳馆，宝安中心游泳馆，三米深的大池无疑是最好的游泳馆。排名第二的应该就是大运中心那个游泳馆。其他的都比较一般</p><h3 id="背包客"><a href="#背包客" class="headerlink" title="背包客"></a>背包客</h3><p>旅行时化作背包客，我的包大概有 10kg。可能还不止。说起来我旅行时一直都是背包客</p><p>我对面基这件事有些逐渐丧失热情了，似乎纯纯的吃饭是一种罪过。如果不聊点技术，商业或者一些思想就觉得荒废了时间一样。我也不知道这是好事还是坏事，如果双方什么都没有学到，那面基真是是件正确的事吗</p><h3 id="牙"><a href="#牙" class="headerlink" title="牙"></a>牙</h3><p>智齿蛀的开始疼。检查发现的牙周炎。目前是拔掉了一边的智齿。明年还要接着拔智齿</p><h2 id="旅行"><a href="#旅行" class="headerlink" title="旅行"></a>旅行</h2><p>一次自驾游：</p><p><img src="/assets/img/2022/map.png" alt="map.png"></p><p>今年大概去了这些地方：</p><ul><li>广西<ul><li>桂林</li></ul></li><li>广东<ul><li>珠海</li></ul></li><li>湖南<ul><li>长沙</li></ul></li><li>云南<ul><li>大理</li><li>丽江</li><li>香格里拉</li><li>德钦</li><li>独龙江乡</li></ul></li><li>四川<ul><li>甘孜</li><li>雅安</li><li>攀枝花</li></ul></li><li>福建<ul><li>厦门</li></ul></li><li>浙江<ul><li>杭州</li></ul></li></ul><p>唯一的遗憾应该就是没去成「西藏」</p><h2 id="学习"><a href="#学习" class="headerlink" title="学习"></a>学习</h2><p>英语学的也不怎么样。争取明年英语达到 CEFR C1 标准</p><p>又再一次开始学习日语</p><p>书也没看多少，电影也没看多少，番剧也没看多少，我的时间都去哪里了！</p><p>读书</p><ul><li><input checked="" disabled="" type="checkbox"> RUST 编程之道</li><li><input disabled="" type="checkbox"> APUE（Advanced Programming in the Unix Environment）</li><li><input disabled="" type="checkbox"> 出埃及记</li><li><input checked="" disabled="" type="checkbox"> 浪潮之巅</li><li><input disabled="" type="checkbox"> 圆圈正义（罗翔）</li><li><input checked="" disabled="" type="checkbox"> 风之谷</li><li><input disabled="" type="checkbox"> 算法（第四版）</li><li><input disabled="" type="checkbox"> 算法导论</li><li><input checked="" disabled="" type="checkbox"> 图解算法</li><li><input disabled="" type="checkbox"> 代码整洁之道</li><li><input checked="" disabled="" type="checkbox"> 编译原理</li><li><input checked="" disabled="" type="checkbox"> 亚洲教父</li><li><input disabled="" type="checkbox"> 现代世界体系</li><li><input disabled="" type="checkbox"> 代码之美</li><li><input disabled="" type="checkbox"> 性能之巅</li></ul><p>影视</p><ul><li><input disabled="" type="checkbox"> 医龙</li><li><input disabled="" type="checkbox"> 冰与火之歌</li><li><input checked="" disabled="" type="checkbox"> 天气之子</li><li><input checked="" disabled="" type="checkbox"> 真田丸</li><li><input checked="" disabled="" type="checkbox"> 想吃掉你的胰脏</li><li><input disabled="" type="checkbox"> 老友记</li></ul><h2 id="技术"><a href="#技术" class="headerlink" title="技术"></a>技术</h2><p>今年编程语言说好的学习 Rust。不过一行已没写</p><p>今年几乎没怎么学习新技术，如果有，那应该就是 docker swarm 和 kubernetes。不过我甚至不认为我了解了 kubernetes。。。</p><p>再就是学了一点关于性能工程的皮毛</p><p>今年更多的关注点都在「产品」和「市场」上。我时常在思考，我的技术是不是已经到头了。或者说不应该再把时间放在技术上了，应该更多的关注「产品」和「市场」</p><p>不过我的下一份工作还得是技术</p><h2 id="开源"><a href="#开源" class="headerlink" title="开源"></a>开源</h2><h3 id="社区项目"><a href="#社区项目" class="headerlink" title="社区项目"></a>社区项目</h3><p>今年几乎没有参与任何的社区项目。除了 archlinuxcn 几个包，<del>这贡献量我都快被除名了。</del></p><p>参与社区，刷 PR 这种事情现在对我已经毫无吸引力。能做的也就是发现的 bug 然后顺手修一下。更多的是时间在实现自己的想法上</p><h3 id="filegogo"><a href="#filegogo" class="headerlink" title="filegogo"></a>filegogo</h3><p>这个应该是我目前唯一主推的开源项目 <a href="https://github.com/a-wing/filegogo">filegogo</a></p><p>今年这个项目花的时间其实并不多，今年应该算找了一些这个项目未来发展的方向。<del>就是 firefox send 的替代品</del></p><p>今年加入了文件中转。P2P 的比重逐渐降低</p><blockquote><p>首先它应该是一个好用的工具。P2P 在这件事情上并不会让他变得更好用。恰恰相反，P2P 所带来的各种问题，产生的 BUG 明显会降低可用性。</p></blockquote><p>作为一个用完即走的工具，人们并不关心他是走中心服务器还是 P2P。核心是要解决问题。</p><blockquote></blockquote><p>所以 filegogo 明年的目标就是：<strong>firefox send 的替代品</strong></p><h3 id="一些因为信仰而开源的仓库"><a href="#一些因为信仰而开源的仓库" class="headerlink" title="一些因为信仰而开源的仓库"></a>一些因为信仰而开源的仓库</h3><p>这个应该是我业余时间花的最多的地方。我不广告，也不推销。因为这几个项目你们用不到。<del>能用到的人自然也会找到</del></p><p>就放张图吧：</p><p><img src="/assets/img/2022/superdock-arch.png" alt="superdock-arch.png"></p><p>这是生产级的东西，也有很多客户在用。这东西是在解决真实存在的问题，不过领域受众太小。也不可能成为社区，几乎也不会有贡献者。 <del>不过我觉得这东西也可能可以应用其他的领域。如果有人知道这种东西比较适合应用在哪些其他场景，欢迎和我讨论</del></p><p><strong>这东西开源纯粹就是因为信仰</strong></p><h2 id="未来"><a href="#未来" class="headerlink" title="未来"></a>未来</h2><p>关于明年的打算。用 OKR 吧</p><ul><li>英语达到 CEFR 的 C1 水平</li><li>日语 N2</li><li>出国工作</li><li>Filegogo 10k star。目前 0.2k</li><li>用 Rust 做个项目。（自己写或者用开源的产品来满足一些商业上的项目）</li><li>马拉松全程四个小时内跑完</li><li>给「一些因为信仰而开源的仓库」找的其他的应用场景</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;这是一件令人血压飙升到事情。最不想总结的就是工作。我今年炒掉的我全职工作的老板&lt;/p&gt;
&lt;h2 id=&quot;工作&quot;&gt;&lt;a href=&quot;#工作&quot; class=&quot;headerlink&quot; title=&quot;工作&quot;&gt;&lt;/a&gt;工作&lt;/h2&gt;&lt;p&gt;？为什么会血压飙升。大概原因应该是这样的：&lt;/</summary>
      
    
    
    
    <category term="self" scheme="https://a-wing.top/categories/self/"/>
    
    
    <category term="self" scheme="https://a-wing.top/tags/self/"/>
    
  </entry>
  
  <entry>
    <title>[译] Arch Linux 上的完整 Wayland 设置</title>
    <link href="https://a-wing.top/linux/2022/01/03/translate_wayland"/>
    <id>https://a-wing.top/linux/2022/01/03/translate_wayland</id>
    <published>2022-01-03T00:00:00.000Z</published>
    <updated>2022-01-03T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>本文是一份在 Arch Linux 上实现完整的 Wayland 设置的指南。</p><p>原文：<a href="https://www.fosskers.ca/en/blog/wayland">Full Wayland Setup on Arch Linux</a></p><p>如果你遵循整个指南，到最后你将拥有：</p><ul><li><a href="https://swaywm.org/">Sway</a> 一个平铺式窗口管理器。</li><li><a href="https://github.com/Alexays/Waybar">Waybar</a> 一个与 <a href="https://github.com/polybar/polybar">Polybar</a> 非常相似的状态条。</li><li><a href="https://hg.sr.ht/~scoopta/wofi">Wofi</a> 一个纯 GTK（也就是 Wayland）的可定制应用程序启动器。</li><li><a href="https://github.com/alacritty/alacritty">Alacritty</a> 一个现代化的终端，”又不是不能用”。</li><li>Wayland 中的 Firefox 和 Chromium，可以进行屏幕共享。</li><li><code>Emacs</code> 通过全新的纯 GTK 内部结构在 Wayland 中完全运行。</li><li>大多数 QT 应用程序在 Wayland 中运行。</li><li>如果可以的话，将 Steam 游戏设置为考虑 Wayland。</li><li>(可选）通过 Fcitx5 的日语输入。</li></ul><p>你还将学习如何确认一个应用程序是否在 <code>Wayland</code> 中运行，并了解 <code>XWayland</code> 和那些仍然需要它才能正常运行的主要程序。<br>虽然本指南是以 Arch Linux 为基础编写的，但它应该可以适应你所运行的任何的 Linux 发行版。好好享受吧，祝你好运!</p><p><strong>注意：在继续之前，你可能希望在手机或第二台电脑上打开本指南，因为我们需要在整个过程中多次重启你的窗口管理器。</strong></p><h2 id="开始之前：Wayland-XWayland"><a href="#开始之前：Wayland-XWayland" class="headerlink" title="开始之前：Wayland? XWayland?"></a>开始之前：Wayland? XWayland?</h2><p>Wayland 是 Linux 的下一代显示协议。你可能听说过 <code>X</code>（或 <code>X11</code> 或 <code>XOrg</code>），但你可能不知道它的问题：年龄、性能、安全性和开发友好性。</p><p>甚至 Adam Jackson（X 的长期发布和管理者），也 <a href="https://ajaxnwnk.blogspot.com/2020/10/on-abandoning-x-server.html">呼吁采用 Wayland</a>。<br>不过，<code>X</code> 已经很成熟了，过渡不会在一夜之间发生。Linux 系统中的许多核心应用都与它的生态系统紧密相连。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt; pacman -Qg xorg | <span class="built_in">wc</span> -l</span><br><span class="line">38</span><br></pre></td></tr></table></figure><p>你可能会惊讶地发现，你几乎肯定已经安装了 <code>wayland</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&gt; pacman -Qi wayland</span><br><span class="line">Name            : wayland</span><br><span class="line">Version         : 1.19.0-1</span><br><span class="line">Description     : A computer display server protocol</span><br><span class="line">Architecture    : x86_64</span><br><span class="line">URL             : https://wayland.freedesktop.org/</span><br><span class="line">Licenses        : MIT</span><br><span class="line"><span class="comment"># ... etc.</span></span><br></pre></td></tr></table></figure><p>幸运的是，Linux 生态系统向 Wayland 的过渡在这些年里一直在 <a href="https://arewewaylandyet.com/">稳步向前推进</a>。<br>主要的 GUI 框架如 GTK 和 QT 完全支持它。<br><a href="https://www.omgubuntu.co.uk/2021/01/ubuntu-21-04-will-use-wayland-by-default">Ubuntu 21.04 将默认使用 Wayland 运行</a>。<br>但我们可以不需要等待主要发行版的行动：今天就可以直接使用 Wayland。</p><p>你应该知道，有一些主要的应用程序并不（或不会 <a href="https://github.com/xmonad/xmonad/issues/38">或不能</a>）支持 Wayland。<br>像这样的程序仍然可以通过一个名为 <code>XWayland</code> 的独立的 <code>X</code> 实例在 Wayland 环境中运行。<br>这意味着向 Wayland 的过渡可以是渐进的：你不会无法使用旧的应用程序。</p><blockquote><p>译者注：</p><p><code>XWayland</code> 本质上时 XOrg 的一个 Fork，可以在 Wayland 环境中使用 XOrg</p></blockquote><p>在我们继续之前，还有一个好消息：<br>在 Wayland 中，你不需要像 <a href="https://github.com/yshui/picom">picom</a> 或 <a href="https://github.com/yshui/picom">compiz</a> 这样独立于窗口管理器的合成器程序。</p><blockquote><p>解释一下？更少的活动组件，更少的配置管理，就可以实现终端透明化</p></blockquote><h2 id="前提条件"><a href="#前提条件" class="headerlink" title="前提条件"></a>前提条件</h2><h3 id="软件包"><a href="#软件包" class="headerlink" title="软件包"></a>软件包</h3><p>在 Arch Linux 上，运行下面的命令来安装本指南主要部分和 Wayland 兼容性所需的环境。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> pacman -S \</span><br><span class="line">  sway alacritty waybar wofi \</span><br><span class="line">  xorg-xwayland xorg-xlsclients qt5-wayland glfw-wayland</span><br></pre></td></tr></table></figure><p><code>qt5-wayland</code> 和 <code>glfw-wayland</code> 分别为 QT5 和 <a href="https://www.glfw.org/">GLFW</a> 提供 Wayland 兼容 API 。<br><code>xlsclients</code> 在下文中解释。</p><h3 id="用-xlsclients-检测-XWayland"><a href="#用-xlsclients-检测-XWayland" class="headerlink" title="用 xlsclients 检测 XWayland"></a>用 <code>xlsclients</code> 检测 XWayland</h3><p>要获得当前通过 XWayland 运行的所有窗口的列表，请使用 <code>xlsclients</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&gt; xlsclients</span><br><span class="line">archlinux  discord</span><br><span class="line">archlinux  krita</span><br><span class="line">archlinux  steam</span><br></pre></td></tr></table></figure><p>这样，你就可以用各种应用程序快速测试你的 Wayland 配置。</p><h2 id="Sway"><a href="#Sway" class="headerlink" title="Sway"></a>Sway</h2><p>Sway 是一个平铺式窗口管理器，是一个在 Wayland 环境下 <a href="https://i3wm.org/">i3</a> 的替代品。<br>和它的 i3 一样，它也是用 C 语言编写的，因此速度非常快，资源开销也很小。<br>尽管 Sway 可以按原样读取 i3 的配置（即你的 <code>/home/you/.config/i3/config</code>），但我建议从一个默认配置开始，然后在你需要时复制特定的绑定。</p><p>首先，复制 Sway 的配置模板。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> ~/.config</span><br><span class="line"><span class="built_in">mkdir</span> sway</span><br><span class="line"><span class="built_in">cd</span> sway</span><br><span class="line"><span class="built_in">cp</span> /etc/sway/config .</span><br></pre></td></tr></table></figure><p>现在退出你所处的任何 桌面环境（DE） &#x2F; 窗口管理器（WM），并回到你的基本登录终端。在这里，运行 <code>sway</code>，Sway 就会启动。恭喜你，你正在运行 Wayland!</p><p>好了，我们不要庆祝的太早。你可能已经习惯了在 <code>.xinitrc</code> 中加入 <code>exec i3</code> 这样的行，然后用 <code>startx</code> 启动 X。现在不一样了!<br>从这里开始，一切都发生在我们的 Sway 配置中。说到这，下面是一些重点。</p><h3 id="Sway-配置和附加功能"><a href="#Sway-配置和附加功能" class="headerlink" title="Sway 配置和附加功能"></a>Sway 配置和附加功能</h3><p><a href="https://github.com/fosskers/dotfiles/blob/master/.config/sway/config">这里是我的全部 Sway 配置</a>。<br>其他方面，Sway 主要的文档是在它的 man pages 中记录的。如果有疑问，请先查看它们。<br>如果不行，你也可以参考 <a href="https://github.com/swaywm/sway/wiki">Sway Wiki</a>。</p><p>这里有一些有用的绑定，你马上就会需要，但以后可以自由改变。</p><ul><li>重新加载 Sway。<code>Super+Shift+c</code> (不会关闭正在运行的程序)</li><li>退出 Sway。<code>Super+Shift+e</code></li><li>打开一个终端。<code>Super+Return</code></li><li>打开一个程序。<code>Super+d</code></li></ul><h4 id="显示器设置"><a href="#显示器设置" class="headerlink" title="显示器设置"></a>显示器设置</h4><p>我有两台显示器：我的笔记本电脑在我的左边，而我主显示器在我的正前方。要让我的鼠标在显示器边界上自然移动，需要做以下工作。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">output eDP-1 mode 2560x1440 position 0,0 scale 2</span><br><span class="line">output HDMI-A-2 mode 1920x1080 position 1280,0</span><br></pre></td></tr></table></figure><p>在确定第二个显示器（第二行中的 <code>1280</code>）使用的适当偏移量时，涉及到一些数学问题。更多信息见 <code>man sway-output</code>。<br>你可以使用 <code>swaymsg -t get_outputs</code> 来查看你所有显示器的正式名称和可用分辨率。</p><h4 id="Gaps"><a href="#Gaps" class="headerlink" title="Gaps"></a>Gaps</h4><p>i3-gaps 是一个流行的 i3 变种，允许窗口之间有间隙。幸运的是，这个功能已经包含在 Sway 中，可以通过在你的 Sway 配置中添加以下内容来激活。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># A 10-pixel border around every window.</span></span><br><span class="line">gaps inner 10</span><br><span class="line"></span><br><span class="line"><span class="comment"># Removes the title bar of each window.</span></span><br><span class="line">default_border pixel 3</span><br></pre></td></tr></table></figure><p>你需要退出 Sway 一次，然后从你的登录终端重新运行它，这样的改变才会生效。</p><h4 id="随机壁纸"><a href="#随机壁纸" class="headerlink" title="随机壁纸"></a>随机壁纸</h4><p>虽然还没有整合到我自己的配置中，但 <a href="https://github.com/fosskers/rs-setwall">setwall</a> 可以用来设置一个随机的背景图片。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">setwall random ~/Pictures/backgrounds/ -c sway</span><br></pre></td></tr></table></figure><blockquote><p>译者注：</p><p><code>setwall</code> 是这篇文章的原作者自己写的工具</p></blockquote><h3 id="Alacritty"><a href="#Alacritty" class="headerlink" title="Alacritty"></a>Alacritty</h3><p>Alacritty 是一个强大的现代终端模拟器，具有理想的默认值。当用 <code>Super+Return</code> 打开一个新的终端时，它也是 Sway 的默认快捷键。<br>我使用 <code>urxvt</code> 多年，但最近切换到 Alacritty 后，我遇到的一些问题就消失了。</p><p>我对 Alacritty 的默认配置的唯一改变是背景的不透明度。在 <code>/home/you/.config/alacritty/alacritty.yml</code> 中。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">background_opacity: 0.8</span><br></pre></td></tr></table></figure><p>看, 透明的终端!</p><blockquote><p>译者注： 新版本的 Alacritty 推荐设置</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">window:</span><br><span class="line">  opacity: 0.8</span><br></pre></td></tr></table></figure></blockquote><h3 id="Waybar"><a href="#Waybar" class="headerlink" title="Waybar"></a>Waybar</h3><p>Sway 的默认状态栏很好，但 Waybar 提供了更多的自定义功能。它还能在多个显示器上 “正常工作”，而不像 Polybar 那样需要自定义脚本。</p><p>要使用 Waybar 而不是默认 <code>bar</code> ，请注释掉你的 Sway 配置中靠近结尾的 <code>bar</code> 部分，并在其位置上添加以下内容。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">bar &#123;</span><br><span class="line">  swaybar_command waybar</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://github.com/Alexays/Waybar/wiki/Examples">Waybar Wiki</a> 有很多配置的例子，<br><a href="https://github.com/fosskers/dotfiles/blob/master/.config/waybar/config">这里是我自己的配置</a>，<br>以及 <a href="https://github.com/fosskers/dotfiles/blob/master/.config/waybar/style.css">自定义的 CSS 样式</a>。<br>bar 本身是透明的，右上方的小部件和托盘看起来像这样。</p><p><img src="/assets/img/translate_wayland/waybar-top-right.png" alt="waybar-top-right"></p><p>在调整了你的 Waybar 配置后，像往常一样通过 <code>Super+Shift+c</code> 刷新 Sway，就可以刷新你的 Waybar 了。</p><h3 id="Wofi"><a href="#Wofi" class="headerlink" title="Wofi"></a>Wofi</h3><p>默认情况下，Sway 使用 <code>dmenu</code> 来打开程序，但令人惊讶的是，它的用户界面在 XWayland 中运行。<br>有 <a href="https://github.com/swaywm/sway/wiki/i3-Migration-Guide">许多可用的替代品</a>，我选择了 <a href="https://hg.sr.ht/~scoopta/wofi">Wofi</a>。</p><p><img src="/assets/img/translate_wayland/wofi.jpg" alt="wofi"></p><p>这是 <a href="https://github.com/fosskers/dotfiles/blob/master/.config/wofi/style.css">我设置的外观</a>，<br>但由于它都是 CSS，所以你可以 <a href="https://cloudninja.pw/docs/wofi.html">自由地进行试验!</a></p><p>请注意，你需要在你的 Sway 配置中加入以下内容。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">set $menu wofi --show=drun --lines=5 --prompt=&quot;&quot;</span><br></pre></td></tr></table></figure><p>这有几种不同的提示模式。<br><code>drun</code> 只匹配并显示那些在你的机器上有 <code>Desktop</code> 条目的程序（就是有 <code>.desktop</code> 文件的程序），而不是你的 <code>PATH</code> 上的所有程序。<br>事实上，不这样做会产生性能问题，是一个已知的问题。</p><h2 id="主要的应用程序"><a href="#主要的应用程序" class="headerlink" title="主要的应用程序"></a>主要的应用程序</h2><p>大多数应用程序，如果在 GTK 或 QT 上运行，都有自动的 Wayland 支持，不需要进一步配置。一些特定的程序需要进行调整，我们将在下面讨论。</p><p>目前有一些资源要求你需要设置 GTK 和 QT 的特定环境变量才能使用 Wayland，但 <strong>我发现这不是真的</strong>。</p><h3 id="Firefox"><a href="#Firefox" class="headerlink" title="Firefox"></a>Firefox</h3><p>在 Firefox 的 <code>about:support</code> 页面上有一个名为 <em>Window Protocol</em> 的字段，告诉我们它是通过哪个协议运行的。<br>如果还在 X11 上，这个字段就会显示 <code>X11</code>。如果通过 Sway 而没有下面的调整，你应该看到 <code>xwayland</code>。<br>用 <code>xlsclients</code> 进行的快速测试也会发现，Firefox 还没有通过 Wayland 原生运行。让我们来解决这个问题。</p><p>将 <code>MOZ_ENABLE_WAYLAND</code> 环境变量设为 <code>1</code> ，我在我的 Fish 配置中设置了以下内容（其他 shell 的用户也需要类似的内容）。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">set -x MOZ_ENABLE_WAYLAND 1</span><br></pre></td></tr></table></figure><p><strong>退出 Sway 并完全注销一次。</strong> 一旦重新登录并重新打开 Sway，这个变量的变化应该已经传播到了所有重要的地方。<br>现在，如果你通过 Wofi 再次打开 Firefox，并检查 <code>about:support</code>，你应该发现。</p><p><img src="/assets/img/translate_wayland/firefox-wayland.png" alt="firefox-wayland"></p><h3 id="Chromium"><a href="#Chromium" class="headerlink" title="Chromium"></a>Chromium</h3><p>Chromium 的转换要简单一些。在 <code>/home/you/.config/chromium-flags.conf</code> 中，添加以下几行。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">--enable-features=UseOzonePlatform</span><br><span class="line">--ozone-platform=wayland</span><br></pre></td></tr></table></figure><p>重新启动 Chromium，这样就可以了。你可以用 <code>xlsclients</code> 来确认。</p><h3 id="Emacs"><a href="#Emacs" class="headerlink" title="Emacs"></a>Emacs</h3><p>是的，Emacs 可以纯粹地在 Wayland 中运行。你们中的一些人可能会说。</p><blockquote><p>但是 Emacs 并不是一个真正的 GTK 应用程序!</p></blockquote><p>是的，这曾经是真的。<a href="https://lwn.net/Articles/843896/">从 2021 年初开始</a>，Emacs 可以用 “纯 GTK” 的内部结构构建，使其完全兼容 Wayland。<br>这项功能将在 Emacs 28 中实现（截至本文撰写时尚未发布），但幸运的是，<br><a href="https://aur.archlinux.org/packages/emacs-gcc-wayland-devel-bin/">有一个 AUR 包</a> 可以跟踪 Wayland 开发分支，并提供一个预构建的二进制文件。<br>我们可以用 <a href="https://github.com/fosskers/aura">AURA</a> 这样的工具来安装它。</p><blockquote><p>译者注：</p><p>AURA 也是这篇文章的原作者写的，中文社区的用户可能更习惯使用 <code>yay</code></p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> aura -Axa emacs-gcc-wayland-devel-bin</span><br></pre></td></tr></table></figure><p>注意，这个软件包 <code>Provides: emacs</code>，所以它将取代你所安装的任何其他 Emacs 软件包。</p><h3 id="Steam-and-Gaming"><a href="#Steam-and-Gaming" class="headerlink" title="Steam and Gaming"></a>Steam and Gaming</h3><p>像 <em>Among Us</em> 这样的 <a href="https://www.protondb.com/">Proton games</a> 可以按原样运行，<br>因为它们在高度修改的 Wine&#x2F;dependency 环境中运行，而这种环境对每个游戏都是已知的。<br><em>Among Us</em> 对 Sway 中的窗口大小调整和重新定位反应良好。</p><p>对于像 <em>Half-life</em> (old)、 <em>Trine 2</em> (graphics heavy) 和 <em>Tabletop Simulator</em>（modern toolchain）这样的原生游戏，<br>我不得不将环境变量 <code>SDL_VIDEODRIVER</code> 设为 <code>x11</code>。否则它们就不能正常启动。</p><p>来自 Arch Wiki：</p><blockquote><p>注意: 许多专有游戏都捆绑了旧版本的 SDL，它们不支持 Wayland，如果你设置 <code>SDL_VIDEODRIVER=wayland</code>，可能会完全崩溃。</p></blockquote><p>甚至 <em>Stellaris</em> 也需要 x11 才能工作。</p><p>如果你不想把所有 SDL 的使用强制到 X11，你也不必这样做。Steam 允许我们为每个游戏设置特定的环境变量。<br>要设置这个，右键单击一个游戏，并访问其 <code>Properties</code>。在 <code>GENERAL &gt; LAUNCH OPTIONS</code> 中，输入以下内容，你的游戏应该可以运行了。</p><p><img src="/assets/img/translate_wayland/steam-sdl-override.png" alt="steam-sdl-override"></p><p>所以重申一下，这里是我在 <code>Fish</code> 中设置的环境变量。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">set -x SDL_VIDEODRIVER &#x27;wayland&#x27;</span><br></pre></td></tr></table></figure><p>而我在 Steam 中根据具体情况将其覆盖为 <code>x11</code>。</p><h3 id="Signal"><a href="#Signal" class="headerlink" title="Signal"></a>Signal</h3><p>在 2021 年 5 月初，Signal 发布了 <code>5.1.0</code> 版本，该版本使用与 Wayland 兼容的 Electron。<br>不幸的是，Arch 软件包 <code>signal-desktop</code> 还没有默认在这种模式下运行，所以手动激活是必要的。在命令行中。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">signal-desktop --use-tray-icon --enable-features=UseOzonePlatform --ozon-platform=wayland</span><br></pre></td></tr></table></figure><p>或者如果你通过启动器运行 Signal，我们可以编辑软件包提供的 <code>.desktop</code> 文件来尊重这些选项。<br>在 <code>/usr/share/applications/signal-desktop.desktop</code> 中，修改 <code>Exec</code> 一行，使其成为以下内容。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Exec=signal-desktop --use-tray-icon --enable-features=UseOzonePlatform --ozone-platform=wayland -- %u</span><br></pre></td></tr></table></figure><p>类似的策略也适用于其他至少使用 12 版的 Electron 应用程序。</p><h2 id="Other-Settings"><a href="#Other-Settings" class="headerlink" title="Other Settings"></a>Other Settings</h2><p>如果这里的章节对你不适用，请随意跳过。</p><h3 id="Keyboard-Layouts"><a href="#Keyboard-Layouts" class="headerlink" title="Keyboard Layouts"></a>Keyboard Layouts</h3><p>我在打字时使用 Colemak 布局，所以我在我的 Sway 配置里有以下内容。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">input * &#123;</span><br><span class="line">  xkb_layout &quot;us&quot;</span><br><span class="line">  xkb_variant &quot;colemak&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>不幸的是，似乎有 <a href="https://github.com/swaywm/sway/issues/4664">一个奇怪的 Bug</a>，在某些窗口中布局会突然切换回 qwerty。<br>我注意到以下症状：当一个终端被打开时，最左边的 XWayland 窗口会切换回 qwerty。<br>我发现有两个办法可以解决这个问题。</p><ul><li>尽可能多地使用纯 Wayland 应用程序，或者。</li><li>安装一个IME（Input Method Editor），例如用于输入 non-ASCII 语言（见下文）。</li></ul><h3 id="日语输入法"><a href="#日语输入法" class="headerlink" title="日语输入法"></a>日语输入法</h3><p>Sway 已经非常接近对切换输入法的一流支持（见<br><a href="https://github.com/swaywm/sway/pull/4740#issuecomment-787578644">Sway#4740</a>、<br><a href="https://github.com/swaywm/sway/pull/5890">Sway#5890</a><br>和 <a href="https://github.com/swaywm/sway/pull/4932">Sway#4932</a>）。<br>目前，这里有一个通过 <code>dbus</code> 工作的设置，允许我们在 <strong>除 Alacritty</strong> 之外的所有 Wayland 和 XWayland 窗口中改变方法和输入日文。</p><p>首先，安装这些软件包。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> pacman -S fcitx5 fcitx5-configtool \</span><br><span class="line">  fcitx5-gtk fcitx5-mozc fcitx5-qt</span><br></pre></td></tr></table></figure><p>然后在 <code>/etc/environment</code> 中添加以下内容：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">GTK_IM_MODULE=fcitx</span><br><span class="line">QT_IM_MODULE=fcitx</span><br><span class="line">XMODIFIERS=@im=fcitx</span><br></pre></td></tr></table></figure><p>然后把这个放到你的 Sway 配置中：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">exec_always fcitx5 -d --replace</span><br></pre></td></tr></table></figure><p><strong>现在重新启动你的电脑。</strong></p><p>希望你现在能在你的 Waybar 托盘中看到一个键盘图标。要配置 <code>fcitx5</code>，请打开 <code>fcitx5-configtool</code>。这是我的设置。</p><p><img src="/assets/img/translate_wayland/fcitx5.png" alt="fcitx5"></p><p>你会看到，我特别将我的英文键盘设置为 Colemak，并从右边的列表中添加了 <code>Mozc</code>。<br>勾选 <code>Global Options</code> 标签，设置你的方法切换键绑定。<br>之后，点击 <code>Apply</code>，你现在应该可以切换输入法并输入日语了。<br>如果键盘绑定不起作用，你也可以通过点击 Waybar 托盘上的图标来切换方法。</p><blockquote><p>译者注：</p><p>对于中文用户，就是把 <code>Mozc</code> 换成 <code>Rime</code></p></blockquote><h3 id="屏幕共享"><a href="#屏幕共享" class="headerlink" title="屏幕共享"></a>屏幕共享</h3><p>在 Firefox 和 Chromium 中，通过 Pipewire 和一些辅助包可以实现屏幕共享，尽管目前我们只能共享整个屏幕，而不是单个窗口。要继续进行，首先安装以下软件包。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> pacman -S xdg-desktop-portal-wlr libpipewire02</span><br></pre></td></tr></table></figure><p>后者只对 Chromium 是必要的。 <strong>现在重新启动你的电脑。</strong></p><p>让我们先用 Mozilla 的 <a href="https://mozilla.github.io/webrtc-landing/gum_test.html">gum test page</a> 测试一下 Firefox。<br>当浏览器提示你选择窗口时，选择 <em>Use operating system settings:</em></p><p><img src="/assets/img/translate_wayland/firefox-screen-select.jpg" alt="firefox-screen-select"></p><p>你会注意到你的光标发生了变化；<code>xdg-desktop-portal-wlr</code> 正在期待你选择一个显示器来共享。点击一个，屏幕共享应该开始。</p><p>对于 Chromium，我们需要激活一个功能标志，让 Chromium 与 Pipewire 对话。<br>首先访问 <code>chrome://flags</code>，然后找到并启用 <code>WebRTC PipeWire support</code>。这就是了!</p><p>如果你在使用这些浏览器时遇到问题，请查看 <a href="https://github.com/emersion/xdg-desktop-portal-wlr/wiki/FAQ">XDPW FAQ</a>。</p><h2 id="XWayland-和不兼容"><a href="#XWayland-和不兼容" class="headerlink" title="XWayland 和不兼容"></a>XWayland 和不兼容</h2><p>您还知道其他不兼容的情况吗？请让我知道。</p><h3 id="Krita"><a href="#Krita" class="headerlink" title="Krita"></a>Krita</h3><p>数字艺术程序 <a href="https://krita.org/">Krita</a> 是一个在 QT5 中运行的很棒的应用程序，但由于某些硬件支持不成熟的原因（对于手写板等），<br><a href="https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=955730">它不支持 Wayland</a>，因此总是在 XWayland 中运行。</p><h3 id="Electron-Apps"><a href="#Electron-Apps" class="headerlink" title="Electron Apps"></a>Electron Apps</h3><p>从 2021 年 5 月起，由于 Signal 和 VSCode 升级到 Electron 12，它们现在可以在 Wayland 中运行。</p><p>而其他 Electron Apps，如 Discord 和 Slack，则必须在 XWayland 中运行，直到他们能够升级。</p><h2 id="社区提示"><a href="#社区提示" class="headerlink" title="社区提示"></a>社区提示</h2><h3 id="KWin-用户"><a href="#KWin-用户" class="headerlink" title="KWin 用户"></a>KWin 用户</h3><p>感谢 <strong>flying-sheep</strong> 提供的这个提示。</p><blockquote><p>对于使用 KWin 的人来说：你可以显示一个窗口，帮助你使用识别 XWayland 窗口。</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">qdbus org.kde.KWin /KWin org.kde.KWin.showDebugConsole</span><br></pre></td></tr></table></figure><h3 id="Polkit"><a href="#Polkit" class="headerlink" title="Polkit"></a>Polkit</h3><p>感谢 Aaron Wiedemer 提出的以下建议。</p><blockquote><p>一些应用程序有时需要权限，例如，软件管理器需要权限来启动更新，但只是搜索软件包不需要额外权限。<br>这些应用程序会弹出一个小盒子，要求输入密码。这需要一个不由 sway 启动的守护程序，所以我们需要用我们的 sway 配置自动启动一个。</p></blockquote><p><a href="https://wiki.archlinux.org/title/Polkit">Polkit 客户端有很多选择</a>。<br>例如，<code>polkit-gnome</code> 没有依赖性，可以通过以下方式在 <code>sway</code> 中启动：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">exec_always /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1</span><br></pre></td></tr></table></figure><h2 id="Resources"><a href="#Resources" class="headerlink" title="Resources"></a>Resources</h2><ul><li><a href="https://github.com/swaywm/sway/wiki/Running-programs-natively-under-wayland">Sway: Running GUI Programs under Wayland</a></li><li><a href="https://github.com/swaywm/sway/wiki/i3-Migration-Guide">Sway: i3 Migration Guide</a></li><li><a href="https://github.com/Alexays/Waybar">Sway: Waybar</a></li><li><a href="https://hg.sr.ht/~scoopta/wofi">Sway: Wofi launcher</a></li><li><a href="https://wiki.archlinux.org/index.php/Wayland">Arch Wiki: Wayland</a></li><li><a href="https://arewewaylandyet.com/">Are We Wayland Yet?</a></li><li><a href="https://hugo.barrera.io/journal/2020/06/14/zoom-screensharing-on-archlinux/">Blog: Zoom Screen Sharing</a></li></ul>]]></content>
    
    
    <summary type="html">本文是一份在 Arch Linux 上实现完整的 Wayland 设置的指南</summary>
    
    
    
    <category term="linux" scheme="https://a-wing.top/categories/linux/"/>
    
    
    <category term="linux" scheme="https://a-wing.top/tags/linux/"/>
    
    <category term="archlinux" scheme="https://a-wing.top/tags/archlinux/"/>
    
    <category term="wayland" scheme="https://a-wing.top/tags/wayland/"/>
    
    <category term="translate" scheme="https://a-wing.top/tags/translate/"/>
    
  </entry>
  
  <entry>
    <title>2021 年的总结</title>
    <link href="https://a-wing.top/self/2022/01/01/2021"/>
    <id>https://a-wing.top/self/2022/01/01/2021</id>
    <published>2022-01-01T00:00:00.000Z</published>
    <updated>2022-01-01T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>今年最大的变化是换了工作，离开了陪伴我三年的  <a href="https://sb.im/">草莓</a> 。去了互联网公司</p><h2 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h2><p>换了新工作，在家里 Gap 了半年。每天都在思考：</p><blockquote><p>我是谁，我在哪，我在做什么</p></blockquote><p>在家里总结之前的工作，写了一个关于无人机系列的文章（共七篇，每篇大概 5000 字），这些知识给需要的人吧，我估计以后用不到了，草莓也已经放弃了自研无人机，全面转向和大疆合作</p><p>我在草莓做的的东西类似于 “大疆司空”。当然最大矛盾在于，软件和硬件之间。作为无人机厂商希望我只支持自家的产品，包括各种在出货的全部机型。还有定制版</p><p>而我希望做的是一个通用型远程机器人的控制平台，不受各种厂家机型的限制，支持各种挂载</p><p>这就是谁说的算的问题，靠硬件出货赚钱，那软件只能成为硬件的附属品。和硬件的生命周期一样，一起换代。但这个软件的特殊性又要依赖硬件，又没有办法做成中立的平台。这就变成了现有鸡还是先有蛋问题。当然，结果显而易见，谁赚钱，谁说的算</p><p>另一个原因是草莓的客户，买这个东西唯一用途就是给领导做演示。所以希望我们坐炫酷的界面，来满足演示目的。我也并不想给大客户做定制版本</p><blockquote><p>我是想站着，还把钱挣了</p><p>————《让子弹飞》</p></blockquote><h2 id="生活"><a href="#生活" class="headerlink" title="生活"></a>生活</h2><p>等了一年多的手办终于到了</p><p><img src="/assets/img/2021/img_3507.jpg" alt="img_3507.jpg"></p><h3 id="notion"><a href="#notion" class="headerlink" title="notion"></a>notion</h3><p>使用 notion 作为我日常笔记工具，试用一下感觉不错，然后就去某宝上充了 10 年的 Pro</p><p>notion 真好用，我吹爆。（不过，请注意做好备份。以免 notion 服务器挂掉导致没法使用笔记）</p><h3 id="滑雪"><a href="#滑雪" class="headerlink" title="滑雪"></a>滑雪</h3><p>把今年滑雪的拍的视频剪辑，做成了个视频</p><p>也顺便学习了一下 Final Cut Pro 的用法</p><p><img src="/assets/img/2021/screenshot-fcpx.png" alt="Final Cut Pro X"></p><p>最后的成品</p><iframe width="560" height="315" src="//player.bilibili.com/player.html?aid=247597135&bvid=BV1hv41177BQ&cid=323437177&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe><h3 id="跑步"><a href="#跑步" class="headerlink" title="跑步"></a>跑步</h3><p>很遗憾，今年又是一场马拉松都没跑。不是未中签就是因为疫情取消</p><p>跑的最多那个月公里数可以达到 150km 了。希望明年月公里数可以突破 200km</p><h3 id="初中同学的婚礼"><a href="#初中同学的婚礼" class="headerlink" title="初中同学的婚礼"></a>初中同学的婚礼</h3><p>参加了初中同学的婚礼（新郎新娘都是我同学）。同桌的你结婚了（抱歉，新郎是我同桌</p><p><strong>以免有人受到伤害，就不放照片了</strong></p><h3 id="塞尔达传说-旷野之息"><a href="#塞尔达传说-旷野之息" class="headerlink" title="塞尔达传说-旷野之息"></a>塞尔达传说-旷野之息</h3><p>十月一假期买了个二手 Switch 。每天都在打塞尔达。后来由于没法在这个假期内打通关，就直接不玩了，把机器卖掉了</p><p>后来又和同事聊到塞尔达。这件事情越想越气，然后又去买了一个二手  Switch。然后查了很多攻略，两天打通关了。（最后连续打六个 boss 真的爽）</p><p>然后开了个大师档，现在唯一的乐趣就是打“黄金人马”</p><p>不得不说这个游戏机设计是真的不错，游戏本身交互也非常不错</p><h2 id="旅行"><a href="#旅行" class="headerlink" title="旅行"></a>旅行</h2><h3 id="长白山天池"><a href="#长白山天池" class="headerlink" title="长白山天池"></a>长白山天池</h3><p>长白山天池有四个坡可以登上，三个在国内</p><ul><li>北坡（二道白河镇）</li><li>西坡（抚松县，长白山机场就在这）松江河镇</li><li>南坡（长白县，目前处于关闭状态，在修高铁，通车之后应该就会开放吧）</li><li>东坡（在朝鲜境内）</li></ul><p>我最开始准备去南坡，发现已经关闭了。然后又去西坡，然后由于路还没有清理出来，没法去天池</p><p>最后只能去北坡（二道白河镇）</p><p>北坡，车可以直接开到山上，上天池要额外花 80 的越野车票</p><p>天池，瀑布，温泉，小天池，绿渊潭，地下森林</p><p>淡季请去北坡，北坡开的几率大一些</p><p>路线：北坡景区入口——天池——瀑布——聚龙泉——小天池——绿渊潭——地下森林——返程</p><p>地下森林是个隐藏的好地方，大部分人都走几百米就返航，要在深林里走 1km 才能到真正的地下森林。由于照片的效果不好看，就不放了</p><p>地下森林差不多要一个小时的，我花了 45分钟 才逛完</p><p><img src="/assets/img/2021/changbaishan/img_3136.jpg" alt="img_3136.jpg"></p><p>天池</p><p><img src="/assets/img/2021/changbaishan/img_3151.jpg" alt="img_3151.jpg"></p><p>在天池边上往山下看</p><p><img src="/assets/img/2021/changbaishan/img_3160.jpg" alt="img_3160.jpg"></p><p>绿渊潭</p><p><img src="/assets/img/2021/changbaishan/img_3169.jpg" alt="img_3169.jpg"></p><p>聚龙泉：周围都是积雪，只有这一片的地下温泉还冒着热气</p><p><img src="/assets/img/2021/changbaishan/img_3186.jpg" alt="img_3186.jpg"></p><p>回程的时候路过梅河口，这张实在是太漂亮了</p><p><img src="/assets/img/2021/changbaishan/img_3291.jpg" alt="img_3291.jpg"></p><h3 id="深圳周边"><a href="#深圳周边" class="headerlink" title="深圳周边"></a>深圳周边</h3><p>南头古城</p><p><img src="/assets/img/2021/img_3359.jpg" alt="img_3359.jpg"></p><p>梧桐山其中的一条路（忘记是哪条了），被雾包围的感觉</p><p><img src="/assets/img/2021/img_3387.jpg" alt="img_3387.jpg"></p><p>塘朗山，那个雾蒙蒙的地方是南山正在下雨</p><p><img src="/assets/img/2021/img_3407.jpg" alt="img_3407.jpg"></p><p>福永 立新湖</p><p><img src="/assets/img/2021/img_3572.jpg" alt="img_3572.jpg"></p><h2 id="学习"><a href="#学习" class="headerlink" title="学习"></a>学习</h2><h3 id="leetcode"><a href="#leetcode" class="headerlink" title="leetcode"></a>leetcode</h3><p>leetcode 每天 5 道题，写完才能吃饭</p><p>一共才 150 道题，<del>主要是找到工作就没有动力刷了</del></p><p><img src="/assets/img/2021/screenshot-leetcode.png" alt="leetcode"></p><h3 id="读书"><a href="#读书" class="headerlink" title="读书"></a>读书</h3><ul><li>人月神话</li><li>TCP&#x2F;IP 详解（1-3 卷）</li><li>WebRTC 音视频实时互动技术（今年新出的那本，我想写的和这本的内容基本一致，看到这本书。我就放弃了想写书的念头）</li><li>还有关于图像处理的书</li></ul><h2 id="设备"><a href="#设备" class="headerlink" title="设备"></a>设备</h2><h3 id="台式机"><a href="#台式机" class="headerlink" title="台式机"></a>台式机</h3><p>淘了个二手的台式机 i9-10900K + z490 主板</p><p>主机 5500 + 显示器 330 + 显卡 300 + ikbc 键盘 300</p><h3 id="Pi400"><a href="#Pi400" class="headerlink" title="Pi400"></a>Pi400</h3><p>入手一个 pi400 。感觉一般，还不如买 pi4 那</p><p><img src="/assets/img/2021/img_3480.jpg" alt="img_3480.jpg"></p><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><p>买了个新旁路由 EasePi（KoolShare 那群人的新产品）。换掉了之前的 ac68u 。换成了ASUS RT-ax86u 。终于有了第一台 ax 协议的设备</p><h2 id="技术"><a href="#技术" class="headerlink" title="技术"></a>技术</h2><h3 id="视频剪辑-图片处理"><a href="#视频剪辑-图片处理" class="headerlink" title="视频剪辑 &amp;&amp; 图片处理"></a>视频剪辑 &amp;&amp; 图片处理</h3><p>学了一些完全不相关的东西技术，感觉很不错</p><p>下次争取学习一下 3D 和音频处理</p><h3 id="编程语言"><a href="#编程语言" class="headerlink" title="编程语言"></a>编程语言</h3><p>今年比较遗憾没有学新的编程语言，<del>react 算不，当然不算，typescript 算不，好像也不能算</del></p><p>学会了使用 React 函数式写法，</p><p>下次我一定要把 Rust 入门</p><h3 id="WebRTC"><a href="#WebRTC" class="headerlink" title="WebRTC"></a>WebRTC</h3><p>WebRTC 今年正式成为标准，我本来想写本书，但是看到有人和我想到了一样的事。看到他的书出版了之后，我就不想再写了</p><h3 id="filegogo-项目"><a href="#filegogo-项目" class="headerlink" title="filegogo 项目"></a>filegogo 项目</h3><p>值得一提的是，我把 <a href="https://github.com/a-wing/filegogo/">filegogo</a> 全部重写了。<br>支持了命令行和浏览器文件 p2p 互传。把信令部分单独拆了出来 <a href="https://github.com/a-wing/lightcable">lightcable</a>，这个东西似乎可以当成一个独立的库来使用</p><div class="twitter-wrapper"><blockquote class="twitter-tweet"><a href="https://twitter.com/_a_wing/status/1452210912585203716"></a></blockquote></div><script async defer src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><p>下个版本优化一下传输协议<del>（下个版本明年 0404 时发布）</del></p><h3 id="Docker-Kubernetes"><a href="#Docker-Kubernetes" class="headerlink" title="Docker &amp;&amp; Kubernetes"></a>Docker &amp;&amp; Kubernetes</h3><p>今年被动学习了 Docker &amp;&amp; Kubernetes 。这个东西今年反复在我身边出现</p><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>希望下一年多搞一点事情</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;今年最大的变化是换了工作，离开了陪伴我三年的  &lt;a href=&quot;https://sb.im/&quot;&gt;草莓&lt;/a&gt; 。去了互联网公司&lt;/p&gt;
&lt;h2 id=&quot;工作&quot;&gt;&lt;a href=&quot;#工作&quot; class=&quot;headerlink&quot; title=&quot;工作&quot;&gt;&lt;/a&gt;工作&lt;/h2&gt;&lt;</summary>
      
    
    
    
    <category term="self" scheme="https://a-wing.top/categories/self/"/>
    
    
    <category term="self" scheme="https://a-wing.top/tags/self/"/>
    
  </entry>
  
  <entry>
    <title>科学的检测用户浏览器</title>
    <link href="https://a-wing.top/browser/2021/08/21/user-agent"/>
    <id>https://a-wing.top/browser/2021/08/21/user-agent</id>
    <published>2021-08-21T22:00:00.000Z</published>
    <updated>2021-08-21T22:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本篇文章已入选 ShowMeBug 公司官方发布的技术文章，授权的很多地方转载</p><p>本文放在这里做个归档。（这才是真正的原稿）</p><p>另外，本文后发布的，内容上会有些差异（下次把首发放在自己的 Blog）</p></blockquote><h2 id="用户的特殊浏览器"><a href="#用户的特殊浏览器" class="headerlink" title="用户的特殊浏览器"></a>用户的特殊浏览器</h2><blockquote><p>经常有用户使用奇怪的浏览器，来使用我们的产品，然后出问题。</p></blockquote><p>因为产品上的音视频使用了浏览器的 WebRTC 特性，所以对浏览器的要求就很高</p><p><strong>当然，从原则上来看，是不应该根据 User-Agent 来提供差异化服务的，应该使用另一种方式：<code>feature detection</code></strong></p><p>可以使用 <a href="https://modernizr.com/">modernizr</a> 来进行 <code>feature detection</code></p><p>毕竟 WebRTC 是由浏览器 API 组成，通过探测这些 API 是否存在，来判读是否可以使用 WebRTC</p><p>但是这个不够严谨，WebRTC 是很新的东西，有些浏览器可以支持，但是支持不够完整和稳定性都有问题。</p><blockquote><p><a href="https://www.w3.org/2021/01/pressrelease-webrtc-rec.html">Web Real-Time Communications (WebRTC) transforms the communications landscape; becomes a World Wide Web Consortium (W3C) Recommendation and multiple Internet Engineering Task Force (IETF) standards</a></p><p>WebRTC 改变了通信格局； 成为 W3C 和 IETF <a href="https://www.w3.org/TR/webrtc/">官方标准</a></p><p>———— 2021.01.26</p></blockquote><p>所以我们需要去检测用户到底用了什么浏览器，是如何出现了问题的。一个很通用的办法就是检测 <code>User-Agent</code></p><h3 id="从黑曜石浏览器（HEICORE）说起"><a href="#从黑曜石浏览器（HEICORE）说起" class="headerlink" title="从黑曜石浏览器（HEICORE）说起"></a>从黑曜石浏览器（HEICORE）说起</h3><p>某年的科大的 CTF 要使用黑曜石浏览器去解题。当然，所谓的黑曜石浏览器是不存在的</p><blockquote><p>不得不说，黑曜石浏览器是我用过最好用的浏览器 用户体验极佳</p><p>—— 知乎某用户 <a href="https://www.zhihu.com/question/298070646">如何评价黑曜石浏览器（HEICORE）？</a></p></blockquote><p>通过设置 <code>User-Agent</code> ，把自己伪装成了黑曜石浏览器</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl http://202.38.95.46:12001/ -H <span class="string">&quot;User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) HEICORE/49.1.2623.213 Safari/537.36&quot;</span></span><br></pre></td></tr></table></figure><p>出自<a href="https://github.com/ustclug/hackergame2018-writeups/blob/master/official/heicore/README.md">中国科学技术大学第五届信息安全大赛</a></p><h3 id="User-Agent-是什么"><a href="#User-Agent-是什么" class="headerlink" title="User-Agent 是什么"></a>User-Agent 是什么</h3><p>用户不能直接去互联网上获取信息，需要一个软件去代表用户的行为，这个软件就是 User-Agent （用户代理）</p><p>浏览器就是一种 User-Agent 。用户使用不同的软件去用统一的协议去做相同的事情。</p><p>这也是定义在 http 请求里的，每一条 http 请求一定会携带 User-Agent 头</p><p>网站的服务者可以通过 User-Agent 头来判断用户使用了什么浏览器，当然也可以根据 User-Agent 的内容来提供差异化的服务</p><h3 id="标准语法和历史"><a href="#标准语法和历史" class="headerlink" title="标准语法和历史"></a>标准语法和历史</h3><p>原本 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent">User-Agent 浏览器的语法</a> 是很清晰的</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">User</span>-<span class="title class_">Agent</span>: &lt;product&gt; / &lt;product-version&gt; &lt;comment&gt;</span><br></pre></td></tr></table></figure><p>因为可以根据 User-Agent 的内容来提供差异化的服务，所以当年在浏览器大战时期，浏览器的实现各不相同。<br>当年 Mozilla （Firefox 的前身）浏览器最强的，很多网站都只对 Mozilla 提供高质量的服务，后来有人把自己伪装成了 Mozilla （没错，就是 IE 先开始的）。<br>从此 <code>Mozilla/5.0</code> 就变成了 User-Agent 的第一段</p><p>后来的浏览就在这上面不断扩充，就像今天这样：</p><p>Linux &#x2F; Firefox</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Mozilla/5.0 (X11; Linux x86_64; rv:89.0) Gecko/20100101 Firefox/89.0</span><br></pre></td></tr></table></figure><p>Mac OS &#x2F; Safari</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Mozilla/5.0 (Macintosh; Intel Mac OS X 11_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Safari/605.1.15</span><br></pre></td></tr></table></figure><p>Chromium OS  &#x2F; Chrome</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Mozilla/5.0 (X11; CrOS x86_64 13904.16.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.25 Safari/537.36</span><br></pre></td></tr></table></figure><p>Windows &#x2F; Edge</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4482.0 Safari/537.36 Edg/92.0.874.0</span><br></pre></td></tr></table></figure><p>这就变成了，去识别 User-Agent 变得很困难。目前的识别基本上都是使用正则表达式，配合自己的 User-Agent 库来判断</p><p>这方面的库有很多，对比很多后，这个库是比较全的 <a href="https://www.npmjs.com/package/ua-parser-js">ua-parser-js</a></p><p>目前几乎所有的网站识别浏览器都是 User-Agent 来判断，目前有两个接口：</p><p>前端有浏览器接口：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">window</span>.<span class="property">navigator</span>.<span class="property">userAgent</span></span><br></pre></td></tr></table></figure><p>后端可以通过浏览器的 http request header 来拿到 <code>User-Agent</code></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">user-<span class="attr">agent</span>: <span class="title class_">Mozilla</span>/<span class="number">5.0</span> (<span class="title class_">Macintosh</span>; <span class="title class_">Intel</span> <span class="title class_">Mac</span> <span class="variable constant_">OS</span> X <span class="number">10_15_7</span>) <span class="title class_">AppleWebKit</span>/<span class="number">537.36</span> (<span class="variable constant_">KHTML</span>, like <span class="title class_">Gecko</span>) <span class="title class_">Chrome</span>/<span class="number">92.0</span><span class="number">.4515</span><span class="number">.107</span> <span class="title class_">Safari</span>/<span class="number">537.36</span></span><br></pre></td></tr></table></figure><h3 id="如何使用-User-Agent"><a href="#如何使用-User-Agent" class="headerlink" title="如何使用 User-Agent"></a>如何使用 User-Agent</h3><p>另外，在移动端。因为算力的问题，在有些老旧的处理器上也会出现卡顿的情况</p><p>当然，我们可以在浏览器里跑一下 Benchmark 来判断算力是否够用</p><p>不过，移动端的 User-Agent 会携带处理器信息（可以去查数据库来判断）</p><p>综上，目前所进行的操作是：<strong>检测用户浏览器，给予提示，并在文档页显示支持性列表</strong></p><p>最后，要通过定义的规则生成这样一个表格</p><p><img src="/assets/img/user-agent/browser-list.png" alt="browser-list"></p><p>为了降低未来的维护成本要使用一套数据源，既可以检测，又可以在文档页生成列表</p><p><code>ua-parser-js</code> 的返回结果的数据结构比较科学，直接复用这一数据结构</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">Result</span> &#123;</span><br><span class="line">  <span class="attr">ua</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">browser</span>: &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="built_in">string</span> | <span class="literal">undefined</span>;</span><br><span class="line">    <span class="attr">version</span>: <span class="built_in">string</span> | <span class="literal">undefined</span>;</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="attr">device</span>: &#123;</span><br><span class="line">    <span class="attr">model</span>: <span class="built_in">string</span> | <span class="literal">undefined</span>;</span><br><span class="line">    <span class="attr">type</span>: <span class="built_in">string</span> | <span class="literal">undefined</span>;</span><br><span class="line">    <span class="attr">vendor</span>: <span class="built_in">string</span> | <span class="literal">undefined</span>;</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="attr">engine</span>: &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="built_in">string</span> | <span class="literal">undefined</span>;</span><br><span class="line">    <span class="attr">version</span>: <span class="built_in">string</span> | <span class="literal">undefined</span>;</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="attr">os</span>: &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="built_in">string</span> | <span class="literal">undefined</span>;</span><br><span class="line">    <span class="attr">version</span>: <span class="built_in">string</span> | <span class="literal">undefined</span>;</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="attr">cpu</span>: &#123;</span><br><span class="line">    <span class="attr">architecture</span>: <span class="built_in">string</span> | <span class="literal">undefined</span>;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样我们可以定义三条规则：</p><ul><li><code>daily_list</code> （某个测试版的浏览器）</li><li><code>white_list</code> （测试过没有问题的浏览器）</li><li><code>black_list</code> （已知有问题的浏览器）</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 存在优先级关系: daily_list &gt; white_list &gt; black_list</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// https://www.npmjs.com/package/ua-parser-js</span></span><br><span class="line"><span class="comment">// @params: ua-parser-js.result</span></span><br><span class="line"><span class="comment">// @params: &#123; &lt;list_name&gt;: &lt;whiteList | blackList&gt;&#125;</span></span><br><span class="line"><span class="comment">// @return: string(list_name)</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">browserDetect</span>(<span class="params">ua, list</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; daily_list, white_list, black_list &#125; = list</span><br><span class="line">  <span class="keyword">if</span> (<span class="title function_">checkList</span>(ua, daily_list)) <span class="keyword">return</span> <span class="string">&#x27;daily_list&#x27;</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="title function_">checkList</span>(ua, white_list)) <span class="keyword">return</span> <span class="string">&#x27;white_list&#x27;</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="title function_">checkList</span>(ua, black_list)) <span class="keyword">return</span> <span class="string">&#x27;black_list&#x27;</span></span><br><span class="line">  <span class="keyword">return</span> <span class="string">&#x27;&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>基于这种数据结构，加入一种简单的语法。支持版本号判断，加入这个几种符号的支持：<code>&gt;</code>, <code>≥</code>, <code>=</code>, <code>&lt;</code>, <code>≤</code></p><p>由于没有现成可以用的，所以要自己用 <a href="https://www.npmjs.com/package/compare-versions">compare-versions</a> 写一段判断，遍历整个结构对比全部的版本号</p><p>这样的话配置文件就可以这样写：<code>config/browser.yml</code></p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># https://www.npmjs.com/package/ua-parser-js</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># 本文件的语法是在这个库之上做的修改</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 白名单，完全没有问题的版本</span></span><br><span class="line"><span class="attr">white_list:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">browser:</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">&quot;Chrome&quot;</span></span><br><span class="line">      <span class="attr">version:</span> <span class="string">&quot;&gt;= 85.0.0.0&quot;</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">browser:</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">&quot;Firefox&quot;</span></span><br><span class="line">      <span class="attr">version:</span> <span class="string">&quot;&gt;= 85.0.0.0&quot;</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">browser:</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">&quot;Edge&quot;</span></span><br><span class="line">      <span class="attr">version:</span> <span class="string">&quot;&gt;= 45.0.0.0&quot;</span></span><br><span class="line">    <span class="attr">device:</span></span><br><span class="line">      <span class="attr">type:</span> <span class="string">&quot;mobile&quot;</span></span><br><span class="line">    <span class="attr">os:</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">&quot;Android&quot;</span></span><br><span class="line">      <span class="attr">version:</span> <span class="string">&quot;&gt;= 10.0&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">black_list:</span></span><br><span class="line">  <span class="comment"># 老版本的 Edge 不支持</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">browser:</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">&quot;Edge&quot;</span></span><br><span class="line">      <span class="attr">version:</span> <span class="string">&quot;&lt; 80.0.0.0&quot;</span></span><br><span class="line">    <span class="attr">os:</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">&quot;Windows&quot;</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># 手机微信内置浏览器</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">browser:</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">&quot;WeChat&quot;</span></span><br><span class="line">    <span class="attr">device:</span></span><br><span class="line">      <span class="attr">type:</span> <span class="string">&quot;mobile&quot;</span></span><br></pre></td></tr></table></figure><p>我们的产品的用户上至自己编译浏览器自己用的极客，下至用微信内置浏览器的小白</p><p>所以要给使用 beta 版，dev 版，canary 开发版，nightly 版给予提示。这个 WebRTC 不稳定，可能会有问题。</p><blockquote><p>这件事情出现过，有人用了开发版的浏览器，音视频不稳定，然后他又更新了的版本。。。</p></blockquote><p>由于开发版浏览器并不会在 ua 里面携带 dev 的标识。只能通过版本号来判断。可以使用 caniuse-lite 的数据库，取出最新稳定版的的版本号，然后进行版本号比对</p><p>但是 caniuse-lite 的数据库有 <code>1.3M</code> 如果直接使用，会打包整个数据库。这个体积的增加了太多</p><p>需要优化一下，所以采用把查询结果打包成文件的办法，实际上真正有用的数据非常的少。</p><p>创建一个生成器，可以动态创建这个文件 <code>latest_browser_list_generator.js</code></p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env node</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> browserslist = <span class="built_in">require</span>(<span class="string">&#x27;browserslist&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> list = &#123;</span><br><span class="line">  <span class="string">&#x27;firefox&#x27;</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="string">&#x27;chrome&#x27;</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="string">&#x27;edge&#x27;</span>: <span class="literal">true</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> latest = <span class="title function_">browserslist</span>(<span class="string">&quot;last 1 version&quot;</span>).<span class="title function_">filter</span>(<span class="function"><span class="params">i</span> =&gt;</span> list[i.<span class="title function_">split</span>(<span class="string">&#x27; &#x27;</span>)[<span class="number">0</span>]])</span><br><span class="line">fs.<span class="title function_">writeFileSync</span>(<span class="string">&#x27;latest_browser_list.js&#x27;</span>, <span class="string">`export default <span class="subst">$&#123;<span class="built_in">JSON</span>.stringify(latest)&#125;</span>`</span>)</span><br></pre></td></tr></table></figure><p>之后定期执行这两个就可以了</p><ul><li><code>npx browserslist@latest --update-db</code></li><li><code>node latest_browser_list_generator.js</code></li></ul><p>当然，这个可以用 GitHub action 或 GitLab CI 来每周执行一次</p><h3 id="360-浏览器检测"><a href="#360-浏览器检测" class="headerlink" title="360 浏览器检测"></a>360 浏览器检测</h3><p>360 浏览器隐藏了自己的 UA 。360 浏览器只有在访问自己的网站（比如：<code>360.cn</code>）是才会在 UA 里携带 <code>QIHU 360SE</code> （360 安全浏览器）或 <code>QIHU 360EE</code> （360 极速浏览器）字段</p><p>我们只能使用一下其他的方式，通过一些其他的特征来检测</p><p>对待国内浏览器：<a href="https://github.com/mumuy/browser">这个库可以检测到 360</a></p><p>不过这个库的作者并没有提供可以直接使用包。只能把核心代码提取出来</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// https://github.com/mumuy/browser/blob/4a50ee18cc76a5013dea3596bb33fbab9ed584c3/Browser.js#L111-L143</span></span><br><span class="line"><span class="keyword">if</span> (_window.<span class="property">chrome</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> chrome_version = u.<span class="title function_">replace</span>(<span class="regexp">/^.*Chrome\/([\d]+).*$/</span>, <span class="string">&#x27;$1&#x27;</span>)</span><br><span class="line">  <span class="keyword">if</span> (_window.<span class="property">chrome</span>.<span class="property">adblock2345</span> || _window.<span class="property">chrome</span>.<span class="property">common2345</span>) &#123;</span><br><span class="line">    match[<span class="string">&#x27;2345Explorer&#x27;</span>] = <span class="literal">true</span></span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (</span><br><span class="line">    <span class="title function_">_mime</span>(<span class="string">&#x27;type&#x27;</span>, <span class="string">&#x27;application/360softmgrplugin&#x27;</span>) ||</span><br><span class="line">    <span class="title function_">_mime</span>(<span class="string">&#x27;type&#x27;</span>, <span class="string">&#x27;application/mozilla-npqihooquicklogin&#x27;</span>)</span><br><span class="line">  ) &#123;</span><br><span class="line">    is360 = <span class="literal">true</span></span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (chrome_version &gt; <span class="number">36</span> &amp;&amp; _window.<span class="property">showModalDialog</span>) &#123;</span><br><span class="line">    is360 = <span class="literal">true</span></span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (chrome_version &gt; <span class="number">45</span>) &#123;</span><br><span class="line">    is360 = <span class="title function_">_mime</span>(<span class="string">&#x27;type&#x27;</span>, <span class="string">&#x27;application/vnd.chromium.remoting-viewer&#x27;</span>)</span><br><span class="line">    <span class="keyword">if</span> (!is360 &amp;&amp; chrome_version &gt;= <span class="number">69</span>) &#123;</span><br><span class="line">      is360 = <span class="title function_">_mime</span>(<span class="string">&#x27;type&#x27;</span>, <span class="string">&#x27;application/hwepass2001.installepass2001&#x27;</span>) || <span class="title function_">_mime</span>(<span class="string">&#x27;type&#x27;</span>, <span class="string">&#x27;application/asx&#x27;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 修正</span></span><br><span class="line"><span class="keyword">if</span> (match[<span class="string">&#x27;Mobile&#x27;</span>]) &#123;</span><br><span class="line">  match[<span class="string">&#x27;Mobile&#x27;</span>] = !(u.<span class="title function_">indexOf</span>(<span class="string">&#x27;iPad&#x27;</span>) &gt; -<span class="number">1</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (is360) &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="title function_">_mime</span>(<span class="string">&#x27;type&#x27;</span>, <span class="string">&#x27;application/gameplugin&#x27;</span>)) &#123;</span><br><span class="line">    match[<span class="string">&#x27;360SE&#x27;</span>] = <span class="literal">true</span></span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (</span><br><span class="line">    _navigator &amp;&amp;</span><br><span class="line">    <span class="keyword">typeof</span> _navigator[<span class="string">&#x27;connection&#x27;</span>] !== <span class="string">&#x27;undefined&#x27;</span> &amp;&amp;</span><br><span class="line">    <span class="keyword">typeof</span> _navigator[<span class="string">&#x27;connection&#x27;</span>][<span class="string">&#x27;saveData&#x27;</span>] == <span class="string">&#x27;undefined&#x27;</span></span><br><span class="line">  ) &#123;</span><br><span class="line">    match[<span class="string">&#x27;360SE&#x27;</span>] = <span class="literal">true</span></span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    match[<span class="string">&#x27;360EE&#x27;</span>] = <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>不过这里要注意：<a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/mimeTypes"><code>navigator.mimeTypes</code> 已经从 Web 标准中移除</a>（也许未来的某天这个方法就没法用了）</p><p>判断 360 的版本，是做了一个版本的对应关系</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// https://github.com/mumuy/browser/blob/4a50ee18cc76a5013dea3596bb33fbab9ed584c3/Browser.js#L283-L292</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">get360SEVersion</span>(<span class="params">u</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> hash = &#123; <span class="string">&#x27;86&#x27;</span>: <span class="string">&#x27;13.0&#x27;</span>, <span class="string">&#x27;78&#x27;</span>: <span class="string">&#x27;12.0&#x27;</span>, <span class="string">&#x27;69&#x27;</span>: <span class="string">&#x27;11.0&#x27;</span>, <span class="string">&#x27;63&#x27;</span>: <span class="string">&#x27;10.0&#x27;</span>, <span class="string">&#x27;55&#x27;</span>: <span class="string">&#x27;9.1&#x27;</span>, <span class="string">&#x27;45&#x27;</span>: <span class="string">&#x27;8.1&#x27;</span>, <span class="string">&#x27;42&#x27;</span>: <span class="string">&#x27;8.0&#x27;</span>, <span class="string">&#x27;31&#x27;</span>: <span class="string">&#x27;7.0&#x27;</span>, <span class="string">&#x27;21&#x27;</span>: <span class="string">&#x27;6.3&#x27;</span> &#125;</span><br><span class="line">  <span class="keyword">let</span> chrome_version = u.<span class="title function_">replace</span>(<span class="regexp">/^.*Chrome\/([\d]+).*$/</span>, <span class="string">&#x27;$1&#x27;</span>)</span><br><span class="line">  <span class="keyword">return</span> hash[chrome_version] || <span class="string">&#x27;&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">get360EEVersion</span>(<span class="params">u</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> hash = &#123; <span class="string">&#x27;86&#x27;</span>: <span class="string">&#x27;13.0&#x27;</span>, <span class="string">&#x27;78&#x27;</span>: <span class="string">&#x27;12.0&#x27;</span>, <span class="string">&#x27;69&#x27;</span>: <span class="string">&#x27;11.0&#x27;</span>, <span class="string">&#x27;63&#x27;</span>: <span class="string">&#x27;9.5&#x27;</span>, <span class="string">&#x27;55&#x27;</span>: <span class="string">&#x27;9.0&#x27;</span>, <span class="string">&#x27;50&#x27;</span>: <span class="string">&#x27;8.7&#x27;</span>, <span class="string">&#x27;30&#x27;</span>: <span class="string">&#x27;7.5&#x27;</span> &#125;</span><br><span class="line">  <span class="keyword">let</span> chrome_version = u.<span class="title function_">replace</span>(<span class="regexp">/^.*Chrome\/([\d]+).*$/</span>, <span class="string">&#x27;$1&#x27;</span>)</span><br><span class="line">  <span class="keyword">return</span> hash[chrome_version] || <span class="string">&#x27;&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>为了网络环境的健康，为了不重蹈浏览器大战时的覆辙。不要针对特定浏览器提供差异化内容。</p><p>我们可以告知用户：<strong>「我们没有在这个浏览器上充分测试过」</strong></p><p>但不能去禁止某个特定的浏览器，或对不同浏览器提供差异化内容</p><p>尽可能使用 <code>feature detection</code> 来判读浏览器的支持情况</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://webaim.org/blog/user-agent-string-history/">History of the browser user-agent string</a></p><p><a href="https://www.v2ex.com/t/425627">360 安全浏览器和 360 极速浏览器的判断 - V2EX</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;本篇文章已入选 ShowMeBug 公司官方发布的技术文章，授权的很多地方转载&lt;/p&gt;
&lt;p&gt;本文放在这里做个归档。（这才是真正的原稿）&lt;/p&gt;
&lt;p&gt;另外，本文后发布的，内容上会有些差异（下次把首发放在自己的 Blog）&lt;/p&gt;
&lt;/blockq</summary>
      
    
    
    
    <category term="browser" scheme="https://a-wing.top/categories/browser/"/>
    
    
    <category term="linux" scheme="https://a-wing.top/tags/linux/"/>
    
    <category term="browser" scheme="https://a-wing.top/tags/browser/"/>
    
  </entry>
  
  <entry>
    <title>如何使用 shell（3/3）—— 配置 zsh</title>
    <link href="https://a-wing.top/shell/2021/05/10/zsh-config"/>
    <id>https://a-wing.top/shell/2021/05/10/zsh-config</id>
    <published>2021-05-10T04:00:00.000Z</published>
    <updated>2021-05-10T04:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>轻量，简洁，简单。这个我对 zsh 配置的追求</p><ol><li><a href="/shell/2021/05/01/sh-compatibles-history.html">如何使用 shell（1&#x2F;3）—— shell 兼容和历史</a></li><li><a href="/shell/2021/05/05/new-shell.html">如何使用 shell（2&#x2F;3）—— 新时代的 shell</a></li><li>如何使用 shell（3&#x2F;3）—— 配置 zsh</li></ol><h2 id="启动-shell"><a href="#启动-shell" class="headerlink" title="启动 shell"></a>启动 shell</h2><p>先来复习一下 shell 的登录和交互</p><h3 id="交互（Interactive）"><a href="#交互（Interactive）" class="headerlink" title="交互（Interactive）"></a>交互（Interactive）</h3><p>shell 有交互，或者没有交互。正常启动输入命令等待返回是需要交互的， 直接运行脚本的情况是不需要交互的</p><p><code>echo $-</code> 判断是否交互，每个字母代表一个属性。 <code>i</code> 代表交互（interactive）</p><p>交互 shell 会有 PROMPT ，所以会有 <code>PS1</code> 变量，也可以通过 <code>PS1</code> 变量来判断是否交互</p><h3 id="登录（Login）"><a href="#登录（Login）" class="headerlink" title="登录（Login）"></a>登录（Login）</h3><p>login shell ，bash 通过 <code>shopt</code> 来判断，zsh 通过 <code>setopt</code> 判断。（zsh 的 <code>echo $-</code> 也可以判断是否登录）。<br>请记住 <code>setopt</code> 这条命令， zsh 的好多功能都是通过 <code>setopt</code> 来设置的，取消用 <code>setopt noxxx</code>  或者 <code>unsetopt</code></p><p>打开终端和 ssh 默认都是登录 shell。直接执行 <code>bash</code> &#x2F; <code>zsh</code> 是非登录的 （non-login shell），要指定 <code>--login</code> 参数。<br><code>su</code> 命令默认也是非登录的，要指定参数：<code>su -</code> 或 <code>su -l</code> 。 <code>-</code> 和 <code>-l</code> 是完全等同的</p><p>交互和登录排列组合可以分成四种情况：</p><ul><li>非交互非登录</li><li>非交互登录</li><li>交互非登录</li><li>交互登录</li></ul><p>他们加载的配置文件也不同。为了简化处理，直接忽略其他的文件，只用 <code>bashrc</code> 或 <code>zshrc</code> 。<br>把其他的都删掉（ <code>.bash_profile</code> , <code>.bash_login</code> , <code>.zprofile</code> , <code>.zlogin</code> 这些都删掉），这样一定会读 <code>.bashrc</code> 或 <code>.zshrc</code> 文件</p><h3 id="motd"><a href="#motd" class="headerlink" title="motd"></a>motd</h3><p>熟悉 Linux 的同学想必对 motd 全称 Message Of The Day 并不陌生</p><p>把内容放到 <code>/etc/motd</code> 里面然后每次登录显示里面的内容</p><p>motd 本身是纯文本，传统的 motd 只能是纯文本。当然，我们可以在 shell 启动执行命令时显示一些东西，达到类似 motd 功能的效果</p><p>比如使用 <code>cowsay</code> 显示欢迎信息（把这条命令放到 shell 的配置文件里）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">echo</span> <span class="string">&quot;Hello! <span class="variable">$&#123;USERNAME&#125;</span>&quot;</span> | cowsay -f tux</span><br><span class="line"> ______________</span><br><span class="line">&lt; Hello! metal &gt;</span><br><span class="line"> --------------</span><br><span class="line">   \</span><br><span class="line">    \</span><br><span class="line">        .--.</span><br><span class="line">       |o_o |</span><br><span class="line">       |:_/ |</span><br><span class="line">      //   \ \</span><br><span class="line">     (|     | )</span><br><span class="line">    /<span class="string">&#x27;\_   _/`\</span></span><br><span class="line"><span class="string">    \___)=(___/</span></span><br></pre></td></tr></table></figure><h2 id="主题和配置框架"><a href="#主题和配置框架" class="headerlink" title="主题和配置框架"></a>主题和配置框架</h2><p>如果不使用任何主题，就要自己和 <code>$PS1</code> 变量打交道， <code>PS</code> 一共四个变量。考虑自己设置的复杂性。首选当然是使用大佬们的配置</p><p>zsh 由于历史问题，默认设置基本上和几十年前一样，现在的 zsh 有很多非常强大的功能。只不过默认是并不开启</p><h3 id="oh-my-zsh"><a href="#oh-my-zsh" class="headerlink" title="oh-my-zsh"></a>oh-my-zsh</h3><p>作为 zsh 最出名的配置框架。配置 zsh 甚至可以说 oh-my-zsh 和 非 oh-my-zsh</p><p>自动设置颜色部分，只要启用就可以覆盖掉难看的要死的默认配色。默认配置很完美</p><p>集成了大量实用的函数和主题（个人推荐 <code>ys</code> 主题），比较极端的用户甚至会使用 ramdom 主题，每次开启随机选择一个，保证新鲜感</p><p>oh-my-zsh 管的东西太多了。oh-my-zsh 的各种插件里面基本上全是 aliases。建议一个都不要用</p><h3 id="prezto"><a href="#prezto" class="headerlink" title="prezto"></a>prezto</h3><p>oh-my-zsh 之外的另一个选择，或者说是他的的替代品。比 oh-my-zsh 轻量一点。</p><h3 id="zimfw"><a href="#zimfw" class="headerlink" title="zimfw"></a>zimfw</h3><p>用 ruby 的 <code>erb</code> 模版引擎，编译产生最终配置文件。</p><p>在 <code>zimrc</code> 里面配置启用的插件，然后编译产生 <code>~/.zshrc</code> 文件。每次修改更新都要重新编译，不过问题不大，<strong>每天都在修改配置文件才是最大的问题</strong></p><p>默认会开启 git 插件，注意把 git 插件禁用（我推荐把这个禁用），这个插件全是 git aliases。请仔细阅读代码再决定是否要使用这个插件</p><h3 id="grml-zsh-config"><a href="#grml-zsh-config" class="headerlink" title="grml-zsh-config"></a>grml-zsh-config</h3><p>一个及其轻量化的配置，也没有管理插件的功能。Grml OS 的默认 zsh 配置，Grml 是基于 Debian 的 Linux 发行版。主要是为了 live CD 设计的</p><p>我很喜欢 grml 的配置，简单轻量。这才是新时代的默认设置。grml 不包括管理插件的功能。是一个纯粹的 <code>zshrc</code> 文件</p><p>grml 内置了几个主题：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 列出主题</span></span><br><span class="line">prompt -l</span><br><span class="line"></span><br><span class="line"><span class="comment"># 预览主题</span></span><br><span class="line">prompt -p adam2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置主题</span></span><br><span class="line">prompt -s adam2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 关闭主题，使用其他的主题</span></span><br><span class="line">prompt off</span><br></pre></td></tr></table></figure><p>grml 的 zsh 配置拥有非常灵活的配置，默认的 grml 主题支持 vsc （git 显示当前分支）可以自定义显示的各种项。比如像这样来让 prompt 显示两行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Set Theme</span></span><br><span class="line"><span class="comment"># http://bewatermyfriend.org/p/2013/001/</span></span><br><span class="line"><span class="built_in">zstyle</span> <span class="string">&#x27;:prompt:grml:left:setup&#x27;</span> items rc change-root user at host path vcs newline percent</span><br><span class="line"></span><br><span class="line">prompt grml</span><br></pre></td></tr></table></figure><h3 id="powerlevel-系列"><a href="#powerlevel-系列" class="headerlink" title="powerlevel 系列"></a>powerlevel 系列</h3><p>powerline 系列的 zsh 版主题</p><p><img src="/assets/img/zsh-config/power.png" alt="power.png"></p><p>powerlevel9k 是一个极其复杂的主题，他绝对会令你眼前一亮，或者应该称为地表最强 zsh 主题。这个主题最与众不同的地方大概就是和各种编程环境的整合，甚至可以识别一些 Web 框架，编程语言的 VM 环境</p><p>后来针对性能，有人开发 powerlevel10k，就如同 powerlevel9k 一样，同时又有的不错的性能</p><p><img src="/assets/img/zsh-config/powerlevel.png" alt="power.png"></p><h3 id="pure"><a href="#pure" class="headerlink" title="pure"></a>pure</h3><p>这是一个非常不错的主题。虽然自称是 prompt ，不过应该把他归类为主题。因为对于 shell 来说，主题就是 prompt</p><h2 id="分离式-prompt"><a href="#分离式-prompt" class="headerlink" title="分离式 prompt"></a>分离式 prompt</h2><p>在 shell 里实现复杂的 prompt 这太复杂了，干脆在外面套一层吧。后来就又了新的思路。在旧的 shell 上套一个进行交互外壳。<br>我就自作主张起个新名字叫 “分离式 prompt” 吧。分离式 prompt 虽然功能强大。不过，我们真的需要这么强大的提示符吗？</p><h3 id="Liquid-Prompt"><a href="#Liquid-Prompt" class="headerlink" title="Liquid Prompt"></a>Liquid Prompt</h3><p>这个大概 Liquid Prompt 是最早这么干的，把 prompt 单独做成一个组件。配置管理都是独立于其他的功能，支持 bash 和 zsh</p><h3 id="spaceship-prompt"><a href="#spaceship-prompt" class="headerlink" title="spaceship-prompt"></a>spaceship-prompt</h3><p>这个一个专门为 zsh 优化的 prompt，仍然时纯 shell 的实现，可以说 spaceship 直接把 prompt 的复杂程度拉高了一个等级。逐步走向前后端分离的 prompt</p><h3 id="starship"><a href="#starship" class="headerlink" title="starship"></a>starship</h3><p>只是一个 prompt ，使用 rust 的实现，速度和效率异常之高。这个作为一个交互的前端，具体使用的 shell 作为后端。可以支持绝大部分的 shell。已经彻底走向了前后端分离的 prompt。</p><p><img src="https://raw.githubusercontent.com/starship/starship/master/media/demo.gif" alt="https:&#x2F;&#x2F;raw.githubusercontent.com&#x2F;starship&#x2F;starship&#x2F;master&#x2F;media&#x2F;demo.gif"></p><h3 id="oh-my-posh"><a href="#oh-my-posh" class="headerlink" title="oh-my-posh"></a>oh-my-posh</h3><p>最开始是专门给 powershell 写的，后来用 golang 重写了之后，也变成了前后端分离的 prompt。也可以指定不同的后端</p><h2 id="插件管理器"><a href="#插件管理器" class="headerlink" title="插件管理器"></a>插件管理器</h2><p>我个人是不用这个插件管理器的，插件管理器只是方便了你来回折腾，但每天折腾 shell 配置这就不对了</p><h3 id="antigen"><a href="#antigen" class="headerlink" title="antigen"></a>antigen</h3><p>这个是 zsh 官方的插件管理工具，绝对清真。是一个存粹的包管理。哦不，他对 oh-my-zsh 和 prezto 做了特殊的适配</p><h3 id="antibody"><a href="#antibody" class="headerlink" title="antibody"></a>antibody</h3><p>使用 golang 写的包管理器。看名字就知道，对标 antigen</p><h3 id="zplug"><a href="#zplug" class="headerlink" title="zplug"></a>zplug</h3><p>类似 vim-plug 的插件管理器</p><p><img src="https://raw.githubusercontent.com/b4b4r07/screenshots/master/zplug/demo.gif" alt="https:&#x2F;&#x2F;raw.githubusercontent.com&#x2F;b4b4r07&#x2F;screenshots&#x2F;master&#x2F;zplug&#x2F;demo.gif"></p><h3 id="zgen"><a href="#zgen" class="headerlink" title="zgen"></a>zgen</h3><p>一个轻量化的插件管理器，只有 500 行代码</p><h3 id="zinit"><a href="#zinit" class="headerlink" title="zinit"></a>zinit</h3><p>是用 C 实现的插件管理器，有极高的性能，可能是速度最快的。zplugin 和 zinit 的是一个项目，zplugin 是之前的名字。</p><p>和 zimfw 一样，可以编译插件来运行的更快。和 zimfw 不同，不提供配置，这个只是插件管理器和编译器。</p><h2 id="zsh-插件"><a href="#zsh-插件" class="headerlink" title="zsh 插件"></a>zsh 插件</h2><p>fish 有不少实用的特性，代码高亮、自动建议。fish 的很多交互特性已经在 zsh 都实现了</p><h3 id="zsh-users-系列"><a href="#zsh-users-系列" class="headerlink" title="zsh-users 系列"></a>zsh-users 系列</h3><p>zsh-users 是一个 zsh 官方的仓库，有几个很棒的插件，基本上所有的 zsh 配置框架都集成这几个插件：</p><ul><li>zsh-syntax-highlighting（代码高亮）</li><li>zsh-autosuggestions（自动建议）</li><li>zsh-history-substring-search（前缀搜索）</li></ul><p>还有比如：<a href="https://github.com/zsh-users/zsh-apple-touchbar">zsh-users&#x2F;zsh-apple-touchbar</a> 我的 MBP 没有 touchbar 所以不做评论</p><h3 id="zinit-系列"><a href="#zinit-系列" class="headerlink" title="zinit 系列"></a>zinit 系列</h3><ul><li>fast-syntax-highlighting（高性能代码高亮）</li><li>history-search-multi-word（增强版的 <code>Crtl-R</code>）</li></ul><p>zinit 的作者写的插件，可以用这里的插件去替代 zsh-users 系列的插件</p><h3 id="thefuck"><a href="#thefuck" class="headerlink" title="thefuck"></a>thefuck</h3><p>纠正拼写错误。用个插件的人好像还不少，我不用这个，也不推荐用这个插件。</p><p>我倒是很想知道用这个插件的人是什么心态 fuck，fuck，fuuuu…</p><h3 id="autojump"><a href="#autojump" class="headerlink" title="autojump"></a>autojump</h3><p>根据使用习惯来自动跳转目录，他会维护一个数据库，记录平时都使用哪些目录</p><h2 id="自定义功能"><a href="#自定义功能" class="headerlink" title="自定义功能"></a>自定义功能</h2><p>MacOS 需要设置一下默认快捷键，<code>Crtl + left/right arrow</code> 被 Mission Control 功能占用了</p><p><code>System Preferences &gt; Keyboard &gt; Shortcuts</code> 的 <code>Crtl + left/right arrow</code> 的快捷键改掉，不然跳一个词的功能就没有了。<br>我是改成了 <code>Crtl + Command + left/right arrow</code> 。我记得 Deepin Linux 的切换虚拟桌面默认就是这个键位</p><h3 id="关闭流控功能"><a href="#关闭流控功能" class="headerlink" title="关闭流控功能"></a>关闭流控功能</h3><p>shell 和终端（iTerm2 是忽略这个功能的）要同时支持才能看到效果，大部分终端都是支持的。shell 默认都是启用这个功能的</p><p>这个是一个古老的功能，在历史上（现在某些终端中仍然如此），<code>Ctrl-S</code> 暂停输出，而 <code>Ctrl-Q</code> 继续输出。站在今天的角度来看，这个功能基本上没用。还要占用两个快捷键</p><p>很多框架的设置默认是不禁用 <code>flowcontrol</code> 。配置框架里只有 oh-my-zsh 是设置禁用了</p><p>我们来把他关掉： <code>setopt noflowcontrol</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 关掉 flowcontrol</span></span><br><span class="line"><span class="built_in">unsetopt</span> flowcontrol</span><br><span class="line"></span><br><span class="line"><span class="comment"># 上面的设置在 Tmux 里面无效</span></span><br><span class="line"><span class="built_in">stty</span> -ixon</span><br></pre></td></tr></table></figure><h3 id="push-line"><a href="#push-line" class="headerlink" title="push-line"></a>push-line</h3><p>这个功能不知道该怎么描述。oh-my-zsh 有个 <code>Ctrl-Q</code> 的快捷键，可以把当前的输入保存起来，然后去执行一条新的命令，执行完之后在保存的内容放出来。这个功能非常的实用</p><p>很多设置和框架会设置把 <code>Ctrl-Q</code> 设置成 <code>push-line</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">bindkey</span> <span class="string">&quot;\eq&quot;</span> push-line</span><br></pre></td></tr></table></figure><p>但 <code>push-line</code> 是没法处理 here doc 的情况的</p><p>可以设置成 <code>push-line-or-edit</code> 就可以处理 here doc</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">bindkey</span> <span class="string">&quot;\eq&quot;</span> push-line-or-edit</span><br></pre></td></tr></table></figure><p><code>push-line</code> 和 <code>push-line-or-edit</code> 很实用，是 zsh 内置的功能。默认是不启用的，zsh 还有好多这样默认不启用的功能。<br>写新功能之前最好先去 <code>man zshzle</code> 里看看有没有现成的</p><h3 id="快速-sudo"><a href="#快速-sudo" class="headerlink" title="快速 sudo"></a>快速 sudo</h3><p>或许你会经常 <code>sudo !!</code> 来使用 <code>sudo</code> 执行前一条命令</p><p><code>sudo</code> 用的比较多，所以我们来定制一个快捷键。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">sudo-command-<span class="function"><span class="title">line</span></span>() &#123;</span><br><span class="line">  [[ -z <span class="variable">$BUFFER</span> ]] &amp;&amp; <span class="built_in">zle</span> up-history</span><br><span class="line">  <span class="built_in">local</span> cmd=<span class="string">&quot;sudo &quot;</span></span><br><span class="line">  <span class="keyword">if</span> [[ <span class="variable">$&#123;BUFFER&#125;</span> == <span class="variable">$&#123;cmd&#125;</span>* ]]; <span class="keyword">then</span></span><br><span class="line">    CURSOR=$(( CURSOR-<span class="variable">$&#123;#cmd&#125;</span> ))</span><br><span class="line">    BUFFER=<span class="string">&quot;<span class="variable">$&#123;BUFFER#$cmd&#125;</span>&quot;</span></span><br><span class="line">  <span class="keyword">else</span></span><br><span class="line">    BUFFER=<span class="string">&quot;<span class="variable">$&#123;cmd&#125;</span><span class="variable">$&#123;BUFFER&#125;</span>&quot;</span></span><br><span class="line">    CURSOR=$(( CURSOR+<span class="variable">$&#123;#cmd&#125;</span> ))</span><br><span class="line">  <span class="keyword">fi</span></span><br><span class="line">  <span class="built_in">zle</span> reset-prompt</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">zle</span>     -N   sudo-command-line</span><br><span class="line"><span class="comment"># Ctrl-S</span></span><br><span class="line"><span class="built_in">bindkey</span> <span class="string">&#x27;^S&#x27;</span> sudo-command-line</span><br></pre></td></tr></table></figure><p>这样直接使用 <code>Ctrl-S</code> 就可以快速在前面添加 <code>sudo</code> ，如果前面有 <code>sudo</code> 就自动去掉 <code>sudo</code></p><p><del>grml 里也带了类似的配置，不过只支持添加 <code>sudo</code> ，我这种改法和 grml 是有区别的。</del></p><p>现在 grml zsh 的配置和我这个用的是同样的配置了 <a href="https://github.com/grml/grml-etc-core/pull/119">grml&#x2F;zshrc#119</a></p><h3 id="命令补全加载优化"><a href="#命令补全加载优化" class="headerlink" title="命令补全加载优化"></a>命令补全加载优化</h3><p><code>kubectl</code> 这个命令是个典型，这个补全文件非常大，有上万行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">kubectl completion zsh | <span class="built_in">wc</span> -l</span><br><span class="line">13090</span><br></pre></td></tr></table></figure><p>官方文档这个放在 <code>.zshrc</code> 里是一种性能很差的写法</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">source</span> &lt;(kubectl completion zsh)</span><br></pre></td></tr></table></figure><p>生成一个文件，然后放到 <code>$FPATH</code> 是个更好的选择</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl completion zsh &gt; ~/.zsh-completions/_kubectl.zsh</span><br></pre></td></tr></table></figure><p>把这个放到 zsh 的配置文件里</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> [[ -d <span class="string">&quot;<span class="variable">$&#123;HOME&#125;</span>/.zsh-completions&quot;</span> ]]; <span class="keyword">then</span></span><br><span class="line">  FPATH=<span class="variable">$&#123;HOME&#125;</span>/.zsh-completions:<span class="variable">$FPATH</span></span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure><p>或者使用 Lazyload 方式，先定义一个占位函数，然后输入时加载。<br>不过 <code>$FPATH</code> 已经是懒加载。我一直都觉得把 Lazyload 用在 completion 上是个误区（看到过有人这样用，欢迎来把我辩倒）</p><h3 id="fzf-和-shell"><a href="#fzf-和-shell" class="headerlink" title="fzf 和 shell"></a>fzf 和 shell</h3><p>fzf 是提供了给 shell 用的快捷键的，需要自己去开启这个功能</p><p><img src="/assets/img/zsh-config/fzf-history.png" alt="fzf-history.png"></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># brew --prefix fzf</span></span><br><span class="line"><span class="comment"># source $(brew --prefix fzf)/shell/completion.zsh</span></span><br><span class="line"><span class="comment"># source $(brew --prefix fzf)/shell/key-bindings.zsh</span></span><br><span class="line"><span class="built_in">source</span> /usr/local/opt/fzf/shell/completion.zsh</span><br><span class="line"><span class="built_in">source</span> /usr/local/opt/fzf/shell/key-bindings.zsh</span><br></pre></td></tr></table></figure><p>提供三个绑定的快捷键</p><ul><li><code>Ctrl-R</code> 增强历史查找</li><li><code>Crtl-T</code> 模糊路径选择</li><li><code>Alt-C</code> 模糊路径跳转</li></ul><p>补全功能提供 <code>**</code> 的然后按下 <code>&lt;TAB&gt;</code> 语法，使用 fzf 来选择补全</p><p><code>Crtl-T</code> 其实是个 zsh 默认的快捷键，fzf 提供的模糊路径选择的功能使用的频率太低，这里再设置回默认的快捷键</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">bindkey</span> <span class="string">&#x27;^T&#x27;</span> transpose-chars</span><br></pre></td></tr></table></figure><p>对于苹果的电脑 <code>Alt-C</code> 的快捷键要处理一下</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Default ALT-C, For Mac OS: Option-C</span></span><br><span class="line"><span class="keyword">if</span> [[ `<span class="built_in">uname</span>` == <span class="string">&quot;Darwin&quot;</span> ]]; <span class="keyword">then</span></span><br><span class="line">  <span class="built_in">bindkey</span> <span class="string">&#x27;ç&#x27;</span> fzf-cd-widget</span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure><h3 id="最近目录"><a href="#最近目录" class="headerlink" title="最近目录"></a>最近目录</h3><p>还记得 Elvish <code>Crtl-L</code> 的列表选择跳转最近目录的功能吗？</p><p>配合 fzf 来实现一个，由于 <code>Crtl-L</code> 快捷键已经被占用，所以使用 <code>ALT-X</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">fzf-dirs-<span class="function"><span class="title">widget</span></span>() &#123;</span><br><span class="line">  <span class="comment"># eval cd $(dirs -v | fzf --height 40% --reverse | cut -b3-)</span></span><br><span class="line">  <span class="built_in">local</span> <span class="built_in">dir</span>=$(<span class="built_in">dirs</span> -v | fzf --height <span class="variable">$&#123;FZF_TMUX_HEIGHT:-40%&#125;</span> --reverse | <span class="built_in">cut</span> -b3-)</span><br><span class="line">  <span class="keyword">if</span> [[ -z <span class="string">&quot;<span class="variable">$dir</span>&quot;</span> ]]; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">zle</span> redisplay</span><br><span class="line">    <span class="built_in">return</span> 0</span><br><span class="line">  <span class="keyword">fi</span></span><br><span class="line">  <span class="built_in">eval</span> <span class="built_in">cd</span> <span class="variable">$&#123;dir&#125;</span></span><br><span class="line">  <span class="built_in">local</span> ret=$?</span><br><span class="line">  <span class="built_in">unset</span> <span class="built_in">dir</span> <span class="comment"># ensure this doesn&#x27;t end up appearing in prompt expansion</span></span><br><span class="line">  <span class="built_in">zle</span> reset-prompt</span><br><span class="line">  <span class="built_in">return</span> <span class="variable">$ret</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">zle</span>     -N    fzf-dirs-widget</span><br><span class="line"></span><br><span class="line"><span class="comment"># Default ALT-X, For Mac OS: Option-X</span></span><br><span class="line"><span class="keyword">if</span> [[ `<span class="built_in">uname</span>` == <span class="string">&quot;Darwin&quot;</span> ]]; <span class="keyword">then</span></span><br><span class="line">  <span class="built_in">bindkey</span> <span class="string">&#x27;≈&#x27;</span> fzf-dirs-widget</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">  <span class="built_in">bindkey</span> <span class="string">&#x27;\ex&#x27;</span> fzf-dirs-widget</span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure><h3 id="导航模式（Navigation-mode）"><a href="#导航模式（Navigation-mode）" class="headerlink" title="导航模式（Navigation mode）"></a>导航模式（Navigation mode）</h3><p>Elvish <code>Crtl-N</code>  的快捷键，来启动导航模式。类似 ranger 或 nnn 的 tui 里面选择目录</p><p>这个功能，可以用 ranger 或 nnn 来改造。这两个项目都提供了例子，我还是更偏爱 ranger 一些，nnn 的运行速度更快。<br>还有一个纯 shell 的非常精简的实现 <a href="https://github.com/vifon/deer">vifon&#x2F;deer</a></p><ul><li>ranger</li></ul><p><img src="/assets/img/zsh-config/ranger.png" alt="ranger.png"></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Fork from: https://github.com/ranger/ranger/blob/master/examples/shell_automatic_cd.sh</span></span><br><span class="line"><span class="function"><span class="title">ranger_cd</span></span>() &#123;</span><br><span class="line">  <span class="built_in">local</span> temp_file=<span class="string">&quot;<span class="subst">$(mktemp -t <span class="string">&quot;ranger_cd.<span class="variable">$&#123;USERNAME&#125;</span>&quot;</span>)</span>&quot;</span></span><br><span class="line">  ranger --choosedir=<span class="string">&quot;<span class="variable">$temp_file</span>&quot;</span> -- <span class="string">&quot;<span class="variable">$&#123;@:-<span class="variable">$PWD</span>&#125;</span>&quot;</span></span><br><span class="line">  <span class="keyword">if</span> chosen_dir=<span class="string">&quot;<span class="subst">$(cat -- <span class="string">&quot;<span class="variable">$temp_file</span>&quot;</span>)</span>&quot;</span> &amp;&amp; [ -n <span class="string">&quot;<span class="variable">$chosen_dir</span>&quot;</span> ] &amp;&amp; [ <span class="string">&quot;<span class="variable">$chosen_dir</span>&quot;</span> != <span class="string">&quot;<span class="variable">$PWD</span>&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">cd</span> -- <span class="string">&quot;<span class="variable">$chosen_dir</span>&quot;</span></span><br><span class="line">  <span class="keyword">fi</span></span><br><span class="line">  <span class="built_in">rm</span> -f -- <span class="string">&quot;<span class="variable">$temp_file</span>&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># This binds Ctrl-N to ranger_cd:</span></span><br><span class="line"><span class="built_in">bindkey</span> -s <span class="string">&#x27;^N&#x27;</span> <span class="string">&#x27;ranger_cd\n&#x27;</span></span><br></pre></td></tr></table></figure><ul><li>nnn</li></ul><p>有的包把这个脚本也包括了进来，比如 Archlinux。可以直接 <code>source</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">source</span> /usr/share/nnn/quitcd/quitcd.bash_zsh</span><br><span class="line"><span class="built_in">bindkey</span> -s <span class="string">&#x27;^N&#x27;</span> <span class="string">&#x27;n\n&#x27;</span></span><br></pre></td></tr></table></figure><p>在 MacOS 的 homebrew 并没有提供 <code>quitcd</code> 脚本，需要自己设置一下</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Frok from: https://github.com/jarun/nnn/blob/master/misc/quitcd/quitcd.bash_zsh</span></span><br><span class="line"><span class="function"><span class="title">nnn_cd</span></span> () &#123;</span><br><span class="line">  <span class="comment"># Block nesting of nnn in subshells</span></span><br><span class="line">  <span class="keyword">if</span> [ -n <span class="variable">$NNNLVL</span> ] &amp;&amp; [ <span class="string">&quot;<span class="variable">$&#123;NNNLVL:-0&#125;</span>&quot;</span> -ge 1 ]; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;nnn is already running&quot;</span></span><br><span class="line">    <span class="built_in">return</span></span><br><span class="line">  <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># The default behaviour is to cd on quit (nnn checks if NNN_TMPFILE is set)</span></span><br><span class="line">  <span class="comment"># To cd on quit only on ^G, remove the &quot;export&quot; as in:</span></span><br><span class="line">  <span class="comment">#     NNN_TMPFILE=&quot;$&#123;XDG_CONFIG_HOME:-$HOME/.config&#125;/nnn/.lastd&quot;</span></span><br><span class="line">  <span class="comment"># NOTE: NNN_TMPFILE is fixed, should not be modified</span></span><br><span class="line">  <span class="built_in">export</span> NNN_TMPFILE=<span class="string">&quot;<span class="variable">$&#123;XDG_CONFIG_HOME:-<span class="variable">$HOME</span>/.config&#125;</span>/nnn/.lastd&quot;</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># Unmask ^Q (, ^V etc.) (if required, see `stty -a`) to Quit nnn</span></span><br><span class="line">  <span class="comment"># stty start undef</span></span><br><span class="line">  <span class="comment"># stty stop undef</span></span><br><span class="line">  <span class="comment"># stty lwrap undef</span></span><br><span class="line">  <span class="comment"># stty lnext undef</span></span><br><span class="line"></span><br><span class="line">  nnn <span class="string">&quot;<span class="variable">$@</span>&quot;</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> [ -f <span class="string">&quot;<span class="variable">$NNN_TMPFILE</span>&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">    . <span class="string">&quot;<span class="variable">$NNN_TMPFILE</span>&quot;</span></span><br><span class="line">    <span class="built_in">rm</span> -f <span class="string">&quot;<span class="variable">$NNN_TMPFILE</span>&quot;</span> &gt; /dev/null</span><br><span class="line">  <span class="keyword">fi</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">bindkey</span> -s <span class="string">&#x27;^N&#x27;</span> <span class="string">&#x27;nnn_cd\n&#x27;</span></span><br></pre></td></tr></table></figure><ul><li>lf</li></ul><p>如果觉得 <code>ranger</code> 是 python 写的比较慢，也不喜欢 <code>nnn</code> 。那可以试试 golang 写的 <code>lf</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">Frok from: https://github.com/gokcehan/lf/blob/master/etc/lfcd.sh</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="title">lfcd</span></span> () &#123;</span><br><span class="line">  tmp=<span class="string">&quot;<span class="subst">$(mktemp)</span>&quot;</span></span><br><span class="line">  lf -last-dir-path=<span class="string">&quot;<span class="variable">$tmp</span>&quot;</span> <span class="string">&quot;<span class="variable">$@</span>&quot;</span></span><br><span class="line">  <span class="keyword">if</span> [ -f <span class="string">&quot;<span class="variable">$tmp</span>&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">dir</span>=<span class="string">&quot;<span class="subst">$(cat <span class="string">&quot;<span class="variable">$tmp</span>&quot;</span>)</span>&quot;</span></span><br><span class="line">    <span class="built_in">rm</span> -f <span class="string">&quot;<span class="variable">$tmp</span>&quot;</span></span><br><span class="line">    <span class="keyword">if</span> [ -d <span class="string">&quot;<span class="variable">$dir</span>&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">      <span class="keyword">if</span> [ <span class="string">&quot;<span class="variable">$dir</span>&quot;</span> != <span class="string">&quot;<span class="subst">$(pwd)</span>&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">cd</span> <span class="string">&quot;<span class="variable">$dir</span>&quot;</span></span><br><span class="line">      <span class="keyword">fi</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line">  <span class="keyword">fi</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">bindkey</span> -s <span class="string">&#x27;^N&#x27;</span> <span class="string">&#x27;lfcd\n&#x27;</span></span><br></pre></td></tr></table></figure><ul><li>hunter</li></ul><p>这是一个 Rust 写的命令行文件管理器。目前还没有办法和 shell 联动，未来也许会有这个功能</p><h3 id="变量"><a href="#变量" class="headerlink" title="变量"></a>变量</h3><p>我们肯定都见过这种写法</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> [ -d <span class="string">&quot;<span class="variable">$HOME</span>/go/bin&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">  PATH=<span class="string">&quot;<span class="variable">$HOME</span>/go/bin:<span class="variable">$PATH</span>&quot;</span></span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure><p>路径比较多，统一一下</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Load path</span></span><br><span class="line">_enabled_paths=(</span><br><span class="line">  <span class="string">&#x27;go/bin&#x27;</span></span><br><span class="line">  <span class="string">&#x27;.bin&#x27;</span></span><br><span class="line">  <span class="string">&#x27;.local/bin&#x27;</span></span><br><span class="line">  <span class="string">&#x27;.cargo/bin&#x27;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> _enabled_path <span class="keyword">in</span> <span class="variable">$_enabled_paths</span>[@]; <span class="keyword">do</span></span><br><span class="line">  [[ -d <span class="string">&quot;<span class="variable">$HOME</span>/<span class="variable">$&#123;_enabled_path&#125;</span>&quot;</span> ]] &amp;&amp; PATH=<span class="string">&quot;<span class="variable">$HOME</span>/<span class="variable">$&#123;_enabled_path&#125;</span>:<span class="variable">$PATH</span>&quot;</span></span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">export</span> LANG=en_US.UTF-8</span><br><span class="line"><span class="built_in">export</span> LC_CTYPE=en_US.UTF-8</span><br><span class="line"></span><br><span class="line"><span class="built_in">export</span> EDITOR=<span class="string">&#x27;vi&#x27;</span></span><br><span class="line"><span class="built_in">export</span> VISUAL=<span class="string">&#x27;nvim&#x27;</span></span><br></pre></td></tr></table></figure><p><code>VISUAL</code> 和 <code>EDITOR</code> ，会优先使用 <code>VISUAL</code> ，当无法使用时才会使用 <code>EDITOR</code></p><h3 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h3><p>比起慢点要死的 oh-my-zsh。这已经快了几个量级了</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> $(<span class="built_in">seq</span> 1 5); <span class="keyword">do</span> <span class="keyword">time</span> /bin/zsh -i -c <span class="built_in">exit</span>; <span class="keyword">done</span></span><br><span class="line">/bin/zsh -i -c <span class="built_in">exit</span>  0.11s user 0.07s system 86% cpu 0.211 total</span><br><span class="line">/bin/zsh -i -c <span class="built_in">exit</span>  0.12s user 0.05s system 93% cpu 0.188 total</span><br><span class="line">/bin/zsh -i -c <span class="built_in">exit</span>  0.11s user 0.06s system 91% cpu 0.187 total</span><br><span class="line">/bin/zsh -i -c <span class="built_in">exit</span>  0.12s user 0.06s system 89% cpu 0.199 total</span><br><span class="line">/bin/zsh -i -c <span class="built_in">exit</span>  0.12s user 0.06s system 84% cpu 0.219 total</span><br></pre></td></tr></table></figure><h2 id="我用什么配置"><a href="#我用什么配置" class="headerlink" title="我用什么配置"></a>我用什么配置</h2><p>本文的配图说明不了什么，我选了一些视觉冲击力强的插件的配图，实际上，我个人的配置十分的精简。最实用的配置图片上看不出来任何高端的地方。<strong>真正干活的实用配置看起来和默认的差不多</strong></p><p>好的提示符：</p><ul><li>当前路径</li><li>git branch（应该是可以选的，作为软件开发者，这个很有必要）</li><li>上一条命令的执行结果 <code>$?</code></li><li>如果是服务器，最好要显示主机名和用户</li><li>个人的喜好，偏好两行</li><li>尽量不要有特殊字体和特殊字符（ascii 的字符）</li></ul><p>我的主力机 MPB 上用  zimfw 的默认配置，再加上一点自己定义的函数，其他的机器上都是 grml 的默认配置</p><p>如果对我的配置感兴趣可以来这里看：<a href="https://github.com/a-wing/dotfiles/tree/master/zsh">a-wing&#x2F;dotfiles#zsh</a></p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://zhuanlan.zhihu.com/p/51008087">你不需要花哨的命令提示符</a></p><p><a href="https://zhuanlan.zhihu.com/p/47819029">千万别混淆 Bash&#x2F;Zsh 的四种运行模式</a></p><p><a href="https://untitled.pw/software/linux/2337.html">Untitled Spot_未命名小站 - 浅谈motd的历史，并在Linux下使用多种方法实现动态motd消息显示</a></p><p><a href="http://bewatermyfriend.org/p/2013/001/">Prompt themes for grml</a></p><p><a href="https://github.com/lilydjwg/dotzsh">lilydjwg&#x2F;dotzsh</a></p><p><a href="https://github.com/spf13/cobra/blob/master/shell_completions.md">spf13&#x2F;cobra</a></p><p><a href="https://unix.stackexchange.com/questions/4859/visual-vs-editor-what-s-the-difference">VISUAL vs. EDITOR - what’s the difference?</a></p><p><a href="https://blog.hassler.ec/wp/2018/11/23/powerlevel9k-personalise-your-terminal-prompt-for-any-programming-language/">Powerlevel9k: personalise your terminal prompt for any programming language</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;轻量，简洁，简单。这个我对 zsh 配置的追求&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;/shell/2021/05/01/sh-compatibles-history.html&quot;&gt;如何使用 shell（1&amp;#x2F;3）—— shell 兼容和历史&lt;/a&gt;&lt;/li&gt;
</summary>
      
    
    
    
    <category term="shell" scheme="https://a-wing.top/categories/shell/"/>
    
    
    <category term="linux" scheme="https://a-wing.top/tags/linux/"/>
    
    <category term="shell" scheme="https://a-wing.top/tags/shell/"/>
    
  </entry>
  
  <entry>
    <title>如何使用 shell（2/3）—— 新时代的 shell</title>
    <link href="https://a-wing.top/shell/2021/05/05/new-shell"/>
    <id>https://a-wing.top/shell/2021/05/05/new-shell</id>
    <published>2021-05-05T04:00:00.000Z</published>
    <updated>2021-05-05T04:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>新时代的 <code>shell</code> 改进的地方大体上分为两个部分：<strong>语法</strong> 和 <strong>交互</strong></p><ol><li><a href="/shell/2021/05/01/sh-compatibles-history.html">如何使用 shell（1&#x2F;3）—— shell 兼容和历史</a></li><li>如何使用 shell（2&#x2F;3）—— 新时代的 shell</li><li><a href="/shell/2021/05/10/zsh-config.html">如何使用 shell（3&#x2F;3）—— 配置 zsh</a></li></ol><h2 id="Fish-Friendly-Interactive-shell"><a href="#Fish-Friendly-Interactive-shell" class="headerlink" title="Fish (Friendly Interactive shell)"></a>Fish (Friendly Interactive shell)</h2><p>这是一个划时代的 shell ，开创了全新 shell 用户交互的体验。语法高亮，根据 manpage 来自动补全，自动建议都是它的首创</p><blockquote><p>2B 青年用 bash，普通青年用 zsh，文艺青年用 fish。</p></blockquote><p>fish 的默认配置是真的好用，配置文件是： <code>~/.config/fish/config.fish</code></p><p>fish 通过函数来设置行为： fish 完全使用函数定制行为，你可以通过添加一些特殊的函数定制 fish 的行为，例如 prompt，fish 没有 <code>PS1</code> 这类特殊变量，而是使用函数。</p><p>语法高亮，自动建议</p><pre class="highlight"><code># 语法高亮<span style="color: Tomato;">gr</span><span style="color: Gray;">ep</span># 路径建议<span style="color: MediumSeaGreen;">cat</span> <span style="color: Gray; text-decoration: underline;">/bin/ho</span><span style="color: Gray;">stname</span># 参数建议<span style="color: MediumSeaGreen;">grep</span> --in<span style="color: Gray;">vert-match</span></code></pre><p>路径折叠 <code>~/D/E/detail</code> 避免路径过长</p><p>易懂的语法，这个不是 POSIX shell 兼容，自己有自己的语法</p><p>不支持 here doc （当然 here string 也不支持）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> &lt;&lt; <span class="string">EOF &gt; file.txt</span></span><br><span class="line"><span class="string"># file content</span></span><br><span class="line"><span class="string">...</span></span><br><span class="line"><span class="string">EOF</span></span><br></pre></td></tr></table></figure><p>官方的解释是因为你可以用其它命令替代，例如：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">printf</span> <span class="string">&quot;\</span></span><br><span class="line"><span class="string"># file content</span></span><br><span class="line"><span class="string">...</span></span><br><span class="line"><span class="string">&quot;</span> &gt; file.txt</span><br></pre></td></tr></table></figure><p><code>printf</code> 但写法也可以在 bash 里面使用，但在细节上和 fish 是有区别</p><ul><li>fish 使用 <code>$status</code> 获取上一个命令的退出状态，而不是 <code>$?</code></li><li>fish 里面的数组计数是从 <code>1</code> 开始的，而不是大多数程序的 <code>0</code></li><li>fish 不使用 <code>&amp;&amp;</code> 以及 <code>||</code> 语法来组合命令，而是使用 <code>and</code> 和 <code>or</code> 命令：但 fish 3.x 已经支持了 <code>&amp;&amp;</code>, <code>||</code></li><li>fish 使用 <code>echo (echo 233)</code> 来代替 <code class="language-plaintext highlighter-rouge">echo <code>echo 233</code> </code></li></ul><p><code>fish_config</code> 的奇葩命令，这个会开启一个 http server ，在网页上配置。作为一个 shell ，我一直都觉得打开浏览器去设置 shell 就比较奇怪</p><h3 id="设置变量"><a href="#设置变量" class="headerlink" title="设置变量"></a>设置变量</h3><p>很多人都觉得 fish 设置变量太奇怪了。实际上是 POSIX shell 更奇怪才对</p><p>回顾一下 Shell 变量的作用域可以分为三种：</p><ul><li>有的变量只能在函数内部使用，这叫做局部变量（local variable）</li><li>有的变量可以在当前 Shell 进程中使用，这叫做全局变量（global variable）</li><li>而有的变量还可以在子进程中使用，这叫做环境变量（environment variable）</li></ul><p>我们通常知道的正常编程语言的变量：</p><ul><li><code>g / --global</code> 全局变量（默认），在本次运行环境中有效</li><li><code>l / --local</code> 局部变量，仅在当前作用域中有效</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">gvar=<span class="string">&quot;Global content changed&quot;</span></span><br><span class="line"><span class="comment"># 换成 fish 写法</span></span><br><span class="line"><span class="built_in">set</span> -g gvar <span class="string">&quot;Global content changed&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">local</span> lvar=<span class="string">&quot;Local content&quot;</span></span><br><span class="line"><span class="comment"># 局部变量 fish 写法</span></span><br><span class="line"><span class="built_in">set</span> -l lvar <span class="string">&quot;Local content&quot;</span></span><br></pre></td></tr></table></figure><p>环境变量（环境变量其实是普通的变量是否具有 <code>export</code> 属性）：</p><ul><li><code>x / --export</code> 该变量可以传递给子进程</li><li><code>u / --unexport</code> （默认）该变量不可以传递给子进程</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 平时最常使用的命令</span></span><br><span class="line"><span class="built_in">export</span> foo=xxx</span><br><span class="line"><span class="built_in">declare</span> -x foo=xxx</span><br><span class="line"><span class="built_in">typeset</span> -x foo=xxx</span><br><span class="line"></span><br><span class="line"><span class="comment"># fish 的写法</span></span><br><span class="line"><span class="built_in">set</span> -x foo xxx</span><br><span class="line"></span><br><span class="line"><span class="comment"># 去除环境变量（或者说去除变量的 export 属性）</span></span><br><span class="line"><span class="built_in">declare</span> +x foo=xxx</span><br><span class="line"><span class="built_in">typeset</span> +x foo=xxx</span><br><span class="line"></span><br><span class="line"><span class="comment"># fish 的写法</span></span><br><span class="line"><span class="built_in">set</span> -u foo xxx</span><br></pre></td></tr></table></figure><p><code>declare</code> 的操作符有 <code>+</code>,  <code>-</code> 两种：<code>-</code> 代表启用这个属性，<code>+</code> 代表取消这个属性。是的，我没有写错，他就是反的</p><p><code>export</code> 本质上就是 <code>declare -x</code> 命令</p><p>按照 GNU bash 文档的说法：提供 <code>typeset</code> 命令是为了与 Korn shell 兼容。它和 <code>declare</code> 命令是一样的。</p><p><code>POSIX shell</code> 储存的就是字符串，整数和数组只是一个特殊属性（<code>fish</code> 的变量属性不能添加和删除）。<br>这几个是比较常用的（完整的去看<a href="https://www.gnu.org/software/bash/manual/bash.html">官方的手册</a>，还可以进行大（<code>u</code> upcase）小（<code>l</code> lowcase）写转换等）</p><ul><li><code>a</code> 索引数组变量</li><li><code>A</code> 关联数组变量</li><li><code>f</code> 函数名</li><li><code>i</code> 该变量将被视为整数</li><li><code>r</code> 只读，不能更改不能 <code>unset</code></li><li><code>t</code> 调试，追踪这个变量。（大概是 bash 特有的）</li><li><code>x</code> 导出变量变成环境变量</li></ul><p>持久化和清除变量：</p><ul><li><code>U / --universal</code> 通用变量，当前用户下全部有效，并持久化（会保存）</li><li><code>e / --erase</code> 清除一个变量</li></ul><p><code>U</code> 相当于写在配置文件里，<code>e</code> 相当于 <code>unset</code> 命令</p><p>比如： <code>set -Ux EDITOR vim</code> 会让环境变量全局生效并持久化保存，即使 shell 重启也会保留。<br>推荐使用 <code>set -Ux</code> 保存常用环境变量，会自动保存到 <code>~/.config/fish/fish_variables</code> 里面</p><p>对数组的操作：</p><ul><li><code>a / --append</code> 将值追加到变量的当前数组。 可以与 <code>--prepend</code> 一起使用，以同时添加和添加。 分配给可变切片时，不能使用此功能。</li><li><code>p / --prepend</code> 这些值被预先设置为该变量的当前值集。 可以将它与 <code>--append</code> 一起使用，插入到数组最前面。 分配给可变切片时，不能使用此功能</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">PATH=<span class="string">&quot;<span class="variable">$PATH</span>:<span class="variable">$HOME</span>/go/bin&quot;</span></span><br><span class="line">PATH=<span class="variable">$PATH</span>:~/go/bin</span><br><span class="line"><span class="comment"># fish 的写法</span></span><br><span class="line"><span class="built_in">set</span> -a PATH ~/go/bin/</span><br><span class="line"></span><br><span class="line">PATH=<span class="string">&quot;<span class="variable">$HOME</span>/go/bin:<span class="variable">$PATH</span>&quot;</span></span><br><span class="line">PATH=~/go/bin:<span class="variable">$PATH</span></span><br><span class="line"><span class="comment"># fish 的写法</span></span><br><span class="line"><span class="built_in">set</span> -ap PATH ~/go/bin/</span><br></pre></td></tr></table></figure><p><code>~</code> 和 <code>$HOME</code> 并不是等价的，<code>PATH=&quot;$PATH:~/go/bin&quot;</code> 这种写法就是不行的。 <code>&quot;&quot;</code> 里面只会解释变量，不会对 <code>~</code> 展开。<br>还有 <code>~</code> 必须出现在表达式的最前面，否则它是普通的字符。 <code>:</code> 后面会被当成独立的表达式，所以也是正常的。推荐尽可能使用 <code>$HOME</code></p><p><code>NODE_ENV=production node index.js</code> 是无法在 fish 内使用的，可以换成一个更通用的写法，使用 <code>env</code> 命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">env</span> NODE_ENV=production node index.js</span><br></pre></td></tr></table></figure><h3 id="配置框架和插件管理"><a href="#配置框架和插件管理" class="headerlink" title="配置框架和插件管理"></a>配置框架和插件管理</h3><ul><li>Oh-my-fish</li></ul><p>类似于 oh-my-zsh 的配置框架，顺便附带了包管理器的功能</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">omf install [&lt;name&gt;|&lt;url&gt;]</span><br><span class="line">omf list</span><br></pre></td></tr></table></figure><p>当然，fish 默认已经非常好用了。不需要什么配置了，但还是可以从 oh-my-fish 配置里面找到一些更好的用法（比如类似 Elvish 的导航模式）</p><ul><li><a href="https://github.com/jorgebucaran/fisher">Fisher</a> 这个原来叫 fisherman</li></ul><p>这个是一个极为精简的 fish 插件管理器</p><h2 id="Elvish"><a href="#Elvish" class="headerlink" title="Elvish"></a>Elvish</h2><p>tuna 曾经的会长肖骐开发的 shell</p><p>截止到目前 Elvish 还没有发布 1.0 版</p><p>Elvish 对于交互的改进</p><ul><li><code>Ctrl-R</code> 对于搜索历史做了优化</li><li><code>Ctrl-L</code> 显示目录栈。相当于列表选择 <code>dirs -v</code> 进行跳转</li><li><code>Ctrl-N</code> 导航模式（Navigation mode） 这个是我最喜欢的功能，类似于 ranger 或 nnn 之类的文件管理功能，这个功能很棒</li></ul><p><img src="/assets/img/sh-compatibles-history/elvish-navigation-mode.png" alt="elvish-navigation-mode"></p><p>Elvish 也自己集成包管理</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">use epm</span><br><span class="line">epm:install &lt;package name&gt;</span><br></pre></td></tr></table></figure><h3 id="管道应该传递什么？"><a href="#管道应该传递什么？" class="headerlink" title="管道应该传递什么？"></a>管道应该传递什么？</h3><p>这里来自 Elvish 作者 的： <a href="https://zhuanlan.zhihu.com/p/24301670">elvish 的设计和实现 (零) shell 的优势和缺陷#管道的缺陷</a></p><p>Unix 的管道中传输的是一个接一个的字符，没有别的结构。<br>一个字符能表达的信息很有限；为了克服这一困难，大多数 Unix 工具把一行视为一条数据。<br>例如 <code>sed</code> 可以针对每一行进行替换等操作，<code>grep</code> 也是找出包含指定字符的所有行。这种「结构」不是管道内在的，而是工具约定的。</p><p>这样一来，数据本身包含换行符时就会有问题。例如这段删除当前目录及其任意层子目录下 <code>.bak</code> 文件的代码（不理解这段代码可以看 <a href="https://explainshell.com/explain?cmd=find+.+-name+%27*.bak%27+%7C+xargs+rm">这里</a>）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">find . -name <span class="string">&#x27;*.bak&#x27;</span> | xargs <span class="built_in">rm</span></span><br></pre></td></tr></table></figure><p>如果当前目录下有个文件名叫  <code>a\n.bak</code> ，那么 <code>rm</code> 就会尝试删除文件 <code>a</code> 和文件 <code>.bak</code>，而不是<br> <code>a\n.bak</code> 。这是因为 <code>xargs</code> 无法区分分隔数据的换行和数据内部的换行。</p><p>通常的做法是，让 <code>find</code> 用 <code>\0</code> 分隔输出的各项，并且让 <code>xargs</code> 在读入时也用 <code>\0</code> 分隔：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">find . -name <span class="string">&#x27;*.bak&#x27;</span> -print0 | xargs -0 <span class="built_in">rm</span></span><br></pre></td></tr></table></figure><p>在文件名的问题上我们还比较幸运，因为 Unix 文件名不允许包含 <code>\0</code> 。那如果我们要处理的数据里刚好也可能包含 <code>\0</code> 呢？</p><p>这种纯文本的处理总是会出现有歧义的情况，为了解决这种情况，所以要去传输个「类型」的字段</p><p>我也不知道该如何描述，借用 <a href="https://zhuanlan.zhihu.com/p/20625453">elvish 作者的描述</a>：分成 <strong>穷管道</strong> 和 <strong>富管道</strong></p><ul><li>穷管道就是传统的 raw 传输方式（POSIX shell 的传输方式）</li><li>富管道是一个有类型的句柄（一段数据加上类型，这就是两个字段了，或者说传递对象）</li></ul><p>对于都是内置命令的情况传递使用富管道，和外部命令交互传递穷管道保持兼容</p><p>富管道里面传输的是对象，比较有名的 PowerShell 也是这样管道里面传输 <code>dotnet</code> 对象</p><h2 id="Ion-Shell"><a href="#Ion-Shell" class="headerlink" title="Ion Shell"></a>Ion Shell</h2><p>这是一群疯狂的人造的轮子，Redox OS 的默认 shell</p><p>重点都在语法和执行效率上，用起来和默认 bash &#x2F; zsh 差不多，不兼容 POSIX shell。</p><p>Ion 对于数据重定向方面使用了新的语法 <code>^</code> ，我觉得这个改进或许并不好。或许配合上 Redox OS 这个也许是很不错的</p><p>标准的 IO 有三种：</p><ul><li><code>stdin</code> 标准输入 <code>/dev/stdin</code></li><li><code>stdout</code> 标准输出 <code>/dev/stdout</code></li><li><code>stderr</code> 标准错误 <code>/dev/stderr</code></li></ul><p>输出信息有 <code>stdout</code> 和 <code>stderr</code> 两种，可以分开来重定向</p><table><thead><tr><th>Name</th><th>POSIX shell</th><th>Fish</th><th>Elvish</th><th>Ion</th><th>PowerShell</th><th>Nushell</th></tr></thead><tbody><tr><td>从文件到标准输入</td><td><code>&lt;</code></td><td><code>&lt;</code></td><td><code>&lt;</code></td><td><code>&lt;</code></td><td><code>&lt;</code></td><td><code>&lt;</code></td></tr><tr><td>将输出写入到一个文件</td><td><code>&gt;</code> , <code>1&gt;</code></td><td><code>&gt;</code> , <code>1&gt;</code></td><td><code>&gt;</code> , <code>1&gt;</code></td><td><code>&gt;</code></td><td><code>&gt;</code> , <code>1&gt;</code></td><td><code>&gt;</code> , <code>1&gt;</code></td></tr><tr><td>输出追加写入到一个文件</td><td><code>&gt;&gt;</code> , <code>1&gt;&gt;</code></td><td><code>&gt;&gt;</code> , <code>1&gt;&gt;</code></td><td><code>&gt;&gt;</code> , <code>1&gt;&gt;</code></td><td><code>&gt;&gt;</code></td><td><code>&gt;&gt;</code> , <code>1&gt;&gt;</code></td><td><code>&gt;&gt;</code> , <code>1&gt;&gt;</code></td></tr><tr><td>将错误写入到一个文件</td><td><code>2&gt;</code></td><td><code>^</code> , <code>2&gt;</code></td><td><code>2&gt;</code></td><td><code>^&gt;</code></td><td><code>2&gt;</code></td><td><code>2&gt;</code></td></tr><tr><td>将错误追加写入到一个文件</td><td><code>2&gt;&gt;</code></td><td><code>^^</code> , <code>2&gt;&gt;</code></td><td><code>2&gt;&gt;</code></td><td><code>^&gt;&gt;</code></td><td><code>2&gt;&gt;</code></td><td><code>2&gt;&gt;</code></td></tr><tr><td>全部写入到一个文件</td><td><code>&amp;&gt;</code></td><td><code>&amp;&gt;</code></td><td>不支持</td><td><code>&amp;&gt;</code></td><td>不支持</td><td><code>&amp;&gt;</code></td></tr><tr><td>输出和错误写入到一个文件</td><td><code>&gt; file 2&gt;&amp;1</code></td><td><code>&gt; file 2&gt;&amp;1</code></td><td><code>&gt; file 2&gt;&amp;1</code></td><td>不支持</td><td>可以，但语意不同</td><td><code>&gt; file 2&gt;&amp;1</code></td></tr></tbody></table><p><strong>全部写入到一个文件</strong> 和 <strong>输出和错误写入到一个文件</strong> （<code>command &gt;file 2&gt;&amp;1</code> 或追加写入 <code>command &gt;&gt;file 2&gt;&amp;1</code>）是完全等价的</p><p>Fish 还支持 <code>&gt;?</code> , <code>^?</code> 如果文件存在则不写入输出，ion 还支持多个重定向 <code>command &gt; stdout ^&gt; stderr &amp;&gt; combined</code></p><h3 id="文件描述符-FD（File-Descriptor）"><a href="#文件描述符-FD（File-Descriptor）" class="headerlink" title="文件描述符 FD（File Descriptor）"></a>文件描述符 FD（File Descriptor）</h3><p>fish 和 ion 的 <code>^</code> 描述 <code>stderr</code> 语法看起来好多了。不过，为什么 POSIX shell 要用 <code>1</code> 和 <code>2</code> 来区分 <code>stdout</code> 和 <code>stderr</code> ？</p><p>在 Unix 系统当中，一切皆文件，每个进程默认会打开三个文件作为输入，输出和错误，前三个文件描述符为（顺序是恒定的）：</p><ul><li><code>0</code> ： <code>stdin</code></li><li><code>1</code> ： <code>stdout</code></li><li><code>2</code> ： <code>stderr</code></li></ul><p>通过这里，就很容易理解， Unix 系统的标准输入输出，本质上就是三个文件的读写， <code>1&gt;</code> , <code>2&gt;</code> 指的的是文件描述符。所以说用 <code>^</code> 来描述错误或许并不合理（这是一个约定，并非规则）</p><p>打开文件会分配 3 往后的唯一的 FD，也可以手动指定。（可以在 <code>/proc/self/fd</code> 里面看到 FD 和文件的对应关系）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># std.sh</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># stdin 自己对自己的输入</span></span><br><span class="line"><span class="built_in">echo</span> 000 &gt;&amp;0</span><br><span class="line"></span><br><span class="line"><span class="comment"># 打印 stdin 的数据</span></span><br><span class="line"><span class="built_in">echo</span> -n &lt;&amp;0</span><br><span class="line"></span><br><span class="line"><span class="comment"># stdout</span></span><br><span class="line"><span class="built_in">echo</span> 111 &gt;&amp;1</span><br><span class="line"></span><br><span class="line"><span class="comment"># stderr</span></span><br><span class="line"><span class="built_in">echo</span> 222 &gt;&amp;2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 打开文件， FD 设置为 3</span></span><br><span class="line"><span class="built_in">exec</span> 3&lt;&gt; /tmp/foo</span><br><span class="line"></span><br><span class="line"><span class="comment"># 刚打开了新的文件，可以看到映射关系</span></span><br><span class="line"><span class="built_in">ls</span> -l /proc/self/fd</span><br><span class="line"></span><br><span class="line"><span class="comment"># 写入内容</span></span><br><span class="line"><span class="built_in">echo</span> 333 &gt;&amp;3</span><br><span class="line"></span><br><span class="line"><span class="comment"># 关闭文件（关闭 3 这个 FD）</span></span><br><span class="line"><span class="built_in">exec</span> 3&gt;&amp;-</span><br></pre></td></tr></table></figure><p>这里注意：<code>sh -c &quot;echo 000 &gt;&amp;0; echo -n &lt;&amp;0&quot;</code> 在脚本内部传递数据， <code>stdin</code> 一直处于交互输入（使用 <code>cat</code> 会由于读不到文件结尾而阻塞）</p><p>而管道会写入 <code>stdin</code> 数据之后会有终止符 <code>EOF</code> ，这下面这种写法</p><ul><li><code>echo 000 | sh -c &quot;cat &lt;&amp;0&quot;</code></li><li><code>sh -c &quot;cat &lt;&amp;0&quot; &lt;&lt;&lt; 000</code></li><li><code>echo 000 | sh -c &quot;cat /dev/stdin&quot;</code> 当然，<code>/dev/stdin</code> 和 <code>&lt;&amp;0</code> 是一样的</li></ul><p>使用 <code>ls</code> 查看 <code>fd</code> 时要注意，由于 <code>ls</code> 的工作原理，会产生一个临时的 <code>fd</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">ls</span> -l /proc/self/fd</span><br><span class="line">total 0</span><br><span class="line">lrwx------ 1 metal metal 64 May  5 13:21 0 -&gt; /dev/pts/89</span><br><span class="line">lrwx------ 1 metal metal 64 May  5 13:21 1 -&gt; /dev/pts/89</span><br><span class="line">lrwx------ 1 metal metal 64 May  5 13:21 2 -&gt; /dev/pts/89</span><br><span class="line">lr-x------ 1 metal metal 64 May  5 13:21 3 -&gt; /proc/2663987/fd</span><br></pre></td></tr></table></figure><h2 id="PowerShell（pwsh）"><a href="#PowerShell（pwsh）" class="headerlink" title="PowerShell（pwsh）"></a>PowerShell（pwsh）</h2><p>PowerShell 是一个来自 Microsoft 的任务自动化和配置管理框架，由命令行 shell 和相关的脚本语言组成。</p><p>最初，它只是一个 Windows 组件，被称为 Windows PowerShell，在 2016 年 8 月 18 日，随着 PowerShell Core 的引入，目前分成两个版本：Windows PowerShell 和 跨平台的 PowerShell Core</p><p>PowerShell 离开了 Windows 似乎水土不服，一点也不好用。或许是我不会用的关系</p><p>PowerShell 的管道传递的是 <code>.net object</code>，而不是 <code>raw</code> 字符串</p><p>拥有的现代化的语法，类似 python 或 ruby 一类脚本语言的操作。所有的东西都看作 dotnet 对象（这点有点和 ipython， pry 类似）。可以去理解常见的数据结构</p><h2 id="NuShell"><a href="#NuShell" class="headerlink" title="NuShell"></a>NuShell</h2><p>和 PowerShell 思想很像，也可以理解数据结构，或者所是 Linux 下的  “PowerShell”</p><p>看这个默认的 <code>ls</code> ，这才是一个现在代化的 shell 应该有的样子。当然，这个 <code>ls</code> 是内置命令。Nushell 内置了大量的常用命令，给人眼前一亮的感觉</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">/Users/metal/Code/blog(master)&gt; <span class="built_in">ls</span></span><br><span class="line">────┬──────────────┬──────┬──────────┬──────────────</span><br><span class="line"> <span class="comment">#  │     name     │ type │   size   │   modified</span></span><br><span class="line">────┼──────────────┼──────┼──────────┼──────────────</span><br><span class="line">  0 │ 404.html     │ File │    398 B │ 3 months ago</span><br><span class="line">  1 │ Gemfile      │ File │   1.2 KB │ 3 months ago</span><br><span class="line">  2 │ Gemfile.lock │ File │   2.2 KB │ 3 months ago</span><br><span class="line">  3 │ README.md    │ File │    322 B │ 3 months ago</span><br><span class="line">  4 │ _config.yml  │ File │   2.0 KB │ 2 months ago</span><br><span class="line">  5 │ _drafts      │ Dir  │     96 B │ 3 months ago</span><br><span class="line">  6 │ _includes    │ Dir  │    160 B │ 3 months ago</span><br><span class="line">  7 │ _layouts     │ Dir  │     96 B │ 4 days ago</span><br><span class="line">  8 │ _posts       │ Dir  │   1.9 KB │ 4 days ago</span><br><span class="line">  9 │ _site        │ Dir  │   1.3 KB │ 4 days ago</span><br><span class="line"> 10 │ about.md     │ File │   1.6 KB │ 3 months ago</span><br><span class="line"> 11 │ assets       │ Dir  │    224 B │ 3 months ago</span><br><span class="line"> 12 │ equipment.md │ File │   8.3 KB │ 3 months ago</span><br><span class="line"> 13 │ favicon.ico  │ File │ 238.1 KB │ 3 months ago</span><br><span class="line"> 14 │ index.md     │ File │    239 B │ 3 months ago</span><br><span class="line"> 15 │ links.md     │ File │   1.7 KB │ 2 months ago</span><br><span class="line"> 16 │ new.rb       │ File │   1.1 KB │ 3 months ago</span><br><span class="line"> 17 │ projects.md  │ File │   7.7 KB │ 3 months ago</span><br><span class="line"> 18 │ vendor       │ Dir  │     96 B │ 3 months ago</span><br><span class="line">────┴──────────────┴──────┴──────────┴──────────────</span><br></pre></td></tr></table></figure><p>nushell 具有极其丰富的数据结构，就如同现在化的编程语言一样</p><p>支持了常用的数据格式：</p><ul><li>json</li><li>yaml</li><li>toml</li><li>xml</li><li>csv</li><li>ini</li></ul><p>有了更合理的语法：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">open people.txt | lines | <span class="built_in">split</span> column <span class="string">&quot;|&quot;</span> | str trim</span><br></pre></td></tr></table></figure><p>甚至集成了类似 curl 的命令（使用 nushell 理解并解析 xml）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">/home/metal/Code&gt; fetch https://a-wing.top/feed.xml | get feed.children.subtitle.children.0</span><br><span class="line">新一的个人博客</span><br></pre></td></tr></table></figure><h2 id="把-REPL（Read–Eval–Print-Loop）-当-shell-用"><a href="#把-REPL（Read–Eval–Print-Loop）-当-shell-用" class="headerlink" title="把 REPL（Read–Eval–Print Loop） 当 shell 用"></a>把 REPL（Read–Eval–Print Loop） 当 shell 用</h2><p>现在化的编程语言的交互式的环境，比如 <code>IPython</code>，<code>Pry</code> 当登录 shell 用好像也是可以的。<br>这不是传统意义上的 shell，但是经常有人建议用它做登录 shell。但启动实在是太慢了</p><p>还有，作为一个 shell，大部分时间都会被用来执行外部命令，每次都多打一个字符是很累的。</p><p>或许我们可以融合一下普通的 shell 和 REPL。比如：xonsh 融合 python 和 shell，既可以用 shell 写法，也可以直接使用 python。</p><p>类似的，也有融合其他语言的，比如融合了 <code>Node.js</code> 的 <a href="https://github.com/rikumi/fresh-shell">fresh-shell</a></p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://zhuanlan.zhihu.com/p/24301670">elvish 的设计和实现 (零) shell 的优势和缺陷</a></p><p><a href="https://zhuanlan.zhihu.com/p/20625453">管道、awk 和 JSON</a></p><p><a href="https://zhuanlan.zhihu.com/p/24372878">elvish 的设计和实现 (二) 语法基础和词法</a></p><p><a href="https://zhuanlan.zhihu.com/p/26157081">使用 fish 的一些注意点</a></p><p><a href="https://fishshell.com/docs/current/cmds/set.html">set - display and change shell variables - fish-shell 3.2.2 documentation</a></p><p><a href="https://doc.redox-os.org/ion-manual/html/pipelines.html#redirect-stdout">Ion Documentation</a></p><p><a href="https://mywiki.wooledge.org/FileDescriptor">FileDescriptor</a></p><p><a href="https://unix.stackexchange.com/questions/41421/what-is-the-file-descriptor-3-assigned-by-default">What is the file descriptor 3 assigned by default?</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;新时代的 &lt;code&gt;shell&lt;/code&gt; 改进的地方大体上分为两个部分：&lt;strong&gt;语法&lt;/strong&gt; 和 &lt;strong&gt;交互&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;/shell/2021/05/01/sh-compatibles-h</summary>
      
    
    
    
    <category term="shell" scheme="https://a-wing.top/categories/shell/"/>
    
    
    <category term="linux" scheme="https://a-wing.top/tags/linux/"/>
    
    <category term="shell" scheme="https://a-wing.top/tags/shell/"/>
    
  </entry>
  
  <entry>
    <title>如何使用 shell（1/3）—— shell 兼容和历史</title>
    <link href="https://a-wing.top/shell/2021/05/01/sh-compatibles-history"/>
    <id>https://a-wing.top/shell/2021/05/01/sh-compatibles-history</id>
    <published>2021-05-01T04:00:00.000Z</published>
    <updated>2021-05-01T04:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p><code>shell</code> 的名字和概念是从 Unix 的前身 Multics 发展过来的。应用程序通过 shell 来进行调用并被系统执行。</p><p>一般的 sh 指的是 bsh （Bourne shell），但 Bourne shell 并不是 bash。bash 是 Bourne Again Shell</p><p><img src="/assets/img/sh-compatibles-history/unix-shells-large.png" alt="unix-shells-large.png"></p><ol><li>如何使用 shell（1&#x2F;3）—— shell 兼容和历史</li><li><a href="/shell/2021/05/05/new-shell.html">如何使用 shell（2&#x2F;3）—— 新时代的 shell</a></li><li><a href="/shell/2021/05/10/zsh-config.html">如何使用 shell（3&#x2F;3）—— 配置 zsh</a></li></ol><h2 id="Thompson-shell"><a href="#Thompson-shell" class="headerlink" title="Thompson shell"></a>Thompson shell</h2><p>Thompson shell（即 V6 Shell）是历史上第一个 Unix shell，1971 年由 Ken Thompson 写作出第一版并加入 UNIX 之中。<br>它是一个简单的命令行解释器，但不能被用来运行 Shell script 。<br>它的许多特征影响了以后命令行界面的发展。至 Unix V7 之后，被 Bourne shell 取代。</p><p>当时关键字还只有 <code>if</code> and <code>goto</code> 两种</p><p>管道和重定向就是那个时候做出来的</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 管道</span></span><br><span class="line">command1 | command2</span><br><span class="line"><span class="comment"># 输出重定向</span></span><br><span class="line">command1 &gt; file1</span><br></pre></td></tr></table></figure><h2 id="PWB-shell"><a href="#PWB-shell" class="headerlink" title="PWB shell"></a>PWB shell</h2><p>PWB shell 是 Thompson shell 的一个改进版本，完全兼容 Thompson shell</p><p>为了解决硬编码的问题引入来这两个变量： <code>$HOME</code> , <code>$PATH</code></p><p>加入双引号 <code>&quot;&quot;</code> 里面放变量的做法</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">VAR=World</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Hello <span class="variable">$VAR</span>&quot;</span></span><br><span class="line"><span class="comment"># hello world</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 或者使用这样消除歧义的写法</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Hello <span class="variable">$&#123;VAR&#125;</span>&quot;</span></span><br></pre></td></tr></table></figure><p>PHP 也是参照这一设计</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="variable">$VAR</span>=<span class="string">&quot;World&quot;</span>;</span><br><span class="line"><span class="keyword">echo</span> <span class="string">&quot;Hello <span class="subst">$VAR</span>&quot;</span>;</span><br><span class="line"><span class="comment">// hello world</span></span><br></pre></td></tr></table></figure><p>Perl 也是这样</p><figure class="highlight perl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">$VAR</span> = <span class="string">&quot;World&quot;</span>;</span><br><span class="line"><span class="keyword">print</span> <span class="string">&quot;Hello <span class="variable">$VAR</span>&quot;</span>;</span><br></pre></td></tr></table></figure><p>这时的语法还是 <code>if-then-else-endif</code> 语法</p><h2 id="Bourne-shell"><a href="#Bourne-shell" class="headerlink" title="Bourne shell"></a>Bourne shell</h2><p>Bourne shell 于 1979 年作为 Unix V7 的一部分首次公开发布。（几乎所有的 Unix 和 类Unix 系统都是 Unix V7 的后代）</p><p>Bourne shell 并没有去兼容 Thompson shell 。然后 <code>case ~ in ~ esac</code> 这个诡异的语法就诞生了</p><p>Here Documents 也是这个时候诞生的 <code>cat &lt;&lt;-EOF</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> &gt; file1 &lt;&lt; <span class="string">EOF</span></span><br><span class="line"><span class="string">233</span></span><br><span class="line"><span class="string">EOF</span></span><br></pre></td></tr></table></figure><p>还有就是增加了 <code>command</code> 子命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">&quot;两数之和为 : `expr 2 + 2`&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Hello `echo World`&quot;</span></span><br><span class="line"></span><br><span class="line">go run `<span class="built_in">ls</span> *.go | grep -v _test.go`</span><br></pre></td></tr></table></figure><hr><p>Bourne shell 取代了早期的 Thompson shell，发生在 Unix 历史的早期，现在几乎被遗忘。不过，为什么 Bourne shell 替代了 Thompson shell？</p><p>Ken Thompson 不想继续维护 shell。还有一个问题是 Thompson shell 不足以适应 unix 的发展，PWB shell 难以解决 Thompson shell 自己的问题。或者说当时的 shell 只是一个用户的接口。就像我们今天看到的 web 界面一样的微不足道的地方。当时大概没有人想到 shell 会变成一门语言，或是一个标准</p><h2 id="Csh"><a href="#Csh" class="headerlink" title="Csh"></a>Csh</h2><p>1978 年，Bill Joy 还在加州大学伯克利分校读书的时候，就为 BSD UNIX（Berkeley Software Distribution UNIX）开发了 C Shell。</p><p>C shell 的 C 是来自于 c 语言，语法更接近 c 语言， 但并不和 Bourne shell 兼容</p><p>csh 对交互使用做出了巨大的改进</p><ul><li>命令历史</li><li><code>~</code> 出现了作为 <code>$HOME</code> 的另一种写法</li><li><code>alias</code> 命令</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">alias</span> gc=<span class="string">&#x27;git commit&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Overriding aliases</span></span><br><span class="line"><span class="built_in">alias</span> <span class="built_in">ls</span>=<span class="string">&#x27;ls -la&#x27;</span></span><br></pre></td></tr></table></figure><ul><li>最近访问目录栈<ul><li><code>dirs</code> 显示当前目录栈 <code>dirs -v</code></li><li><code>pushd</code> 入栈，切换当然目录（等同 <code>cd &amp;&amp; dirs</code> ）<ul><li><code>pushd</code> 没有参数时和 <code>cd -</code> 一样效果</li><li><code>pushd</code> 和 <code>cd</code> 参数完全一样 <code>cd -1</code> , <code>cd +2</code> , <code>cd -0</code>, <code>pushd +0</code>, <code>pushd -2</code></li></ul></li><li><code>popd</code> 出栈，并切换到栈顶部的目录</li></ul></li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">[user@server /etc] $ <span class="built_in">dirs</span></span><br><span class="line">/usr/bin</span><br><span class="line">[user@server /usr/bin] $ <span class="built_in">pushd</span> /etc</span><br><span class="line">/etc /usr/bin</span><br><span class="line">[user@server /etc] $ <span class="built_in">popd</span></span><br><span class="line">/usr/bin</span><br></pre></td></tr></table></figure><ul><li>任务控制<ul><li><code>Ctrl-z</code> 将信号 <code>SIGTSTP</code> 发送到当前任务，该任务将挂起，并放到后台</li><li><code>bg</code> 把挂起的任务继续执行（在后台执行）</li><li><code>fg</code> 把后台任务放到前台执行，如果是挂起状态也恢复执行</li><li><code>&amp;</code> 放到后台运行</li></ul></li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">sheep 10</span><br><span class="line"><span class="comment"># Ctrl-z 暂停这个任务</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># bg 命令把他放到后台运行</span></span><br><span class="line"><span class="built_in">bg</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 也可以直接放到后台运行</span></span><br><span class="line"><span class="built_in">sleep</span> 10 &amp;</span><br><span class="line"><span class="comment"># fg 把后台最上面的放到前台运行</span></span><br><span class="line"><span class="built_in">fg</span></span><br></pre></td></tr></table></figure><h2 id="Tcsh"><a href="#Tcsh" class="headerlink" title="Tcsh"></a>Tcsh</h2><p>Csh 出现的五年之后 卡内基-梅隆大学 的 Ken Greer 引入了 Tenex 系统中的一些功能，如命令行编辑功能和文件名和命令自动补全功能。开发了 Tenex C shell（tcsh）</p><p>在 csh 的基础上增加了</p><ul><li>行编辑</li><li>命令补全</li></ul><p><code>!!</code> 执行之前的命令</p><p><code>!n</code> 执行之前执行的 n 条命令</p><p>Mac OS X 在 10.1 puma 的时候默认 shell 是 tcsh</p><p>tcsh 直到今天还在维护，我们可以在 Github 看到代码：<a href="https://github.com/tcsh-org/tcsh">tcsh-org&#x2F;tcsh</a></p><h2 id="KornShell"><a href="#KornShell" class="headerlink" title="KornShell"></a>KornShell</h2><p>KornShell（ksh）是由 David Korn 在 1980 年代初期由贝尔实验室（Bell Labs）开发，并于 1983 年 7 月 14 日在 USENIX 上发布（差不多跟 Tenex C Shell 同时发布）。</p><p>最初的开发基于 Bourne shell 源代码。早期贝尔实验室的贡献者 Mike Veach 和 Pat Sullivan，他们分别开发了 <code>emacs</code> 和 <code>vi</code> 风格的行编辑模式。</p><p>KornShell 与 Bourne Shell 向后兼容，并受贝尔实验室用户的要求和启发，包括 C Shell 的许多功能。 最引人瞩目的特性就是支持脚本编程。</p><p>现在的 zsh&#x2F;bash 也有这两种行编辑模式：默认是 <code>emacs</code> 风格</p><ul><li><code>Ctrl-b</code> 左移光标</li><li><code>Ctrl-f</code> 右移光标</li><li><code>Ctrl-p</code> 查看上一条命令（或上移光标）</li><li><code>Ctrl-n</code> 查看下一条命令（或下移光标）</li><li><code>Ctrl-a</code> 移动光标至行首</li><li><code>Ctrl-e</code> 移动光标至行尾</li><li><code>Ctrl-w</code> 删除前一个词</li><li><code>Ctrl-u</code> 删除从光标至行首的内容</li><li><code>Ctrl-k</code> 删除从光标至行尾的内容</li><li><code>Ctrl-y</code> 粘贴已删除的文本（例如粘贴 <code>Ctrl-u</code> 所删除的内容）</li><li><code>Alt-right</code>, <code>Alt-f</code>, <code>Ctrl-right</code> 向右移动一个词</li><li><code>Alt-left</code>, <code>Alt-b</code>, <code>Ctrl-left</code> 向左移动一个词</li></ul><p>不过在 Apple 的设备里移动一个词只有 <code>Alt-right</code>, <code>Alt-left</code> 是可以使用的</p><p>我认为这里面用的最多的就是移动到 <code>Ctrl-a</code> （移动到行首，顺便加个 <code>sudo</code>）和 <code>Ctrl-e</code> （移动到行尾）</p><p>也可以使用 <code>set -o vi</code> 切换成 <code>vi</code> 风格的操作，我个人是基本不使用这个，因为绝大部分情况只有一行，需要仔细编辑的长命令太少了。最蛋疼的地方在于 <code>vi</code> 风格的操作是有模式的，而且看不到模式状态，只能靠盲打</p><hr><p>甚至还支持关联数组（Associative Array）。关联数组是个啥？在现代化的脚本语言（Ruby 和 Python 之类）里面很常见，又称映射（Map）、字典（Dictionary）。或者说是哈希表（Hashmap）（严格的来说哈希表只是实现关联数组的一个实现）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">declare</span> -A filetypes=([txt]=text [sh]=shell [mk]=makefile)</span><br><span class="line">filetypes[c]=<span class="string">&quot;c source file&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取关联数组的长度</span></span><br><span class="line"><span class="built_in">echo</span> <span class="variable">$&#123;#filetypes[*]&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 输出键为 txt 的值</span></span><br><span class="line"><span class="built_in">echo</span> <span class="variable">$&#123;filetypes[txt]&#125;</span></span><br></pre></td></tr></table></figure><h2 id="POSIX-shell"><a href="#POSIX-shell" class="headerlink" title="POSIX shell"></a>POSIX shell</h2><p>POSIX shell 是基于 1988 年版本的 KornShell（<code>ksh88</code>），而 KornShell 又是为了取代 AT&amp;T Unix 上的 Bourne shell，在功能上超过了 BSD 的 C shell。<br>就 ksh 是 POSIX shell 的祖先而言，大多数 Unix 和类似 Unix 的系统今天都包括 Korn shell 的一些变体。<br>例外的情况一般是微小的嵌入式系统，它们无法承受一个完整的 POSIX shell 所需要的空间。</p><p>我们今天说的 POSIX shell 兼容就是这个时候衍生出的标准</p><h2 id="Ash"><a href="#Ash" class="headerlink" title="Ash"></a>Ash</h2><p>Almquist shell (也称为 A shell、ash 和 sh) 是一种轻量级 Unix shell（最大的特点就是轻量化）。<br>最初由 Kenneth Almquist 在20 世纪 80 年代末编写。它在 BSD 版本的 Unix 取代了 Bourne shell</p><p>BSD 世界之外有两个重要的分支：</p><ol><li><p><code>dash</code> (Debian Almquist shell) 在 2006 年被 Debian 和 Ubuntu 作为默认的 <code>/bin/sh</code> 实现。（Bash 仍然是 Debian 衍生产品中的默认交互式命令 shell。）</p></li><li><p>BusyBox 中的 <code>ash</code> 命令，在嵌入式 Linux 中经常使用，可用于实现 <code>/bin/sh</code>。由于它是在 <code>dash</code> 之前发布的，并且它是从 Debian 的旧 <code>ash</code> 软件包衍生而来的，所以我选择将其视为 <code>dash</code> 的派生形式，而不是 <code>ash</code>，尽管它在 BusyBox 中是命令名称也是 <code>ash</code></p><p> （BusyBox 还包括一个叫 <code>hush</code> 的 <code>ash</code> 替代品，功能较弱。通常，在给定的 BusyBox 二进制文件中将仅内置这两者之一：默认情况下为 <code>ash</code>，但当空间非常狭窄时则采用 <code>hush</code>。因此，基于 BusyBox 的 <code>/bin/sh</code> 系统并不总是像 <code>dash</code> 一样。）</p></li></ol><p>嵌入式 Linux 系统，或以 Linux 为基础的路由系统：OpenWRT 之类的都 BusyBox <code>ash</code></p><h2 id="Rc-shell"><a href="#Rc-shell" class="headerlink" title="Rc shell"></a>Rc shell</h2><p>rc（Run Commond）是 Bell Labs 操作系统的 Unix V10 和 Plan 9 的命令行解释器。<br>它类似于Bourne shell，但是其语法稍微简单一些。 由 Tom Duff 开发，这个并不是 POSIX shell 兼容</p><h2 id="Bash-Bourne-Again-Shell"><a href="#Bash-Bourne-Again-Shell" class="headerlink" title="Bash (Bourne Again Shell)"></a>Bash (Bourne Again Shell)</h2><p>Bash（Bourne Again Shell）是 Brian Fox 为 GNU 项目编写的 Unix Shell 和命令语言，是 Bourne Shell 的免费软件替代品。<br>它于 1989 年首次发布，被用作大多数 Linux 发行版的默认登录 shell。</p><p>Bash 也是个双关词。也有 “Born Again（重生）shell” 的意思</p><p>Korn Shell 原本是专有软件，直到 2000 年，它才（遵照通用公共许可协议）作为开源软件发布</p><p>BSD Unix 避开了它，而选择了 C shell，而且在 Linux 起步时，它的源代码也不能免费提供给 Linux 使用。<br>因此，当早期的 Linux 发行版去寻找一个 shell 来搭配他们的 Linux 内核时，他们通常会选择 GNU Bash</p><p>自 2002 年发布 Mac OS X 10.2 Jaguar 以来，Mac OS X 也换成了 Bash</p><p>Linux 与 Bash 之间的这种早期联系几乎决定了许多其他 shell 的命运，包括 ksh、csh 和 tcsh。今天仍有一些死忠粉在使用这些 shell。</p><p>signal 处理 (使用 <code>trap</code>)</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 捕获 INT 信号来实现安全的退出</span></span><br><span class="line"><span class="comment"># trap ctrl-c and call ctrl_c()</span></span><br><span class="line"><span class="built_in">trap</span> ctrl_c INT</span><br><span class="line"><span class="keyword">function</span> <span class="function"><span class="title">ctrl_c</span></span>() &#123;</span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;** Trapped CTRL-C&quot;</span></span><br><span class="line">  <span class="built_in">exit</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>有 Debug 模式</p><ul><li><code>bash -x script.sh</code> 来运行脚本也可以 <code>-v</code> 输出更详细的信息</li><li><code>set -x</code> 来启用 debug 模式</li><li><code>set +x</code> 来禁用 debug 模式</li></ul><p>或者可以这样说：Here String 是 Here Document 的一个变种</p><p>Bash 可以使用 <code>&lt;&lt;&lt;</code> 操作符从 “here string” 重定向标准输入(stdin)。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> &lt;&lt;&lt; <span class="string">233</span></span><br></pre></td></tr></table></figure><h2 id="Zsh"><a href="#Zsh" class="headerlink" title="Zsh"></a>Zsh</h2><p>Paul Falstad 于 1990 年在普林斯顿大学读书时编写了 Zsh 的第一版。 zsh 的名称源自耶鲁大学教授 Zhong Shao（邵中，没错是华人，当时是普林斯顿大学的助教）的名字 Paul Falstad 认为 Shao 的登录 ID“ zsh” 是 shell 的好名字。</p><p>还有一种网上流传的说法是：z 是字母表的最后一个字母。zsh 的意思就是最后的 shell。或者从现在来看，zsh 大概是 POSIX shell 里最后一代的 shell 了</p><p>MacOS X 15 Catalina 将默认 shell 程序更改为 Zsh。在 2009 年 2 月，Bash 4.0 将其许可证切换到 GPLv3。<br>一些用户怀疑这种许可变更是 MacOS 继续使用旧版本的原因随着 2019 年 MacOS Catalina 的发布，苹果改变了默认 shell</p><hr><p>Zsh 的模拟模式（emulation mode）<code>emulate sh</code></p><p>Zsh 是与 Bash 兼容的。这种说法既对，也不对，由于 Zsh 自己做为一种脚本语言，是与 Bash 不兼容的。<br>符合 Bash 规范的脚本没法保证被 Zsh 解释器正确执行。因此，Zsh 实现中包含了一个模拟模式（emulation mode），支持对两种主流的 Bourne 衍生版 shell（bash、ksh）和 C shell 的模拟 （csh 的支持并不完整）。</p><p>在 Bash 的 emulation mode 下，可使用与 Bash 相同的语法和命令集合，从而达到近乎彻底兼容的目的。使用对 Bash 的模拟，需要显式执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">emulate</span> bash</span><br></pre></td></tr></table></figure><h2 id="默认的系统-shell-和兼容"><a href="#默认的系统-shell-和兼容" class="headerlink" title="默认的系统 shell 和兼容"></a>默认的系统 shell 和兼容</h2><p>在许多系统中，默认的交互式命令 shell 程序和 <code>/bin/sh</code> 是不同的东西。 <code>/bin/sh</code> 可能是：</p><ul><li>原始的 Bourne shell。这在较早的 UNIX 系统中很常见，例如 Solaris 10（于 2005 年发行）及其前身。</li><li>Korn Shell（ksh93），例如 OpenBSD，Solaris 11（2010）。</li><li>各种 Ash，前几年各大 Linux 发行版还是 Ash（执行脚本用 ash ，交互控制用 Bash） 。现在很多都是 Bash。</li><li>GNU Bash，在称为 sh 时会禁用其大多数非 POSIX 扩展。</li></ul><p>所有这些历史解释了为什么像 bash、zsh 和 <a href="https://github.com/magicant/yash">yash（yet another shell）</a> 这样的后起之秀的创造者会选择使它们与 sh 兼容。<br>Bourne&#x2F;POSIX 兼容性是类 Unix 系统的 shell 必须提供的最低限度，以获得广泛的采用。这就是 <code>sh-compatibles</code></p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://www.debian.org/doc/manuals/debian-reference/ch01.zh-cn.html#list-of-shell-programs">Debian Reference 第 1 章 GNU&#x2F;Linux 教程</a></p><p><a href="https://wiki.archlinux.org/index.php/zsh">Archlinux Zsh</a></p><p><a href="https://unix.stackexchange.com/questions/145522/what-does-it-mean-to-be-sh-compatible">What does it mean to be “sh compatible”?</a></p><p><a href="https://www.quora.com/Why-did-the-Bourne-shell-replace-the-Thompson-shell">Why did the Bourne shell replace the Thompson shell?</a></p><p><a href="https://docs.oracle.com/cd/E23824_01/html/E24456/userenv-1.html">Transitioning From Oracle Solaris 10 to Oracle Solaris 11</a></p><p><a href="https://tldp.org/LDP/Bash-Beginners-Guide/html/sect_02_03.html">Debugging Bash scripts</a></p><p><a href="https://tangentsoft.com/misc/unix-shells-large.png">tangentsoft.com&#x2F;misc&#x2F;unix-shells-large.png</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;code&gt;shell&lt;/code&gt; 的名字和概念是从 Unix 的前身 Multics 发展过来的。应用程序通过 shell 来进行调用并被系统执行。&lt;/p&gt;
&lt;p&gt;一般的 sh 指的是 bsh （Bourne shell），但 Bourne shell 并不是 bash</summary>
      
    
    
    
    <category term="shell" scheme="https://a-wing.top/categories/shell/"/>
    
    
    <category term="linux" scheme="https://a-wing.top/tags/linux/"/>
    
    <category term="shell" scheme="https://a-wing.top/tags/shell/"/>
    
  </entry>
  
  <entry>
    <title>2020 年的总结</title>
    <link href="https://a-wing.top/self/2021/04/01/2020"/>
    <id>https://a-wing.top/self/2021/04/01/2020</id>
    <published>2021-04-01T00:00:00.000Z</published>
    <updated>2021-04-01T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>1565 年，法国国王查理九世颁布法令改以1月1日为一年的开端，改变了过去以4月1日为新年的传统。<br>一些守旧派反对该改革，依然按照旧历法在新历4月1日送新年礼，庆祝新年。支持新年改革的人借此对守旧派大加嘲弄，在当天给他们送假礼物，邀请他们参加假聚会。<br>从此在4月1日捉弄人的习惯便流传开来。当有人上当受骗的时候，捉弄他的人就会高呼 “Poisson d’avril（四月恶作剧）”。</p></blockquote><p>顺便把 2019 年的总结也做了。反正没有发生什么。我就是觉得我改写个总结了。总是在忙</p><p>四月做总结好像不太合理。时间顺序感觉不太好）</p><h2 id="生活"><a href="#生活" class="headerlink" title="生活"></a>生活</h2><p>2019 年初猫丢了这大概是不幸的开端了，虽说 24 岁有逆流。。。这年过的真是惨。不喜欢卖惨，就少讲点</p><h3 id="肝生病"><a href="#肝生病" class="headerlink" title="肝生病"></a>肝生病</h3><p>肝生病，就是平时太肝的结果。（不过还好：肝，是人体唯一一个可以再生的器官）</p><p>生病了，灰色的生活。每一天都是灰色的，我觉得我已经命不久矣。我需要做一些什么，不然以后我的墓志铭上就只能空空如也</p><p>我几乎不生病，我一生病就吃很多的东西，很快就好了。。。但是这次我不能吃东西了</p><p>每天最开心的事就是下班之后看吃播，这个妹子吃了好多东西啊。好几人份 KFC 啊。b站 大祥哥又吃螃蟹龙虾了</p><p>看着他们吃的津津有味，我好像已经饱了</p><p>自从生病之后每一天都是灰色的，直到来了个临时室友，和我一起去爬山，拉着我去游泳</p><p>仿佛世界一下变成了彩色的。螺蛳粉实在是太好吃了</p><p><img src="/assets/img/2020/luosifen.jpeg" alt="luosifen"></p><h3 id="搬家"><a href="#搬家" class="headerlink" title="搬家"></a>搬家</h3><p>搬了一次家，用公司的名义和同事一起租下了好多间。差不多有一层楼。。。</p><p>我和你讲，在保持民主的情况下让大家意见保持统一真是一件困难的事啊。。</p><p>不管怎么做都会有人不同意。（最好的办法就是什么都不做，<del>看，官员无为是占优策略</del></p><p>如果再有一下外部因素，房东再隔三差五搞点事情</p><p>我深刻体会到那些搞政治的的不容易，大家意见就是达不成统一</p><p>组织业主委员会的才是真的大佬（那得是有多闲，去组织这事</p><p>我觉得独裁挺好的。至少效率很高</p><h3 id="疫情之下"><a href="#疫情之下" class="headerlink" title="疫情之下"></a>疫情之下</h3><p>再之后疫情就来了。去年报名了新加坡马拉松，宜宾马拉松都取消了</p><p>马拉松都变成了线上的</p><p>疫情的原因再家里工作，每天空闲时间，就是在尝试着翻译一本关于 WebRTC 的书</p><p>后来终于在 2020.0404 这天，把这个完成了</p><div class="twitter-wrapper"><blockquote class="twitter-tweet"><a href="https://twitter.com/_a_wing/status/1246129464070647808"></a></blockquote></div><script async defer src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><h3 id="脚受伤"><a href="#脚受伤" class="headerlink" title="脚受伤"></a>脚受伤</h3><p>2020 五月末的最后一天，我参加了线上全程马拉松，跑 42km</p><p>哪天刚下完雨，还是比较湿滑的。然后跑到 25km 的时候，我脚下滑了一下，直接空中转体了 360 度。</p><p>然后脚踝就扭伤了。最后走路完成了后半程。一共花了近七个小时。。。</p><p>后来这个扭伤养近三个月</p><h2 id="旅行"><a href="#旅行" class="headerlink" title="旅行"></a>旅行</h2><p>珠三角已经不能算旅游了，唯一的遗憾就是没有去过珠海吧</p><h3 id="黄果树马拉松之旅"><a href="#黄果树马拉松之旅" class="headerlink" title="黄果树马拉松之旅"></a>黄果树马拉松之旅</h3><p>黄果树马拉松，作为马拉松比赛，简直差极了。在一条公路上来回跑 20km</p><p>沿途风景？抱歉没有。离黄果树瀑布还很远那</p><p>当然比完赛就去黄果树瀑布。小时候看到地上有个叫黄果树的烟盒，从此对黄果树有着无限的向往</p><p><img src="/assets/img/2020/huangguoshu.jpeg" alt="huangguoshu"></p><p>黄果树景区里面有三个景点：陡坡塘，大瀑布，天星桥。个人推荐的顺序是：陡坡塘—天星桥—大瀑布</p><p>陡坡塘很小，很快就游览玩。天星桥很大，要走很久。压轴瀑布的要放在最后。一天就可以都逛完，第二天直接去龙宫景区（大概半天就可以逛完）</p><h3 id="韩国济州岛"><a href="#韩国济州岛" class="headerlink" title="韩国济州岛"></a>韩国济州岛</h3><p>去香港已经不算旅游了，过个关就到了。同理去广州也不算旅游</p><p>从香港飞到韩国，然后飞到东北老家过年</p><p><img src="/assets/img/2020/jeju.jpeg" alt="jeju"></p><p>我就是喜欢每天玩这种极限操作，那天在韩国的济州岛（济州岛对中国公民是免签）玩了一天</p><p>会不会韩语没有关系，干就完了（我的入境时间和出境时间是同一天，还被海关特别关照了一下）</p><p>正宗的石锅拌饭和泡菜真的好吃</p><p><img src="/assets/img/2020/bibimbap.jpeg" alt="bibimbap"></p><h3 id="三亚之旅"><a href="#三亚之旅" class="headerlink" title="三亚之旅"></a>三亚之旅</h3><p>我想考潜水证。去三亚旅游顺便考潜水证。</p><p>自从学会了游泳，现在想下海潜水</p><p>潜水大概分为自由潜水（带着泳镜和脚蹼下海）和水肺潜水（背气罐下海）</p><p>体验了下水肺潜水后，还是想学自由潜水，未来准备去考  AIDA 的潜水证</p><p><img src="/assets/img/2020/sanya.jpeg" alt="sanya"></p><p>未来我还会上天的</p><p>为什么少数民族能歌善舞？</p><p>并不是能歌善舞，而是被包装出来的。所谓文化景区只不过是被商业化包装出来的产品</p><p>台上的舞蹈表演是专业的演员在表演，毕竟民族特色舞蹈丝毫吸引不了关注的眼球</p><h2 id="技术"><a href="#技术" class="headerlink" title="技术"></a>技术</h2><h3 id="工作学习"><a href="#工作学习" class="headerlink" title="工作学习"></a>工作学习</h3><p>公司项目上 mqtt 作为主要通信协议</p><p>研究和使用 webrtc</p><p>学习 SICP。我觉得我学的不够深入</p><p>学会了如何使用 go 语言</p><p>了解了一点 react</p><p>坏了，没怎么看书学习。。。今年多看点书</p><p>未来准备学习一下 Rust</p><h3 id="开源项目"><a href="#开源项目" class="headerlink" title="开源项目"></a>开源项目</h3><p>2019 改装家里的门，更新 argus 项目</p><p>翻译一本关于 WebRTC 的书</p><p>Archlinuxcn 的 kiss2u 项目使用 golang 重构了一遍</p><p>文件传输工具 filegogo 项目发布</p><p>我觉得做的太多了，导致质量不够好。2021 最多只开一个新坑。或者一个都不开</p><p>filegogo 项目还要接着开发，我还想支持命令行版的，还有针对于 Symmetric NAT 的打洞（那天看了个关于这个的 paper ，想去实现一下）</p><h3 id="活动"><a href="#活动" class="headerlink" title="活动"></a>活动</h3><p>参加了 rust conf 和 ruby meetup。在 ruby meetup 分享了 WebRTC 相关的东西</p><p><img src="/assets/img/2020/rustccconf.jpeg" alt="rustccconf"></p><h2 id="技能"><a href="#技能" class="headerlink" title="技能"></a>技能</h2><p>滑板技能我差不多要放弃了。住的地方附近没有适合玩滑板的地方。</p><p><code>Ollie</code> 这个动作我实在是学不会。</p><h3 id="剪辑视频"><a href="#剪辑视频" class="headerlink" title="剪辑视频"></a>剪辑视频</h3><p>学习了 final cut pro 。自己制作的人生中第一个滑雪视频</p><iframe width="560" height="315" src="//player.bilibili.com/player.html?aid=85234887&bvid=BV1k7411671Z&cid=145691840&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe><p>说起来刚过去的雪季我拍的素材到现在还没有做成视频。。。</p><h3 id="滑雪"><a href="#滑雪" class="headerlink" title="滑雪"></a>滑雪</h3><p>应该不算新学的技能，不过我的滑雪技术提高了很多。尤其是在刚过去的雪季里受到了一个大佬的指点</p><p>我只会滑双板或者下次可以学习单板滑雪</p><p><img src="/assets/img/2020/lianhuashan.jpeg" alt="lianhuashan"></p><p>说起滑雪，前几天就在我眼前两个人相撞了，飞出了十几米远。每天在高级雪道上滑都快忘记了滑雪是高危运动了。</p><p>仿佛突然知道了什么叫害怕，我在恐惧雪道，之后的几次每次都会摔。要对雪道保持敬畏，不然说不定哪天我就没了。（滑雪场每年都会撞死几个。淹死的都是会水的，撞死的都是滑雪高手）</p><h3 id="游泳"><a href="#游泳" class="headerlink" title="游泳"></a>游泳</h3><p>专业以外的技能 2020 只学习了一项——游泳</p><p>第一次下水游泳，我原来是旱鸭子</p><p>你知道如何在 996 的情况下每天去游泳吗？</p><p>每天要在 7:00 前起床，骑车去游泳馆，游到 8:30 。去吃早餐。然后上班</p><p>晚上要在 11:00 前睡觉，不然第二天早上根本起不来</p><p>后来又参加了游泳比赛</p><p><img src="/assets/img/2020/swimming_competiton.jpeg" alt="swimming competiton"></p><h2 id="未来"><a href="#未来" class="headerlink" title="未来"></a>未来</h2><p>现在是我人生中重要的转折点了，五年后。我该是什么样子就是我之后努力的方向</p><p>我现在想任性一把，把 filegogo 做成我理想中的工具</p><p>还想学习滑翔伞和跑酷，继续学习冲浪（好像都是高危运动。说起来喝水好像也挺危险的，很容易呛死）</p><p>恢复跑步，我都快失去马拉松的参赛资格了。未来目标： 4 个小时完成全马</p><p>我喜欢户外，喜欢极限运动。每一次活着回来就会有一种重获新生的感觉</p><p>想去南极。想去跑 世界马拉松挑战赛 （World Marathon Challenge）7天7大洲7个马拉松</p><p>争取有朝一日，可以用这种破铁皮放满整面墙</p><p><img src="/assets/img/2020/medals.jpeg" alt="medals"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;1565 年，法国国王查理九世颁布法令改以1月1日为一年的开端，改变了过去以4月1日为新年的传统。&lt;br&gt;一些守旧派反对该改革，依然按照旧历法在新历4月1日送新年礼，庆祝新年。支持新年改革的人借此对守旧派大加嘲弄，在当天给他们送假礼物，邀请他们参加</summary>
      
    
    
    
    <category term="self" scheme="https://a-wing.top/categories/self/"/>
    
    
    <category term="self" scheme="https://a-wing.top/tags/self/"/>
    
  </entry>
  
  <entry>
    <title>干活向的 vim 配置</title>
    <link href="https://a-wing.top/vim/2021/03/21/work_vim_config"/>
    <id>https://a-wing.top/vim/2021/03/21/work_vim_config</id>
    <published>2021-03-21T05:00:00.000Z</published>
    <updated>2021-03-21T05:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>2021 年该用什么样的配置</p><p>极简，高效。这是实际干活用的，不需要太多插件和功能</p><p>这个配置是我用了三年多的 vim 不断改进的，实用主义</p><p>常年不用的插件都被我移除了</p><p>还有一个问题是我经常在不同的编程语言之间反复横跳，所以不依赖特定语言或框架的配置</p><p>我平时工作是就是这样来使用的，我一般都是在 tmux 跑 vim</p><p><img src="/assets/img/work_vim_config/screenshot_2021-03-21_18-10-12.png" alt="tmux vim"></p><h2 id="vim-基础"><a href="#vim-基础" class="headerlink" title="vim 基础"></a>vim 基础</h2><h3 id="版本"><a href="#版本" class="headerlink" title="版本"></a>版本</h3><p>vi 最老的版本。后来有了 Vim。可使用 vi 模式</p><p>近几年重写的版本 nvim (NeoVim)。nvim 最重要的功能就是支持了异步。后来 vim8 也支持了异步，已经没有什么区别了</p><p>带 GUI 的版本 GVim（GTK 的图形前端）</p><p>MacOS 的专属 UI 版本 MacVim</p><p>一个配置发布版本 SpaceVim，模仿 SpaceEmacs</p><h3 id="模式"><a href="#模式" class="headerlink" title="模式"></a>模式</h3><p>vim 有很多模式，最常用的就这三个模式：通常模式（Normal mode），插入模式（Insert mode），可视模式（Visual mode）。</p><p>EX 模式，可以当做没有，不小心进入了知道如何退出就够了。<code>Ctrl + v</code> 进入块选择模式，不知道也无所谓</p><blockquote><p><code>Q</code> 进入 EX 模式，输入 <code>visual</code> 退出 EX 模式</p></blockquote><p><img src="/assets/img/work_vim_config/vim-exit.jpg" alt="vim exit"></p><p>用 vim 先从 <code>h j k l</code> 开始学习移动光标的简直是劝退向。我用了好多年 <code>h j k l</code> 都用不熟练。建议忘记 <code>h j k l</code> 的操作，方向键足够了</p><p>什么没有方向键？建议换键盘，或者让编辑器符合自己的习惯，而不是习惯工具（习惯工具建议直接上 IDE）</p><p>相比之下移动光标用比较多的是：</p><ul><li><code>^</code> , <code>$</code> 跳到行开头，跳到行尾</li><li><code>A</code> 跳到行尾部进入插入模式</li><li><code>Crtl-f</code> , <code>Crtl-b</code> 向后，向前翻页</li><li><code>w</code> , <code>W</code> 跳一个单词，跳到下一个空格</li><li><code>:10</code> 跳到第 10 行</li><li><code>G</code>, <code>gg</code> 分别跳至文件尾部和头部</li></ul><p>写代码用的最多的自然是 <code>Crtl-c</code> <code>Crtl-v</code></p><p><img src="/assets/img/work_vim_config/ctrl-cv.jpg" alt="vim ctrl-cv"></p><p>在 vim 里就是</p><ul><li><code>dd</code> 剪贴</li><li><code>yy</code> 复制</li><li><code>p</code> 粘贴</li><li><code>P</code> 当前行前粘贴</li><li><code>10dd</code> 剪贴 10 行</li><li><code>4yy</code> 复制 4 行</li></ul><h3 id="插件管理器：Plug"><a href="#插件管理器：Plug" class="headerlink" title="插件管理器：Plug"></a>插件管理器：Plug</h3><ul><li>vundle</li><li>vim-plug</li><li>vim8 packadd</li></ul><p>vundle 比较老（这个当年几乎一统江湖）不过一些插件会自带个 lib， vundle 就不太够用了，就有了 NeoBundle 来扩充这部分功能。</p><p>vim-plug 我目前用的就是这个。vim8 之后自带了包管理器 packadd（我还从来没有用过）</p><p>反正基本上都差不多，随便选就行</p><h2 id="语法代码补全"><a href="#语法代码补全" class="headerlink" title="语法代码补全"></a>语法代码补全</h2><p>你可以使用基于 LSP （Language Server protocol） 的实现。当然，也可以使用传统的补全插件。。。以 LSP 现在火的程度，大概未来都是这种插件吧</p><h3 id="coc-nvim"><a href="#coc-nvim" class="headerlink" title="coc.nvim"></a>coc.nvim</h3><p>vim 下的一个 LSP  实现框架。这个稍微有点特殊，它自己带了一套插件体系和包管理。同一个插件甚至可以找到 vim 的包和 coc 的包</p><h3 id="tabnine"><a href="#tabnine" class="headerlink" title="tabnine"></a>tabnine</h3><p>一个基于机器学习的自动补全插件。可以配合 coc 一起用 coc-tabnine</p><p>这个强力推荐，毕竟真的好用，时间使用效果也可能和使用的语言有关</p><p>很多时候我觉得不是我在写代码，而是 tabnine 在写代码。我在 review</p><h3 id="各种编程语言"><a href="#各种编程语言" class="headerlink" title="各种编程语言"></a>各种编程语言</h3><p>去找对应的 coc 的插件。或对应的 vim 插件，这里没什么值得注意的地方</p><p>一些很著名软件的配置文件语法甚至也可以找到，比如：<code>nginx.conf</code> ，<code>systemd</code> 之类的</p><h3 id="markdown"><a href="#markdown" class="headerlink" title="markdown"></a>markdown</h3><p>markdown 要预览功能，所以比较特殊一点</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&quot; markdown support</span></span><br><span class="line">Plug <span class="string">&#x27;godlygeek/tabular&#x27;</span></span><br><span class="line">Plug <span class="string">&#x27;plasticboy/vim-markdown&#x27;</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">g:vim_markdown_folding_disabled</span> = <span class="number">1</span></span><br><span class="line"><span class="comment">&quot; markdown preview</span></span><br><span class="line">Plug <span class="string">&#x27;iamcco/markdown-preview.nvim&#x27;</span>, &#123; <span class="string">&#x27;do&#x27;</span>: &#123; -&gt; mkdp#util#install() &#125;, <span class="string">&#x27;for&#x27;</span>: [<span class="string">&#x27;markdown&#x27;</span>, <span class="string">&#x27;vim-plug&#x27;</span>]&#125;</span><br></pre></td></tr></table></figure><p>这个分成两个部分，一个是。markdown 语法部分，和 markdown 预览部分</p><p><code>:MarkdownPreview</code> 命令就可以一边编辑，一边在浏览器里面预览了</p><h3 id="风格约束"><a href="#风格约束" class="headerlink" title="风格约束"></a>风格约束</h3><p>通过这三行可以把空格缩进和 tab 缩进都显示成一样的风格。嗯，我写 python 也是用两个空格的（去它 PEP8）</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">set</span> tabstop=<span class="number">2</span></span><br><span class="line"><span class="keyword">set</span> softtabstop=<span class="number">2</span></span><br><span class="line"><span class="keyword">set</span> <span class="built_in">shiftwidth</span>=<span class="number">2</span></span><br></pre></td></tr></table></figure><p>但是个人风格可能和团队风格不一致。</p><p>原来写的项目都是用 <code>editorconfig</code> 来控制团队之间不同的人的代码风格</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&quot; .editorconfig</span></span><br><span class="line">Plug <span class="string">&#x27;editorconfig/editorconfig-vim&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="显示相关"><a href="#显示相关" class="headerlink" title="显示相关"></a>显示相关</h2><h3 id="Theme"><a href="#Theme" class="headerlink" title="Theme"></a>Theme</h3><p>这个是个老生常谈的话题，我还是偏爱 gruvbox 的主题。然后把背景设置成透明的，然后在终端上使用模糊效果</p><p>gruvbox 可以支持一些状态，比如 coc 的 <code>CocFloating</code></p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">Plug <span class="string">&#x27;morhetz/gruvbox&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&quot; === theme gruvbox ===</span></span><br><span class="line"><span class="keyword">colorscheme</span> gruvbox</span><br><span class="line"><span class="comment">&quot; Setting dark mode</span></span><br><span class="line"><span class="keyword">set</span> background=dark</span><br><span class="line"></span><br><span class="line"><span class="keyword">nnoremap</span> <span class="symbol">&lt;silent&gt;</span> [oh :<span class="keyword">call</span> gruvbox#hls_show()<span class="symbol">&lt;CR&gt;</span></span><br><span class="line"><span class="keyword">nnoremap</span> <span class="symbol">&lt;silent&gt;</span> ]oh :<span class="keyword">call</span> gruvbox#hls_hide()<span class="symbol">&lt;CR&gt;</span></span><br><span class="line"><span class="keyword">nnoremap</span> <span class="symbol">&lt;silent&gt;</span> coh :<span class="keyword">call</span> gruvbox#hls_toggle()<span class="symbol">&lt;CR&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">nnoremap</span> * :<span class="keyword">let</span> @/ = <span class="string">&quot;&quot;</span><span class="symbol">&lt;CR&gt;</span>:<span class="keyword">call</span> gruvbox#hls_show()<span class="symbol">&lt;CR&gt;</span>*</span><br><span class="line"><span class="keyword">nnoremap</span> / :<span class="keyword">let</span> @/ = <span class="string">&quot;&quot;</span><span class="symbol">&lt;CR&gt;</span>:<span class="keyword">call</span> gruvbox#hls_show()<span class="symbol">&lt;CR&gt;</span>/</span><br><span class="line"><span class="keyword">nnoremap</span> ? :<span class="keyword">let</span> @/ = <span class="string">&quot;&quot;</span><span class="symbol">&lt;CR&gt;</span>:<span class="keyword">call</span> gruvbox#hls_show()<span class="symbol">&lt;CR&gt;</span>?</span><br><span class="line"></span><br><span class="line"><span class="comment">&quot; === theme gruvbox ===</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&quot; transparent background</span></span><br><span class="line"><span class="keyword">hi</span> Normal ctermfg=<span class="number">252</span> ctermbg=none</span><br></pre></td></tr></table></figure><h3 id="底部状态条-airline"><a href="#底部状态条-airline" class="headerlink" title="底部状态条 airline"></a>底部状态条 airline</h3><p><img src="/assets/img/work_vim_config/airline.png" alt="airline"></p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Plug <span class="string">&#x27;vim-airline/vim-airline&#x27;</span></span><br></pre></td></tr></table></figure><p>我觉得这东西最有用的一点就是可以看到光标坐标和文件编码。。。防止不知道哪的人丢过来个 dos 的编码的文件</p><h3 id="显示行号"><a href="#显示行号" class="headerlink" title="显示行号"></a>显示行号</h3><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">set</span> <span class="keyword">number</span></span><br><span class="line"><span class="keyword">map</span> <span class="symbol">&lt;silent&gt;</span><span class="symbol">&lt;F4&gt;</span> :<span class="keyword">set</span> relativenumber!<span class="symbol">&lt;CR&gt;</span></span><br></pre></td></tr></table></figure><p>这样按 <code>F4</code> 来进行相对行号和绝对行号的切换</p><p>我是经常在决定行号和相对行好之间切换。多行删除复制是相对行号用的很多，跳转时用绝对行号</p><h3 id="显示对齐线"><a href="#显示对齐线" class="headerlink" title="显示对齐线"></a>显示对齐线</h3><p><img src="/assets/img/work_vim_config/indent-guides.png" alt="indent guides"></p><p>有时写着写着容易乱，使用这个对齐线插件，默认不开启。只在需要时使用 <code>F7</code> 来开启或关闭这个功能</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&quot; indent guides</span></span><br><span class="line"><span class="keyword">let</span> <span class="variable">g:indent_guides_guide_size</span> = <span class="number">1</span></span><br><span class="line">Plug <span class="string">&#x27;nathanaelkane/vim-indent-guides&#x27;</span></span><br><span class="line"><span class="comment">&quot; indent guides shortcut</span></span><br><span class="line"><span class="keyword">map</span> <span class="symbol">&lt;silent&gt;</span><span class="symbol">&lt;F7&gt;</span>  <span class="symbol">&lt;leader&gt;</span>ig</span><br></pre></td></tr></table></figure><h2 id="检索跳转"><a href="#检索跳转" class="headerlink" title="检索跳转"></a>检索跳转</h2><h3 id="NERDTree"><a href="#NERDTree" class="headerlink" title="NERDTree"></a>NERDTree</h3><p>显示目录树插件。vim 自己带的那个 <code>netrw</code> 需要自己配置一番。</p><p>使用这个插件纯粹是习惯了， <code>F8</code> 打开目录树 <code>Crtl-w w</code> 切过去，按 <code>m</code> 打开菜单</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&quot; A tree explorer plugin for vim</span></span><br><span class="line">Plug <span class="string">&#x27;preservim/nerdtree&#x27;</span></span><br><span class="line"><span class="keyword">map</span> <span class="symbol">&lt;silent&gt;</span><span class="symbol">&lt;F8&gt;</span> :NERDTree<span class="symbol">&lt;CR&gt;</span></span><br></pre></td></tr></table></figure><h3 id="快速搜索"><a href="#快速搜索" class="headerlink" title="快速搜索"></a>快速搜索</h3><p>先来回顾一下最普通的搜索：<code>/xx</code> 命令来搜索</p><p>这样要手动输入搜索内容才能使用，或许可以使用 Visual mode 来选择要搜索的内容</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&quot; https://blog.twofei.com/610/</span></span><br><span class="line"><span class="comment">&quot; https://vim.fandom.com/wiki/Search_for_visually_selected_text</span></span><br><span class="line"><span class="keyword">vnoremap</span> // <span class="keyword">y</span>/<span class="symbol">&lt;c-r&gt;</span><span class="comment">&quot;&lt;cr&gt;</span></span><br></pre></td></tr></table></figure><p>在配置里加上这个：</p><ol><li>先按 <code>v</code> 进入 Visual mode</li><li><code>//</code> 执行在本文里搜索选中内容</li></ol><h3 id="模糊搜索-fzf"><a href="#模糊搜索-fzf" class="headerlink" title="模糊搜索 fzf"></a>模糊搜索 fzf</h3><p>什么？coc 不是已经有 CocList 了吗？为什么还要一个。。。</p><p>我比较懒，这个已经把我最常用的功能也做了。不用自己写了，多一个插件又不会怎么样</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">set</span> rtp+=/usr/local/<span class="keyword">opt</span>/fzf</span><br><span class="line">Plug <span class="string">&#x27;junegunn/fzf.vim&#x27;</span></span><br><span class="line"><span class="keyword">map</span> <span class="symbol">&lt;c-o&gt;</span> :Buffers<span class="symbol">&lt;CR&gt;</span></span><br><span class="line"><span class="keyword">map</span> <span class="symbol">&lt;c-p&gt;</span> :Files<span class="symbol">&lt;CR&gt;</span></span><br></pre></td></tr></table></figure><p><code>fzf.vim</code> 这个插件要依赖外部的 <code>fzf</code> 命令，要指定 <code>fzf</code> 的路径</p><p>在计算机术语有一个 LRU 的名词，大家还记得吗？对的，最近缓冲区列表，每次打开一个新文件就会放到缓冲区，重新打开就会移到最上方</p><p>最近文件历史切换，就是 <code>:Buffers</code> 命令可以打开这样一个文件列表</p><p>这个我用的非常多，设置 <code>Crtl-o</code> 快捷键</p><h3 id="全文搜索"><a href="#全文搜索" class="headerlink" title="全文搜索"></a>全文搜索</h3><p>先从命令行工具 <code>grep</code> 开始，我们都知道，可以配合 <code>find</code> 工具来实现全文检索，但这是一个很低效的实现</p><p>一个代替 <code>grep</code> 的工具 <code>ripgrep</code> 。对就是 R.I.P. （Rest in peace） <code>grep</code></p><p>我们可以直接使用 <code>Rg</code> 命令来全文搜索。当然，也可以配合 <code>fzf</code> 来列表选择，再装到 vim 里选择匹配项目，跳转到文件的行</p><p>这一切 <code>fzf.vim</code> 都已经帮我们做好了，直接输入 <code>:Rg xxx</code> 就可以搜索了</p><p><img src="/assets/img/work_vim_config/screenshot_2021-03-21_18-06-13.png" alt="vim ripgrep"></p><p>还记得刚才是如何改造在文件中搜索的吗？把 <code>Rg</code> 也改造成快捷键，在下面添加这句</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">vnoremap</span> Rg <span class="keyword">y</span>:Rg <span class="symbol">&lt;c-r&gt;</span><span class="comment">&quot;&lt;cr&gt;</span></span><br></pre></td></tr></table></figure><ol><li>先按 <code>v</code> 进入 Visual mode</li><li><code>Rg</code> 全文搜索选中内容</li></ol><h3 id="vimdiff"><a href="#vimdiff" class="headerlink" title="vimdiff"></a>vimdiff</h3><p>大家都知道 <code>diff</code> 命令。不过我们也可以使用 <code>vim</code> 的 <code>vimdiff</code> 来对比两个文件文件</p><p><code>:diffsplit</code> 来开启对比窗口，当然我个人偏好是左右对比窗口（垂直分割），需要在配置里加上下面这行</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">set</span> diffopt+=<span class="keyword">vertical</span></span><br></pre></td></tr></table></figure><h3 id="快速跳转"><a href="#快速跳转" class="headerlink" title="快速跳转"></a>快速跳转</h3><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Plug <span class="string">&#x27;Lokaltog/vim-easymotion&#x27;</span></span><br></pre></td></tr></table></figure><p><code>:make w</code> 把某行标记成 <code>w</code></p><p><code>&#39;w</code> 跳转到 <code>w</code> 这标记的这行</p><h3 id="Ctrl-6"><a href="#Ctrl-6" class="headerlink" title="Ctrl-6"></a><code>Ctrl-6</code></h3><p>这个大概我用的最多的快捷键。很有趣的一点是这个是默认的快捷键。但很少有教程提到这个功能</p><p>和 Buffers 里的最上面一个文件切换</p><h2 id="批量处理"><a href="#批量处理" class="headerlink" title="批量处理"></a>批量处理</h2><h3 id="文件批量重命名"><a href="#文件批量重命名" class="headerlink" title="文件批量重命名"></a>文件批量重命名</h3><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&quot; :Renamer</span></span><br><span class="line">Plug <span class="string">&#x27;vim-scripts/renamer.vim&#x27;</span></span><br></pre></td></tr></table></figure><p>把文件目录当成文本文件来编辑，直接批量重命名文件</p><h3 id="批量编辑"><a href="#批量编辑" class="headerlink" title="批量编辑"></a>批量编辑</h3><p>比如要一次性注释多行，在每行前面添加 <code>//</code></p><ol><li><code>^</code> 到行首</li><li><code>Ctrl + v</code> 进入块选择模式，选择多行</li><li><code>I</code> 进入批量插入模式</li><li>输入添加内容 <code>//</code></li><li><code>esc</code> 退出块编辑模式</li></ol><h3 id="查找替换"><a href="#查找替换" class="headerlink" title="查找替换"></a>查找替换</h3><p>相信大家对 <code>sed</code> 并不陌生，这里直接在 <code>vim</code> 里使用类似的命令</p><p><code>:%s/old/new/g</code></p><p>当然可以使用其他的作为分隔符</p><p><code>:%s#old#new#g</code></p><h3 id="批量删除匹配行"><a href="#批量删除匹配行" class="headerlink" title="批量删除匹配行"></a>批量删除匹配行</h3><p>有些时候在查看大量日志的时候，可以直接用 vim 来处理</p><p>我们使用这个来批量删除一些无关紧要的行 <code>:g/text/d</code></p><p>比如日志分级直接删除 debug 级的日志 <code>:g/[DEBUG]/d</code></p><p>当然可以反过来操作，保留匹配行，按照 vim 的命名习惯 <code>g</code> 命令的相反操作就是 <code>g!</code></p><p><code>:g!/text/d</code></p><p>更准确的应该是 <code>:v/text/d</code></p><p><code>g!</code> 其实是 <code>v</code> 命令的别名</p><h3 id="自动风格处理"><a href="#自动风格处理" class="headerlink" title="自动风格处理"></a>自动风格处理</h3><p>这个也是原本的功能，按 <code>v</code> 进入 visual 模式。多选行然后按 <code>=</code> 来自动格式化缩进。</p><p>当然，如果你是 python ，这个功能当然是残废的。这个只能处理风格，不能理解语义</p><h3 id="自动移除末尾空白"><a href="#自动移除末尾空白" class="headerlink" title="自动移除末尾空白"></a>自动移除末尾空白</h3><p>我习惯在保存文件时  <code>:w</code> 来执行这个操作</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&quot; Automatically remove trailing spaces</span></span><br><span class="line"><span class="keyword">autocmd</span> BufWritePre * :%s/\s\+$//<span class="keyword">e</span></span><br></pre></td></tr></table></figure><h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><h3 id="vim-leetcode"><a href="#vim-leetcode" class="headerlink" title="vim-leetcode"></a>vim-leetcode</h3><p>leetcode 网页那个编辑器简直难用的要死，我们可以在 vim 里面刷题</p><p><img src="/assets/img/work_vim_config/screenshot_2021-03-21_18-48-42.png" alt="vim-leetcode"></p><h3 id="vim-rainbow-fart"><a href="#vim-rainbow-fart" class="headerlink" title="vim-rainbow-fart"></a>vim-rainbow-fart</h3><p>还记得那个使用关键词触发语音的彩虹屁插件吗。什么？和干活没关系，这个可是可以让人码力全开的神器啊（</p><p>可以装上钉宫的语音包。<a href="https://zh.moegirl.org.cn/%E9%92%89%E5%AE%AB%E7%97%85">钉宫病</a> 患者的福音。<strong>傲 娇 钉 宫，鞭 写 鞭 骂</strong></p><blockquote><p>巴嘎 变态 无路赛～～～</p></blockquote><p>然后就有可能发生这种事故</p><div class="twitter-wrapper"><blockquote class="twitter-tweet"><a href="https://twitter.com/_a_wing/status/1300352161205739522"></a></blockquote></div><script async defer src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><p>对我配置感兴趣，可以来这里看：<a href="https://github.com/a-wing/dotfiles/tree/master/nvim">a-wing&#x2F;dotfiles#nvim</a></p><h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><p><a href="https://ruby-china.org/topics/19315">(纯干活) Rails 高效开发工具 vim 指南</a></p><p><a href="https://blog.twofei.com/610/">[VIM] 搜索可视化选中（Visually Selected）的文本</a></p><p><a href="https://vim.fandom.com/wiki/Search_for_visually_selected_text">Search for visually selected text</a></p><p><a href="https://shapeshed.com/vim-netrw">Vim: you don’t need NERDtree or (maybe) netrw</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;2021 年该用什么样的配置&lt;/p&gt;
&lt;p&gt;极简，高效。这是实际干活用的，不需要太多插件和功能&lt;/p&gt;
&lt;p&gt;这个配置是我用了三年多的 vim 不断改进的，实用主义&lt;/p&gt;
&lt;p&gt;常年不用的插件都被我移除了&lt;/p&gt;
&lt;p&gt;还有一个问题是我经常在不同的编程语言之间反复横跳，所</summary>
      
    
    
    
    <category term="vim" scheme="https://a-wing.top/categories/vim/"/>
    
    
    <category term="linux" scheme="https://a-wing.top/tags/linux/"/>
    
    <category term="vim" scheme="https://a-wing.top/tags/vim/"/>
    
  </entry>
  
  <entry>
    <title>创新者异闻录（7/7）——失败者</title>
    <link href="https://a-wing.top/self/2021/02/27/innovator/2021-02-12-innovator-7"/>
    <id>https://a-wing.top/self/2021/02/27/innovator/2021-02-12-innovator-7</id>
    <published>2021-02-27T16:20:00.000Z</published>
    <updated>2021-02-27T16:20:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>我现在是失败者，但我不会一直都是失败者</p><p>本篇是个人感悟，无技术性内容。本系列文章感谢我前司草莓的同事，原大疆的工程师，原零度的工程师，和推友群友提出的宝贵意见和指导我不懂的问题。我知识水平有限，只能去求助各路大佬</p><h2 id="关于本系列"><a href="#关于本系列" class="headerlink" title="关于本系列"></a>关于本系列</h2><p>总计七篇文章：（原计划：初一到初七每天更一篇，这根本就不可能，可算是在二月份更完了）</p><ul><li><a href="/self/2021/02/12/innovator-1.html">第 1 篇 讲一下历史，随便水水</a></li><li>第 2～4 篇 讲一下我这几年学的绝技：徒手造飞机<ul><li><a href="/self/2021/02/12/innovator-2.html">第 2 篇 科普飞行器原理</a></li><li><a href="/self/2021/02/12/innovator-3.html">第 3 篇 造一个最基础的飞行器</a></li><li><a href="/self/2021/02/20/innovator-4.html">第 4 篇 飞行器的扩展</a></li></ul></li><li>第 5～6 篇 讲我前司和我都干了什么<ul><li><a href="/self/2021/02/21/innovator-5.html">第 5 篇 无人机自动机场 （这个是个硬件，机械设备）</a></li><li><a href="/self/2021/02/25/innovator-6.html">第 6 篇 无人机自动系统 （这个是个软件，管理控制系统）</a></li></ul></li><li>第 7 篇 谈谈感悟</li></ul><p>这个系列是否泄漏了前司机密？这里 <strong>用的所有资料全部都是公开资料</strong></p><blockquote><p>看第五篇那个图的文字（文字是不匹配），虽然我可以去找前同事拿一个更好的版本。但是我要说 <strong>用的所有资料全部都是公开资料</strong></p></blockquote><p>我怕有人看这个系列看不懂，所以抓了推友和群友帮我 Review。还是 Xuanwo 一段话惊醒了我</p><blockquote><p>不能干预一个创作者，只能在他创作之后再提出提意见，要不然就不是自己的东西了</p></blockquote><p>不要过多的考虑别人能不能看懂，说清楚就好了，不应该去迎合读者爱好做快消品</p><p>我不想写流水账，流水账再单独开一篇来写吧。所以删去了很多对读者没有营养的东西。做减法远比做加法难的多</p><blockquote><p>我删掉的东西够再写两篇文章了。不加了，只留下经典的部分吧</p></blockquote><h3 id="关于大疆的历史"><a href="#关于大疆的历史" class="headerlink" title="关于大疆的历史"></a>关于大疆的历史</h3><p>或许我不该讲大疆发展的历史，虽然大家都喜闻乐见去看商战，黑料。这就会陷入低俗的大众审美。我希望我写的东西都能让大家有所收获，并不应该迎合大众的口味</p><p>我甚至想把 <a href="/self/2021/02/12/innovator-1.html">第一篇历史（1&#x2F;7）</a> 那篇删掉，因为没有营养，也没有主题，罗列历史，引用真实性不确定的信息，仅仅只不过是在吹牛划水</p><p>或者大家都喜闻乐见看到这样的内容，写这种东西就是在迎合大众的审美做没有营养的东西</p><h3 id="行业基础知识"><a href="#行业基础知识" class="headerlink" title="行业基础知识"></a>行业基础知识</h3><p>快写完了发现，我最基本的东西都没有写，比如多旋翼如何实现飞行的控制的</p><p>写到无人机自动机场是在脑海里自动忽略了无人机自动机场是什么东西，有什么用，因为太熟悉，觉得大家好像都知道一样，其实只有业内人士才知道这个东西是什么，原来看到某同行的在官网上写一些极为基础的东西。</p><p>比如：无人机场是什么？无人机场有什么用？都在心里默默鄙视他整天写一下这么基础的东西。现在突然发现，行业之外的人和业内人士的认知完全是不同的，这个是写给完全不了解的人看的</p><h2 id="草莓创新"><a href="#草莓创新" class="headerlink" title="草莓创新"></a>草莓创新</h2><p>前司的名字叫 草莓（Strawberry） <a href="https://sb.im/">sb.im</a></p><p><img src="/assets/img/innovator/sb-logo.png" alt="sbim logo"></p><p>来个无奖竞猜：草莓这个名字是怎么来的？</p><p>或许太难了，树莓派名字时怎么来的？</p><blockquote><p>树莓派（Raspberry Pi）最初的名字是： “树莓”（Raspberry），这起源于以前对于微处理器以水果命名的传统。<br>“派”（Pi）指的是 “Python” 因为 Python 是第一个移植到树莓派的程序</p></blockquote><p>“草莓” 就是这群人十分擅长使用树莓派来做产品，就找了个听起来相近的植物</p><h3 id="前司之前"><a href="#前司之前" class="headerlink" title="前司之前"></a>前司之前</h3><p>我大一进入美团外卖实习，当时还想准备入职美团外卖。那年吃外卖还满 10 减 8</p><p>现在回想起在美团唯一还能记住的就是： <strong>优秀是一种习惯</strong> 和 <strong>即往不恋，纵情向前</strong></p><p>我还清晰的记得我要入职美团：当时 CM（City Manager） 问我大几？我大一。。。大一绝对不行</p><p>之后我就离开了，连实习证明都没要就走，事后也证明了用不到。因为之后就辍学了（应该说我去南方看到了非常震撼的东西），后来做了购物平台，做了众筹平台，做了无人机</p><p>那时我在做众筹平台，就是模仿 Kickstarter 。就是 cfcart 那个项目前身。（当时有 100W 投资的，其实实际到账就 20W。有啥不能说的，垃圾投资人）后来还是做废了。</p><p>当时草莓（我后来去的公司）就在做这个口袋跟拍产品：叫（HELLO 飞拍相机）后来上了电视台</p><p>为什么要提这个：因为当年口袋跟拍的概念就是众筹平台炒起来了（那时我还不知道草莓），坑了一大堆投资口袋跟拍无人机的人</p><p>众筹平台失败之后就自己开了做网站外包的工作室。。。<br>后来觉得实在是没意思，经过一起做众筹平台的合伙人推荐，去了一个每天可以摸鱼 7h 的地方（太闲了，自学了 ruby on rails, vue, 和无人机相关的东西）</p><p>再后来遇上了个有钱土老板，出钱让我造无人机。后来知道了草莓也在做无人机，推荐我去聊一聊（对，就是去草莓聊一聊）。。。后来发现植保无人机不行（因为大疆来了），决定退出。又再度失业</p><p>后来和当时的草莓一直在聊树莓派，我自认为是树莓派资深玩家，他也自认为自己是树莓派资深玩家，也有可能是我即搞过飞机，又搞过互联网，offer 有了。从此就走出了五线小城市的舞台。来到了深圳</p><blockquote><p>大家都好优秀啊，再优秀也不够优秀</p></blockquote><h3 id="前司草莓"><a href="#前司草莓" class="headerlink" title="前司草莓"></a>前司草莓</h3><p>我们在车库里造出了第一代无人机自动机场。当时使用铝型材做的骨架，使用光圈门（对，就是舱门，这东西贼复杂，还没什么用）。<br>用 Arduino 做主控（同行都用 PLC ，就我们独树一帜）。这也就是个 Demo 级的东西</p><p>后来连续迭代了几代产品，做了无人机的远程控制。产品可算是能卖了。不，这就是一坨垃圾，它只是变得不那么垃圾了而已</p><p>最关键的是缺少应用场景，这个市场真的开拓出来后，和大疆必有一战。然后再输给大疆（有没有感觉这个剧情很熟悉）</p><p>我倒觉得一开始思路就是错了，应该先集中精力做一个模块。我从一开始就反对自研无人机的方案。个人观点，事后诸葛亮嘛。吐槽吐槽而已</p><p>要把整套东西做出自动的：需要飞机，机场和系统的配合</p><p>啊。对，我可以随意说前司坏话了，我觉的应该把软件系统部分全砍了。。。对，应该把我们整个软件系统组都直接 fire 掉。壮士短臂或许对公司整体更好。。。还不是因为刚开始的时候战线拉的太长，都收不回来了</p><p>要不，把机场砍了也行，后来还是把自己做的飞机砍了（改用大疆的飞机了）。。。资源太有限了，根本做不好这三个部分</p><p>我倒是很希望我走的之后，草莓把系统全都砍了（好像太对不起做系统的其他同事人了，我实在是没法说出这种方案），只留下机场，飞机用大疆的飞机。这样资源集中，竞争力会大一些，等着机场稳定大量出货再做系统。这个状态不管系统怎么做都是无用的（软件系统组就是个烧钱货，要么软件系统组就出去当外包）。目前来看机场是最有可能做成标准产品的东西。</p><blockquote><p>我就是没法当着他们的面说：把系统砍了吧。</p></blockquote><h3 id="草莓的未来"><a href="#草莓的未来" class="headerlink" title="草莓的未来"></a>草莓的未来</h3><p>会倒闭吗？当然不会，除非那几个人都不想干了</p><p>大疆什么时候来收拾无人机自动机场，我觉得这个问题取决于极飞的彭老板还可撑多久，最多两年吧，你看极飞都开始做无人车了（已经开始吹牛了）。这明显就是心虚了（万一打不过大疆还可以退守自动驾驶拖拉机）</p><p>活下去是肯定有办法的。但是，<strong>创业是完成这件事情的手段，而不应该是目的</strong>。（不能为了创业而创业）</p><h3 id="这个领域"><a href="#这个领域" class="headerlink" title="这个领域"></a>这个领域</h3><p>和同行交流时，都是在藏着掖着生怕你知道他们有什么，我很不喜欢这样的封闭的领域</p><p>销售就靠吹，忽悠一个是一个。更有意思的一点是：他们曾经都是技术人员</p><blockquote><p>我在美团外卖实习时，我觉得是在做划时代的产品。每天都觉得我在改变世界（这太中二病了）</p></blockquote><p>为什么这个领域会是这个样子，其他的领域也是这样吗？我需要去发现，去体验，去理解</p><p>来我前司交流的人有好多靠资源活着的，我有人脉，我有关系，天天想着把我们收购了。</p><p>我怕我未来会变的和他们一样，创造不了任何的东西，每天出去吹水，再忽悠个技术团队来打工</p><p>仔细想想，或者 <a href="https://en.wikipedia.org/wiki/Richard_Stallman">理查德·斯托曼（GNU项目的发起人）</a> 才是最值得敬佩的人</p><p>或许只有开源才能让企业不重视代码，转为重视人才</p><p>所谓创业，本质是去找了一群人，一起去做事</p><h2 id="价值的体现"><a href="#价值的体现" class="headerlink" title="价值的体现"></a>价值的体现</h2><p>我从来都不认为代码资产，写代码的人才是。就像电路图和图纸一样，这玩意就是废纸。设计出这个东西的工程师才是资产，应该保持持续的创新</p><p>很多时候，就在想，我们到底在做什么？</p><p>卖了一套设备，听着一群外行来指导我们这东西该怎么怎么做，给大国企领导演示，很轻松的盈利了，但是啊，我们到底在做什么</p><p>这个有市场，这个复杂度有意义吗，这个市场会认可吗。只是现在不会，大家都要炫酷界面的系统</p><h3 id="出色的设计"><a href="#出色的设计" class="headerlink" title="出色的设计"></a>出色的设计</h3><blockquote><p>全部的功能都是可选的，层次，逻辑一目了然</p></blockquote><p>系统设计成这样，谁会买单？没人会买单，再优秀的设计，和大国企领导的审美面前都是无用的，他们要那种超大屏的，暗黑色系的，看起来就极为炫酷的控制界面，在外包公司随便拉几个人，跟着大客户屁股后面，给他们定制个专属的页面或许这就是无人机自动系统的真正价值吧</p><p>先来来回顾一下大疆的历史</p><blockquote><p>在公司起步阶段，汪滔依靠在校期间研发的直升机自主悬停系统模型挣取第一桶金。因为技术的稀缺，当时出售一个模型能够得到几万元钱，而大疆一款单品能够卖到20多万。当时产品还没达到实用阶段，主要作用是让大型国企领导进行功能性的展示。</p><p>在这种畸形商业模式中，汪滔看不到发展前景。</p></blockquote><p>我前司草莓目前也是这样，有时我在想：我们把青春浪费在这个地方到底是在干什么，在产品上加一些看起来很唬人的挂载，在把界面上改成那种大国企领导喜欢的那种特别炫酷的，没有任何实际价值东西（也不是没有用：放在指挥大厅里吹牛用）</p><blockquote><p>讲个笑话吧，有人用游戏引擎去做这东西，满足了炫酷高端的需求</p></blockquote><p>这 TM 能实际去使用吗？如果都喜欢炫酷 UI 。都喜欢用屎一样糊起来的东西，那我还不如转行去找个天桥去贴手机膜</p><p>什么，它满足需求了。。。。不，它不能满足需求，它只是满足了表面上看起来的需求，只是满足了从客户嘴里面说出的 “我要的这个东西”</p><p>伟大的产品都是面向终端用户 。只有做不出好产品才回去做定制用户需求</p><blockquote><p>有些人说：“消费者想要什么就给他们什么。”但那不是我的方式。我们的责任是提前一步搞清楚他们将来想要什么。我记得亨利·福特曾说过，“如果我最初是问消费者他们想要什么，他们应该是会告诉我，‘要一匹更快的马！’”人们不知道想要什么，直到你把它摆在他们面前。</p><p>——《乔布斯传》</p></blockquote><h3 id="追求卓越"><a href="#追求卓越" class="headerlink" title="追求卓越"></a>追求卓越</h3><p>Dead line 是个很糟糕的东西，他强迫你要把不够完美的东西拿出去</p><blockquote><p>不想当将军的士兵不是好士兵 —— 拿破仑</p></blockquote><p>我一直觉得这句话是废话，怎么会有 “不想当将军的士兵”</p><p>机械那边招装配的人（算半个工人，或者说生产主管，量少也不是大规模生产，要和供应商打交道），有个某大型工厂流水线上的工人过来面试，我们说这个工作可能要你在装配过程中提出改进方案，他竟然说：提出改进方案的那不都当主管了吗？</p><p>？？？？？？</p><p>我曾经一直以为他们是没有机会，原来不想当将军的士兵是真的存在的</p><p>我真的想吐槽：这年头靠谱的人太少了</p><h2 id="心态"><a href="#心态" class="headerlink" title="心态"></a>心态</h2><p>成功的经验没有，花式把项目做死的经验一大堆。。。（应该没有地方会要有这么多失败经验的人</p><p>可以忍受胯下之辱才是真的强者</p><h3 id="多思考为什么"><a href="#多思考为什么" class="headerlink" title="多思考为什么"></a>多思考为什么</h3><p>有些地方我想尽可能说的精确，不过有些就是经验，我也不知道为什么，也查不到问什么</p><p>写这个系列时发现有趣的问题，比如为什么多旋翼要用无刷电机？</p><p>用了几年我都是没思考过这个问题。写这个系列是突然想到这个问题</p><p>大多数文章都是在讲无刷电机是如何工作的，但多旋翼为什么要用无刷电机。却没人说的清楚</p><p>为什么少数民族能歌善舞？</p><p>一次去海南旅游，才明白，其实本质上都被包装出来了。少数民族都被融合的差不多了。总是要搞点特色</p><p>即使我的判断可能是错的，我也更愿意相信自己的判断。别人又没法决定你的人生</p><blockquote><p>真理往往掌握在少数人手中</p></blockquote><h3 id="关于工具"><a href="#关于工具" class="headerlink" title="关于工具"></a>关于工具</h3><p>我不会再去安利 Arch Linux。也不会安利 vim &#x2F; gimp 等任何工具，只分享一下各种工具的使用心得</p><p>自从我体验了 MacBook 之后就再也回不去了。这个才是我需要的工具</p><p>也不会再吹某些工具，使用什么工具并不重要，重要的是你用它们做了什么</p><h3 id="编程语言战争"><a href="#编程语言战争" class="headerlink" title="编程语言战争"></a>编程语言战争</h3><p>自从我写 <code>lua</code> 定义运行流程之后，我再也不吐槽各种编程语言了。应该带着崇高的敬意去思考他们的设计</p><p><code>golang</code> 是极其优秀的设计，<code>python</code> 非常了不起。各种语言都各有特点， <code>JavaScript</code> 在那么短的时间内完成，真的是非常了不起的作品</p><p>只有自己做过类似的东西，才知道这东西有多难做。无时无刻都在思考这个 API 该怎么写。如何才能写出看一眼就知道这个该怎么用的函数。加任何微小的新功能都要极度克制</p><p>编程语言也仅仅只不过是工具，也没有什么优劣之分</p><h3 id="创业的启示"><a href="#创业的启示" class="headerlink" title="创业的启示"></a>创业的启示</h3><p>如果说在创业公司到学到了什么：应该就是 <strong>“没有什么是做不到的”</strong> 。<br>或许正是因为这样，创业这件事情会更危险，很容易变成什么都要做，最终丢掉了初心。（什么？初心有什么用：等你发现它丢了时就知道他有什么用了）</p><h2 id="生活"><a href="#生活" class="headerlink" title="生活"></a>生活</h2><h3 id="学习"><a href="#学习" class="headerlink" title="学习"></a>学习</h3><p>差不多把计算机科班的学的东西都补完了</p><p>SICP（Structure and Interpretation of Computer Programs） 会让人对计算机的理解上发生变化，真应该早点看到这个</p><p>未来准备去看 APUE （Advanced Programming in the Unix Environment）和 TCP&#x2F;IP 详解</p><p>我还想自己做个 Linux distribution，自己写个编译器（ ← 你咋不上天哪？）</p><h3 id="运动"><a href="#运动" class="headerlink" title="运动"></a>运动</h3><p>学会了滑雪，目前还只会双板（作为北方人小时候没去过滑雪场）</p><p>跑了几场马拉松。大部分时间都在 996 中度过，一个人在外地也不知道该做些什么。去年跑步伤到脚踝了，修养了几个月</p><p>修养期间学会游泳这项新技能（每天工作 12h ，每天早上还是再可以挤出两个小时去学游泳。996 看来还是不够饱和）。后来又去参加了游泳比赛了</p><p>之后想去学习自由潜水。争取可以潜到水下 50M</p><p>旅游的话：大概走遍珠三角了吧（唯独没去过珠海），还有黄果树和海南（把跑马拉松当作旅游挺好的）</p><p>去黄果树看看是我童年的梦想，小时候看到经常看到地上黄果树的烟盒，就好想去黄果树看看啊</p><p>人的梦想是挡不住的，或许现在没有机会，或许未来，总会有机会去实现的</p><h3 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h3><p>我觉得创业公司不应该是 996 的借口，应该以 996 为耻。创业也仅仅只不过是份工作</p><p>工作是为了生活，就算是在家里闲着无聊也不该跑去加班，应该把办公室门都锁上</p><p>应该去更好了去平衡工作和生活。就算很忙，也应该静下来想想。到底那些事情才是真正重要的</p><p>或许上四休三才是更好的工作模式吧</p><h3 id="娱乐"><a href="#娱乐" class="headerlink" title="娱乐"></a>娱乐</h3><p>我从来不玩游戏。USTC 的 CTF 算娱乐了。第一次正式参加了 CTF。那天周六，连刷两天，临时进了前 100 。。。。</p><p><img src="/assets/img/innovator/2020-11-02_11-15.png" alt="USTC CTF"></p><p>之后本社畜要上班（最后的名次 265 ），太羡慕学生了。好想拿出一周时间去做题啊（下次请一周假去做 CTF 吧）</p><p>或者刷 leetcode 也应该算娱乐吧。不准备再使用 B 站了。用其他东西来补番吧</p><p>PingCAP 的面试题要算在娱乐里，那天晚上做题的感觉和 CTF 是一样的</p><p>这很像，虽然很花时间，但还是学会了一项新技能。面试 PingCAP 我至少学会了搭建 Kubernetes</p><blockquote><p>要珍惜那些当面指责你的人</p></blockquote><p>我到非常感谢那个 PingCAP 挂我的面试官。要不是因为说我没讲清楚原来做的是什么，这个系列我也不会写的这么认真</p><p>因为这个整天想着要怼回去，过年干脆没休息，一直在肝这个系列</p><p>不在于你讲了多少高端先进的技术，哪怕只讲一样很基础的东西，让每个人都可以理解，争取每个都可以有所收获</p><h2 id="未来的工作"><a href="#未来的工作" class="headerlink" title="未来的工作"></a>未来的工作</h2><p>我不关心现在有多少财富，当你的财富和实力不匹配时，总有一天还会凭实力输掉</p><p>我要开始我新的冒险了。我觉得我下一次就成功了。巧了，我上一次也是这么想的</p><p>就像是一个旅人，一步步走向更广大的平台。从学校走的城市，走到全国</p><p>先去打工，去学习吧。要去一个国际性的平台。和来自全球的人一起交流。虽然我还英语极烂。我应该马上开始学</p><p>币圈？区块链，别再来找我了。。。不，我看不懂这个领域。我不会做我不懂的东西，死也要死个明白</p><p>我没法决定我的出身，但我至少可以决定我的墓志铭</p><p>我现在没有办法改变过去，但我至少可以决定未来</p><h3 id="碎碎念"><a href="#碎碎念" class="headerlink" title="碎碎念"></a>碎碎念</h3><blockquote><p>总是有人要赢的 那为什么不能是我呢——科比</p></blockquote><p>抱歉，我不会打篮球。但这句话我一直铭记在心</p><p>深圳高交会上见到一个初中生，和我们在聊我的无人机自动机场用什么主控 arduino stm32 之类的。（这年头初中生比我大一时都厉害）</p><p>看到面包高中成为了  nodejs 贡献者时</p><p>看到 ice1000 高三在 PingCAP 实习时</p><p>RustChinaConf 2020 上 rexskz 那个传奇的学弟 Zhou Heyang 时</p><p>看到 twitter 上的思陶博士，觉得一事无成，没资格娱乐时</p><p>我也只能感慨：人外有人，天外有天</p><h3 id="也许我下一次就成功了"><a href="#也许我下一次就成功了" class="headerlink" title="也许我下一次就成功了"></a>也许我下一次就成功了</h3>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;我现在是失败者，但我不会一直都是失败者&lt;/p&gt;
&lt;p&gt;本篇是个人感悟，无技术性内容。本系列文章感谢我前司草莓的同事，原大疆的工程师，原零度的工程师，和推友群友提出的宝贵意见和指导我不懂的问题。我知识水平有限，只能去求助各路大佬&lt;/p&gt;
&lt;h2 id=&quot;关于本系列&quot;&gt;&lt;a h</summary>
      
    
    
    
    <category term="self" scheme="https://a-wing.top/categories/self/"/>
    
    
    <category term="self" scheme="https://a-wing.top/tags/self/"/>
    
    <category term="log" scheme="https://a-wing.top/tags/log/"/>
    
    <category term="uav" scheme="https://a-wing.top/tags/uav/"/>
    
  </entry>
  
  <entry>
    <title>创新者异闻录（6/7）——无人机自动系统</title>
    <link href="https://a-wing.top/self/2021/02/25/innovator/2021-02-12-innovator-6"/>
    <id>https://a-wing.top/self/2021/02/25/innovator/2021-02-12-innovator-6</id>
    <published>2021-02-25T04:20:00.000Z</published>
    <updated>2021-02-25T04:20:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>本文是我在无人机的 IoT 应用架构（给机器人连上网线）上所做的尝试，在小规模场景下表现不错。大规模并没有机会测试，请谨慎参考</p><p>这个东西我也不知道有什么用，或许未来有用，留给若干年后做相同东西的人做参考吧</p><h2 id="无人机自动系统"><a href="#无人机自动系统" class="headerlink" title="无人机自动系统"></a>无人机自动系统</h2><p>这个名字是我自己刚取的，原本做这个时，不叫这个名字。我觉得这个名字更贴切一些</p><p>这是一个很新的东西，没人知道他是什么，该怎么做。近几年才有人提出无人机，无人机自动机场，无人机系统放在一起全自动飞行的概念</p><p>最常见的说法是无人机管理系统。第一反应肯定是类似 CMS&#x2F;ERP 一类的管理系统，当然首推以 Ruby on Rails 为首的管理系统思路。</p><p>但事实上并不是这样，管理系统要有在异常状态下可以接管无人机的能力。<br>要可以实时控制无人机，实时显示各种状态，无人机本身是一个复杂的系统，并且要高度追求稳定性，任何一点微小的错误都可能带来致命的后果，代码里有一个 BUG （飞机掉下来砸到人）。明天公司就直接没了</p><p>这其实并不是一个新的东西，这个其实是基于无人机的传统地面站基础上扩展的一个新的需求</p><h3 id="传统地面站系统"><a href="#传统地面站系统" class="headerlink" title="传统地面站系统"></a>传统地面站系统</h3><p>能够实时显示无人机的飞行姿态、飞行轨迹、位置及参数，实现任务航线的编辑与显示，发送飞行控制指令，实时性高，操作方便。地面站系统是一个古老的东西，可以追溯到军工无人机远程控制</p><p>再来回顾一下，以一个简单的航拍机为例，通信分成三部分</p><ul><li>遥控器</li><li>数传</li><li>图传</li></ul><p>传统地面站架构图</p><p><img src="/assets/img/innovator/ground_station_architecture-Traditional.svg" alt="Traditional ground station architecture"></p><p>一台普通的电脑加上数传再加上地面站程序就组成最基本的地面站系统。（地面站显示图像是新功能。摄像头是近几年刚装到无人机上的，而且图像也只是单纯的显示出来）</p><p>各大飞控都有自己的地面站程序：</p><ul><li><code>ardupilot</code> 有 mission planner（windows 专属） &#x2F; apm planner （用 qt 写的，跨平台的）</li><li><code>PX4</code> 项目有  QGC（QGroundControl）Q 是 Qt 的简写</li><li>大疆的飞机使用手机版的地面站：DJI GO。而大疆的遥控器，相当于把遥控器图传数传一体化了。通过遥控器的 usb 线连接手机就相当于连接了图传和数传，通过手机的地面站去看飞机状态，控制飞机</li></ul><h3 id="云端地面站系统"><a href="#云端地面站系统" class="headerlink" title="云端地面站系统"></a>云端地面站系统</h3><p>所谓的无人机自动系统，其实就是一个放在云端的 <strong>无人机地面站</strong></p><p>有了无人机自己机场这种东西后，无人机现场可以不需要人来控制，需要远程控制的地面站程序</p><p>由于这些东西全部接入了互联网，就产生了一个新需求，连后期的自动处理也可以一起放到系统里，就如同  CI （Continuous Integration）一样。每次飞机执行完任务，自动化把采集到的数据进行处理</p><p>举个自动处理的例子：比如飞机飞了很大一片区域，将图片拼接成一个大图。<br>（类似的还有很多，比如，拍一个建筑物生成三维的云图。矿山识别土堆体积，计算采矿量。水务飞到河流上采水样检测水质。带着空气传感器检测空气质量）。。。<br>或者之后把除了的结果放到 GIS （Geographic Information Systems，地理信息系统）里面</p><p><img src="/assets/img/innovator/ground_station_architecture-Cloud.svg" alt="Cloud ground station architecture"></p><p>使用的 draw.io 绘制的，源文件在这里，可以随意使用 <a href="/assets/img/innovator/ground_station_architecture.drawio">ground_station_architecture.drawio</a></p><p>所以最为关键的功能是实时通信，网页手动控制端，后台自动控制端要实时保持通信，在自动执行异常时手动接管时最重要的功能。<br>这个和一般的互联网产品截然不同，飞机的远程控制系统不能有无法控制飞机的 BUG。实时性和长连接变成了这个系统最重要的功能</p><p>本质上其实是一个 <strong>表面上看起来像管理系统的 IM （Instant Messaging，即时通讯）系统</strong></p><h3 id="多用户同时使用：并发控制"><a href="#多用户同时使用：并发控制" class="headerlink" title="多用户同时使用：并发控制"></a>多用户同时使用：并发控制</h3><p>或许你会看到这样的设备，当一个客户端连接上时，第二个客户端时不能连上的。两个客户端的操作会有冲突。为了避免这种情况，所以大部分硬件控制都是独占的。（最常见的就是路由器，好多路由器管理界面加了限制。同一时刻只能有一个人控制）</p><p>控制是否要独占？A 用户控制时 B 用户能不能控制？要加互斥锁吗？我个人的观点是 B 也可以控制。因为自动执行任务也算一个用户在控制</p><p>我们先回退到传统的无人机，无人机飞航线时是可以手动控制飞机的，紧急情况还是要依赖人的判断。或者会防止误触加锁。但时底层时支持这样的操作的，这个一个无锁的实现</p><blockquote><p>物理上硬件的安全高于一切，避免炸机才是最优先考虑的</p></blockquote><p>放到无人机自动系统也是一样，要 <strong>无锁的底层实现</strong>  。业务上的需求可以为了限制使用来加锁。</p><h2 id="Flux-架构和-IoT（Internet-of-Things）"><a href="#Flux-架构和-IoT（Internet-of-Things）" class="headerlink" title="Flux 架构和 IoT（Internet of Things）"></a>Flux 架构和 IoT（Internet of Things）</h2><p>无人机自动系统也许把他归类到物联网（IoT）或许更贴切一些</p><p>Flux 是由 Facebook 官方提出的一套前端应用架构模式。它的核心概念是单向数据流。它不是具体的框架，而更像是一种软件开发模式。</p><p>前端同学应该对 Flux 非常熟悉（vuex，redux 等状态管理都是这个的实现）。不熟悉也没有关系。设计模式的 “中介者模式” 应该都知道，和这个有些类似，不知道也没有关系。这个基本思想本身很简单</p><blockquote><p>Flux 的核心就是一个简单的约定：视图层组件不允许直接修改应用状态，只能触发 action。<br>应用的状态必须独立出来放到 store 里面统一管理，通过侦听 action 来执行具体的状态操作。<br>——尤雨溪</p></blockquote><p><img src="/assets/img/innovator/flux-simple-f8-diagram-with-client-action-1300w.png" alt="flux diagram"></p><blockquote><p>Flux 的最大特点，就是数据的 “单向流动”</p></blockquote><p>这个四个部分的关系：</p><ul><li><code>View</code>： 视图层</li><li><code>Action</code>（动作）：视图层发出的消息（比如 <code>mouseClick</code> ）</li><li><code>Dispatcher</code>（派发器）：用来接收 Actions、执行回调函数</li><li><code>Store</code>（数据层）：用来存放应用的状态，一旦发生变动，就提醒 Views 要更新页面</li></ul><p>这个是本来是 Web 前端的实现，现在把这个放到一个 IoT 应用的全栈框架上，就像这样：</p><ul><li><code>View</code> 就变成了前端</li><li><code>Action</code> 变成了前后端对嵌入式设备的 RPC（Remote Procedure Call）</li><li><code>Dispatcher</code> 由具体的嵌入式设备来实现（各种功能函数，执行的各种动作）</li><li><code>Store</code> 变成了全部从嵌入式设备的全部状态树。当然，要可以响应式的更新数据</li></ul><p>这里使用单一状态树，用一个对象就包含了全部的应用层级状态。至此它便作为一个 “<strong>唯一数据源</strong> （SSOT（Single source of truth））” 而存在</p><p>在这样的系统里，对应的是物理上的实体，系统中储存的状态并不能表示物体的真实状态。所以 <strong>唯一数据源就是物理上状态</strong></p><p>由于使用单一状态树，应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时，store 就有可能变得相当臃肿。<br>比如 <code>vuejs</code> 官方的状态管理是 Vuex 将 store 分割成模块（module）。当然我们也可以分割我们的状态树（我们可以使用 MQTT 路由来分割）</p><h3 id="MQTT（Message-Queuing-Telemetry-Transport）"><a href="#MQTT（Message-Queuing-Telemetry-Transport）" class="headerlink" title="MQTT（Message Queuing Telemetry Transport）"></a>MQTT（Message Queuing Telemetry Transport）</h3><p>我知道你肯定有问题，用户，权限该如何分割，这里祭出我们的神器 MQTT（注：我做的这个系统和 MQTT 高度耦合）</p><p>我原来写过一篇关于 mqtt 的文章（没看过也没有关系，我用几句话简单介绍一下 MQTT 的核心特性）：</p><ol><li>MQTT 是队列，使用 <code>pub/sub</code> （或者可以把他看成 RabbitMQ ）</li><li>有 QoS 可以保证可达性</li><li>Retain（驻留）。或者说是置顶（就像你新加入一个聊天室自动给你发的消息一样）</li><li>消息路由支持通配符（可以批量加入一堆聊天室（包括当前还不存在的））</li></ol><p>比如使用设备状态： <code>nodes/&lt;id&gt;/msg/&lt;type&gt;</code> ⇒ <code>nodes/1/msg/battery</code> 但是除了 <code>battery</code> 还有其他的的比如 <code>position</code> （位置），<code>gimbal</code> （飞机云台，有些飞机可能没有云台）。<br>配合 MQTT 路由通配符就是可以直接 订阅这个 topic  <code>nodes/1/msg/+</code> （使用 <code>msg</code> 作为 prefix）</p><p>按照 MQTT 的路由规则可以 <code>nodes/+/msg/+</code> 全部设备的全部消息。<br>当然，要考虑权限问题，可以在用户的标识和 <code>id</code> 做限制（在 MQTT broker 里限制），让这个用户只能收到一部分 <code>id</code> 的消息（用户权限限制）</p><p>状态同步单向数据流到 mqtt sub 来实现，拿到消息更新到前端的 store，之后 MVVM 的数据绑定，视图更新。（有些状态改变是过程量（比如电机工作传感器值会一直变），会受到状态同步频率的限制，目前：我把限制设置在每秒每种类型的的消息最多一条）</p><blockquote><p>或许你还会有疑问：刚启动时怎么办？因为没有消息，消息是响应式触发</p></blockquote><p>这里再配合 MQTT 的 retain 来保存最新的状态（加个 Redis 之类的，启动时先拉一缓存的最新状态）。如果丢消息可以考虑 QoS 。有些中间的状态变化是可以丢掉的。</p><p>这套状态管理在这样的 IoT 架构中实际使用中的表现非常好</p><h3 id="jsonrpc2-over-MQTT"><a href="#jsonrpc2-over-MQTT" class="headerlink" title="jsonrpc2 over MQTT"></a>jsonrpc2 over MQTT</h3><p>状态同步之不过是 <code>Store</code> ⇒ <code>View</code> 的实现，我们需要定义 <code>Action</code> 的实现。<code>Action</code> 其实就是个 RPC，所以大部分 RPC 协议都是可行的</p><p>按照 jsonrpc2 标准文档的设计，可以跑在任何的传输协议里。所以我们来让它使用 MQTT 来传输</p><p>使用 jsonrpc2 更主要的原因是容易调试，直接可以通过 mosquitto 命令行去 debug</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">mosquitto_pub -L mqtt://user:pass@localhost:1883/nodes/1/rpc/send \</span><br><span class="line">-m <span class="string">&#x27;&#123;&quot;jsonrpc&quot;:&quot;2.0&quot;,&quot;id&quot;:&quot;test-1&quot;,&quot;method&quot;:&quot;mission.create&quot;,&quot;params&quot;:[]&#125;&#x27;</span></span><br></pre></td></tr></table></figure><blockquote><p>我直到现在还在质疑，jsonrpc2 over MQTT 到底是不是一个好的方案</p></blockquote><p>这个地方是我设计的有问题的，会在特定的极限条件下丢消息，只能配合 mqtt v5 的新特性 请求响应 （request-response） 来解决（其实也不够严谨）</p><p>虽然 MQTT 是可以双向通信的，但我还是把 IO 分成两个 Topic: <code>nodes/:id/rpc/send</code> 和 <code>nodes/:id/rpc/recv</code>。<br>这样其实是因为 MQTT v3 里会 Sub 到自己 Pub 的消息</p><p>这个本身就是个权衡之后的考虑，为了可靠性要加入很多确认。但同时调试又会变得复杂</p><h3 id="为什么不是-MAVLink"><a href="#为什么不是-MAVLink" class="headerlink" title="为什么不是 MAVLink"></a>为什么不是 MAVLink</h3><p>做过无人机或机器人相关的或许会对这个协议非常熟悉，MAVLink （Micro Air Vehicle Link） 协议原自 px4 项目。<br>后来 ardupliot 也使用了 MAVLink 作为通信协议（有些遥控器支持数据回传功能的，直接在硬件上集成这个协议，然后读取状态）。<br>可以通过 MAVLink 来控制一切，MAVLink 就快变成了一个标准</p><p>为什么不是 mavlink 跑在 tcp&#x2F;udp 或者外面套一层 websocket ?</p><blockquote><p>做出不使用 mavlink 作为主通信协议的提议的我在当时是饱受同事的质疑的（同事做机器人的人比较多）</p></blockquote><ol><li>mavlink  协议比较适合跑在 udp 协议里，他在协议里面自己实现了 qos，质量控制（相当于自己实现了 tcp）。</li><li>数据结构是自己定义格式，通过自己的 generate 去生成编码和解析</li><li>这个是飞机自带的，机场是我们从头做的，机场也要再实现一遍 mavlink</li><li>这样后端的自动控制，和前端的手动控制都要和 mavlink 深度绑定</li><li>mavlink 设计之初就不是为了互联网传输，为了高度实时设计的，透过互联网传输之后，这种高度实时的特性已经很难保证了</li><li>mavlink 是支持方言的，可以自定义标准。这意味着状态管理还要提供一层转换来支持各种方言</li><li>mavlink 除了最原始的 C 的那个库，其他的库代码质量堪忧（又不是不能用.jpg）</li></ol><blockquote><p>最重要的原因是：要跑着浏览器上 mavlink 的消息频率太高。同时控制几个飞机，浏览器就要处理不过来了。？什么用 WebWorker 来解析 。定时再同步状态吗？这太糟糕了。</p></blockquote><p>基于以上原因，选择了自己造通信的方式，自己做了一层 MAVLink 到自有协议的转换层。只传输需要传输的消息</p><h3 id="如何调试和自动测试"><a href="#如何调试和自动测试" class="headerlink" title="如何调试和自动测试"></a>如何调试和自动测试</h3><p>我们使用了物理实体的状态作为唯一数据源，为了稳定性，单元测试，集成测试是必须的。</p><p>所以，我们需要一个模拟器，模拟物理上存在的硬件接口，各种飞控都自带模拟器，只需要自己实现一个硬件设备的模拟器就好（当然只是 IO 模拟，对物理世界建模来模拟这个太没必要了）</p><h2 id="可定制流程处理和协同作业"><a href="#可定制流程处理和协同作业" class="headerlink" title="可定制流程处理和协同作业"></a>可定制流程处理和协同作业</h2><p>先来看一段 TED 视频</p><div style="max-width:854px"><div style="position:relative;height:0;padding-bottom:56.25%"><iframe src="https://embed.ted.com/talks/vijay_kumar_robots_that_fly_and_cooperate" width="854" height="480" style="position:absolute;left:0;top:0;width:100%;height:100%" frameborder="0" scrolling="no" allowfullscreen></iframe></div></div><p>像这样的协同作业有两种：蜂群或蚂蚁一样的完全自主协同作业，或者固定的中心调度协同作业</p><p>完全自主协同作业目前是不可能的，主要是规模问题。无人机的数量短期根本达不到需要自主协同的规模（分布式那套东西完全就不用考虑）。所以只能是指挥中心告知具体的设备去执行工作</p><p>该如何描述这样的一种控制流？</p><ul><li>有限状态机（状态机，FSM）</li><li>有向无环图 DAG</li><li>佩特里网 Petri Net</li></ul><p>我们一个一个来分析：</p><h3 id="有限状态机-Finite-State-Machine（FSM）"><a href="#有限状态机-Finite-State-Machine（FSM）" class="headerlink" title="有限状态机 Finite State Machine（FSM）"></a>有限状态机 Finite State Machine（FSM）</h3><p>有限状态机可以有方向，可以有环。但不能描述并发。这个需求一定会有并行，比如飞机和机场的协同工作一定会有并行的状态。（物理世界是一定有并行的）</p><p><img src="/assets/img/innovator/workflow-FSM.svg" alt="workflow FSM"></p><h3 id="有向无环图-Directed-Acyclic-Graph（DAG）"><a href="#有向无环图-Directed-Acyclic-Graph（DAG）" class="headerlink" title="有向无环图 Directed Acyclic Graph（DAG）"></a>有向无环图 Directed Acyclic Graph（DAG）</h3><p>我们可以使用有向无环图（DAG）来加入并行。但是作为一个物理实体控制，当然是要有环的</p><p>任务的 DAG 图</p><p><img src="/assets/img/innovator/workflow-DAG.svg" alt="workflow DAG"></p><p>由于不能有环，所以启动飞机失败后没有办法再启动飞机</p><p> DAG 可以很容易的描述依赖关系，比如 Gitlab CI 的 DAG 图</p><p><img src="/assets/img/innovator/dag_graph_example_v13_1.png" alt="Gitlab DAG"></p><h3 id="佩特里网-Petri-Net（place-transition-PT-net）"><a href="#佩特里网-Petri-Net（place-transition-PT-net）" class="headerlink" title="佩特里网 Petri Net（place&#x2F;transition (PT) net）"></a>佩特里网 Petri Net（place&#x2F;transition (PT) net）</h3><p><img src="/assets/img/innovator/workflow-PTN.svg" alt="workflow PTN"></p><p>使用的 draw.io 绘制的，源文件在这里，可以随意使用 <a href="/assets/img/innovator/workflow.drawio">workflow.drawio</a></p><p><strong>有限状态机</strong> 和 <strong>有向无环图</strong> 的问题可以通过 Petri Net 来解决。这样，并行和有环就都解决了。但还有问题，飞机支持的挂载不同，执行的动作可能是不确定的。需要编辑任务提供个输入框。</p><p>但是还不够，可能要加入自己定义的函数和判断条件。需要在各函数里面嵌入各函数扩展。比如：对起飞条件的判断。Petri Net 可以描述流程，但还要嵌入算法。所以还需要一种扩展性更强的方案</p><h3 id="图灵完备"><a href="#图灵完备" class="headerlink" title="图灵完备"></a>图灵完备</h3><blockquote><p>只要图灵机可以被实现，就可以用来解决任何可计算问题</p></blockquote><p>可以祭出老祖宗留下来的神器：<strong>图灵完备</strong>。把各种功能都做成 API，然后用户自行上传脚本来描述工作流程。<br>这其实也不是什么新东西。你看 <code>nginx</code> 都可以用 <code>lua</code> 扩展。嵌入 <code>lua</code> 当作流程控制的并不再少数（你看游戏领域）。<br>对于控制硬件动作使用 <code>lua</code> 描述动作也不少应用</p><p>如果再把 API 做成异步的，就可以支持并行的动作和协同控制</p><p>嵌入一个脚本语言。经过一番调研底层使用 <code>golang</code> 可行的方案大概有：<code>lua</code> ，<code>lisp</code>，<code>JavaScript</code>，<code>starlark</code>(python 的方言)。或者直接传 AST（Abstract Syntax Tree）</p><p>我最终还是选择了 <code>lua</code> 。先一个一个来看</p><ul><li><code>starlark</code> 不兼容 python ，甚至没有异常处理。还会给人用 python 的错觉，也用不了 python 的库（还有我个人不喜欢缩进作为语法的一部分，因为我作为这个系统的设计者的品味所以不用）</li><li><code>lisp</code> 我真怕写脚本的人理解不明白（我自己都还不会用）</li><li><code>JavaScript</code> 我纠结了好久，原因是因为 <code>JavaScript</code> 那几个神奇的判断 <code>null == undefined</code> 之类的那种</li></ul><p>不过我现在有时还在思考，使用 <code>JavaScript</code> 或许是一个更好的方案。<br>因为，实际使用中有很多是异步（<code>lua</code> coroutine 作为流程控制，太不直观 。还是自己造事件循环模仿 <code>JavaScript</code> 或许更好些）。<br>还有支持流程运行中用户会发信号进来，这个需求是在设计上完全没有考虑过的。这只能说是在开发之前对于使用场景的预估有偏差导致的</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;本文是我在无人机的 IoT 应用架构（给机器人连上网线）上所做的尝试，在小规模场景下表现不错。大规模并没有机会测试，请谨慎参考&lt;/p&gt;
&lt;p&gt;这个东西我也不知道有什么用，或许未来有用，留给若干年后做相同东西的人做参考吧&lt;/p&gt;
&lt;h2 id=&quot;无人机自动系统&quot;&gt;&lt;a hre</summary>
      
    
    
    
    <category term="self" scheme="https://a-wing.top/categories/self/"/>
    
    
    <category term="self" scheme="https://a-wing.top/tags/self/"/>
    
    <category term="log" scheme="https://a-wing.top/tags/log/"/>
    
    <category term="uav" scheme="https://a-wing.top/tags/uav/"/>
    
  </entry>
  
  <entry>
    <title>创新者异闻录（5/7）——无人机自动机场</title>
    <link href="https://a-wing.top/self/2021/02/21/innovator/2021-02-12-innovator-5"/>
    <id>https://a-wing.top/self/2021/02/21/innovator/2021-02-12-innovator-5</id>
    <published>2021-02-21T07:20:00.000Z</published>
    <updated>2021-02-21T07:20:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>这篇文章有很多没法详细说的东西，需要想象力。想象这个东西是什么样子的（最好先看几集高达）</p><p><img src="/assets/img/innovator/sb-video-poster.jpg" alt="sbim superdock"></p><h2 id="无人机航拍的作业流程"><a href="#无人机航拍的作业流程" class="headerlink" title="无人机航拍的作业流程"></a>无人机航拍的作业流程</h2><ul><li>前一天，把电池充满电（锂电池的最佳保存电压不是满电）</li><li>去现场</li><li>拿出飞机</li><li>安装桨叶</li><li>校准磁罗盘</li><li>安装 SD 卡</li><li>启动遥控器</li><li>连接手机</li><li>启动飞机</li><li>起飞前检查，等待 GNSS 信号</li><li>起飞</li><li>作业（切换模式：飞个固定航线）</li><li>降落</li><li>拔出 SD 卡</li><li>下载数据</li><li>回去处理数据</li></ul><p>这是一个固定的流程，每次使用都要重复。或许可以使用一个机器来完成这些动作</p><h2 id="无人机自动机场的开始：airobotics-和-intel"><a href="#无人机自动机场的开始：airobotics-和-intel" class="headerlink" title="无人机自动机场的开始：airobotics 和 intel"></a>无人机自动机场的开始：airobotics 和 intel</h2><p>无人机自动机场的历史可以追溯到美国空军在 1968 年首次尝试使用地面状态下的 “盒子里的无人机” 技术。解决面临高操作和维护成本。</p><p>随着近年来小型无人机商用无人机的发展。终于，出现了自动商用无人机解决方案。</p><p>2017 年 intel 的 <a href="https://www.airoboticsdrones.com/">Airobotics</a>，是世界上第一个被授权在没有飞行员的情况下驾驶全自动无人机，允许超视距商用无人机 超视距飞行（（BVLOS）Beyond Visual Line of Sight） 操作</p><div align="center"><img alt="Airobotics Solution" src="/assets/img/innovator/drone_flying.gif"><img alt="Automated Industrial Drones Solution" src="/assets/img/innovator/drone_box.png"></div><blockquote><p>传统的无人驾驶飞机（uav）包括一架无人驾驶飞机和某种形式的地面控制器。</p><p>（UAV &#x3D; 无人的飞机 + 地面控制器）</p><p>另外，“盒中无人机” 系统可以在一个盒子上自动运行，盒子同时也可以作为起落平台和充电基地。<br>在执行了预先设定的指令列表后，他们会回到 “基地” 进行充电和上传信息。</p></blockquote><p>让无人机完全自动运行的系统由三个主要组件组成：</p><ol><li>无人机本身</li><li>为无人机充电和收纳无人机的地面站（就是无人机自动机场）</li><li>允许操作员与系统交互的计算机管理系统（下一篇文章会讲的：无人机自动系统）</li></ol><h2 id="无人机自动机场是什么"><a href="#无人机自动机场是什么" class="headerlink" title="无人机自动机场是什么"></a>无人机自动机场是什么</h2><p>我们先来思考一个问题。我每天都要用无人机去拍一遍某样东西（举个例子：工地吧），每天固定航线，固定开机，维护电池都需要人来控制（专业领域无人机操作，要考证的，要专门的飞手），每天都拍到的东西都一点区别。</p><p>无人机场存在的价值就是为了把这个人省掉，收纳无人机，自动充电（或换电池），中继无人机通信。简单的说：就是无人机的飞行再也不需要人来干预（把地面的人也给省掉了）</p><p>无人机的无人，是把坐在飞机上的驾驶员省掉了。驾驶员在地面上远程控制。无人机自动机场：是把地面上的拿着遥控器的驾驶员也省掉了，换成一个机器，全自动的工作</p><p>无人机自动机场是一个集无人机收纳和电源管理为一体的设备。更本质上来说：<strong>让无人机实现自动飞行的辅助硬件</strong></p><p>这篇是我们同行写的：<a href="https://zhuanlan.zhihu.com/p/225569044">深度解读：无人机机场是什么？</a></p><h2 id="无人机自动机场实现的基础"><a href="#无人机自动机场实现的基础" class="headerlink" title="无人机自动机场实现的基础"></a>无人机自动机场实现的基础</h2><p><strong>无人机自动机场</strong> 实现的基础是 <strong>无人机全自动飞行</strong> 。自动飞行，起飞条件判断，自动降落，异常处理</p><p>自动飞行这个分成两种情况：<strong>按照固定航线飞行</strong> 和 <strong>全自主飞行</strong></p><p>按照固定航线飞行就是事先规划好航线，无人机按照这个航线飞行</p><p>全自主飞行 目前技术上只有极少数有能力做这个</p><h3 id="无人机协处理器"><a href="#无人机协处理器" class="headerlink" title="无人机协处理器"></a>无人机协处理器</h3><p>起飞条件判断，自动降落，异常处理 该如何实现。上面挂一个上位机来判断条件，模拟人的手动控制（我原来拆过一台做无人机表演的飞机，也是这么干的，参照前面的文章）</p><p>为什么会有这么畸形的实现，因为无人机飞控过于复杂（RTOS 有实时性要求），之后就产生了一种新思路，把无人机飞控当黑盒来使用，模拟人的控制程序放到上位机（比如：放个树莓派）。上位机负责和机场通信和航线选择</p><p>无人机和无人机自动机场放在一起时，就出现了一个全新的需求，自动飞行完数据如何采集，如何远程控制飞机，定时执行任务，自动调度，需要一个无人机控制中心来控制这些无人机和自动机场</p><h2 id="起飞前检测"><a href="#起飞前检测" class="headerlink" title="起飞前检测"></a>起飞前检测</h2><p>飞机对于天气的要求是很高的。但要检测的项目可能是个不固定的，飞机可能会挂个特殊的挂载，然后再起飞前去检查这个特殊的挂载的状态是否符合要求。<br>有些判断需要硬件接口的支持。比如为了实时风速的准确要加个风速传感器。</p><p>判断电源判断，这个有意思，有些飞机要开机之后才能通过 API 读到电量。飞机上设备都很耗电的（就算螺旋桨不动，其他部件也很耗电）。所以有时可以看到怪异情况，飞机先开机，读一下电量，之后再关机</p><h2 id="精准降落"><a href="#精准降落" class="headerlink" title="精准降落"></a>精准降落</h2><p>比如把大疆精灵降落到 <code>1m*1m</code> 的平面内，这听起来似乎很容易。如果加个限制条件：完全自动控制。100% 一定要成功（不成功就成仁了）</p><blockquote><p>地面效应，侧面风，异常处理</p></blockquote><p>关于精准降落时如何实现的，属于商业机密。（等到过几年，不是机密时我再来讲吧）。不过可以讲几种目前公开的，大家都知道的方法</p><h3 id="GNSS-的误差"><a href="#GNSS-的误差" class="headerlink" title="GNSS 的误差"></a>GNSS 的误差</h3><p>因为有机场的存在，可以正好把 RTK 的基站放到机场里面。RTK 可以达到厘米级误差，那不是很容易</p><p>GNSS&#x2F;RTK 有信号不好的时候，这对于降落到机场里面是致命的。信号不好可以一直等，但飞机在飞行，不能一直等下去</p><p>当然我们可以找一个很空的地方，作为飞机降落的备用点，至少可以保证飞机不炸机。之后派人过去处理（降落时基本上都是就快没电了）</p><h3 id="红外引导降落"><a href="#红外引导降落" class="headerlink" title="红外引导降落"></a>红外引导降落</h3><p>GNSS&#x2F;RTK 就算再不济也能把误差控制在 1M 以内。只要在这个范围内提高精度就可以了</p><p>典型的就是开源飞控的 <a href="https://irlock.com/">IR-Lock</a> （IR（Infrared）红外线）方案，分成三部分 红外信标，特制的摄像头，高精度测距模块（一般是激光雷达）</p><p>红外信标放在降落点发射红外信号，特殊的摄像头在机身下面来识别，配合高度解出相对位置来控制精准降落</p><h3 id="大疆的图像识别方案"><a href="#大疆的图像识别方案" class="headerlink" title="大疆的图像识别方案"></a>大疆的图像识别方案</h3><p>可以使用纯软件的方式来实现类似 IR-Lock 的功能。飞机在刚起飞时摄像头朝下拍一张照片，飞机降落时摄像头朝下和起飞时的照片对比，找到起飞点。大疆的飞机就内置了这样的功能。不过精准降落需要地面有明显的纹理，用于下视传感器的识别</p><h3 id="大小二维码"><a href="#大小二维码" class="headerlink" title="大小二维码"></a>大小二维码</h3><p>原理是多个尺寸不同的二维码，高度高时使用大的，高度低时使用小的，不断缩小定位精度</p><p><a href="https://flytbase.com/precision-landing/">Drone Precision Landing System With Widest Compatibility</a></p><h3 id="Ultra-Wideband（UWB）"><a href="#Ultra-Wideband（UWB）" class="headerlink" title="Ultra-Wideband（UWB）"></a>Ultra-Wideband（UWB）</h3><p>VR 应该大家都熟悉，有人思考过把 VR 的定位方案放到无人机上吗？根据。GNSS&#x2F;RTK 的方案再配合空间定位，可以精准的知道位置。</p><p>VR 的定位方案有很多，UWB 是其中之一，iPhone11 也在硬件上加入了这个东西</p><p><a href="https://www.sciencedirect.com/science/article/abs/pii/S1270963816306277">A UWB positioning network enabling unmanned aircraft systems auto land</a></p><h3 id="Simultaneous-localization-and-mapping（SLAM）"><a href="#Simultaneous-localization-and-mapping（SLAM）" class="headerlink" title="Simultaneous localization and mapping（SLAM）"></a>Simultaneous localization and mapping（SLAM）</h3><p><a href="https://zh.wikipedia.org/wiki/%E5%90%8C%E6%97%B6%E5%AE%9A%E4%BD%8D%E4%B8%8E%E5%9C%B0%E5%9B%BE%E6%9E%84%E5%BB%BA">同时定位与地图构建</a></p><p>这个又回到了 视觉识别，这个是用双目摄像头来感知来识别</p><h2 id="无人机机场需要有什么？"><a href="#无人机机场需要有什么？" class="headerlink" title="无人机机场需要有什么？"></a>无人机机场需要有什么？</h2><p>这个问题其实很复杂，要考虑的问题很多。比如：给无人机充电，收纳无人机，信号中继</p><h3 id="电源管理"><a href="#电源管理" class="headerlink" title="电源管理"></a>电源管理</h3><p>如果要个无人机充电，给电源接到无人机上，目前有两个思路，有线像电源插头一样，插上。无线充电线圈</p><p>由于无线充电线圈的实际效果过于拉垮，线圈很重，对无人机这点太要命了。而且充电效率太低</p><p>目前实际使用还是要有线充电，这样就要把插头插进去，无人机自动降落到平台上的精度远远不够，所以需要一个物理上的机械结构把他归正到位置上，然后把插头插上（想象一下，高达那个回到仓位，固定器把高达固定的场景）</p><p>虽然我也很想配个图来说明这个。不过，由于这个实现目前还是保密内容，这里请自行想象：有两个互相垂直的轴，每次把飞机推到一个固定位置后，连接电源</p><p>也有每次给飞机换电池的，有用机械手的，也有特殊设计的机械结构的。这方面没有公开的信息，请自行脑补（想象一下，给高达换燃料包）</p><h3 id="收纳无人机"><a href="#收纳无人机" class="headerlink" title="收纳无人机"></a>收纳无人机</h3><p>这个东西是一直工作在室外，无人机是很娇贵的，当然不能暴露在外面。收纳无人机就涉及到舱门（就和高达里的母舰打开舱门，放出高达一样。机场打开舱门，飞机飞出去）</p><p>机场里面要有温控系统，保暖或制冷（电池比较娇贵），当然也要防雨</p><h3 id="信号中继"><a href="#信号中继" class="headerlink" title="信号中继"></a>信号中继</h3><p>这个取决于无人机使用了什么样的通信方案，如果无人机可以使用 4G 或 5G。那就不需要中继信号了</p><p>如果使用其他频段的信号来通信，通信汇总到机场，然后透过网络给云端的控制系统。一般都需要一个信号塔，或者把天线伸出来</p><h2 id="异常处理"><a href="#异常处理" class="headerlink" title="异常处理"></a>异常处理</h2><p>如果这个东西附近没有人，完全自动运行，要考虑所有的异常情况。在工作流下，任何一个地方都是可能出问题的。软件，机械，电子。所以要把所以可能出现的情况都考虑进去</p><h3 id="供电异常-UPS"><a href="#供电异常-UPS" class="headerlink" title="供电异常 UPS"></a>供电异常 UPS</h3><p>试想一个情况，飞机飞出去执行任务了。然后停电了。设备突然断电会导致一些组件的异常，软件的，硬件的都会有。</p><h3 id="降落异常-备用点"><a href="#降落异常-备用点" class="headerlink" title="降落异常 备用点"></a>降落异常 备用点</h3><p>虽然考虑了这么多。但还是可能存在异常情况，对待完全预计之外无法降落的情况。就降落到备用点</p><h3 id="降落异常-远程手动降落"><a href="#降落异常-远程手动降落" class="headerlink" title="降落异常 远程手动降落"></a>降落异常 远程手动降落</h3><p>对于一些异常情况，可能需要人远程控制无人机（把通知直接发到手机上）。把遥控器底层的控制信号，在控制系统里面也实现一个，直接远程看图传，模拟遥控器，手动控制降落</p><h2 id="紧急停止"><a href="#紧急停止" class="headerlink" title="紧急停止"></a>紧急停止</h2><p>你知道飞机解锁时的内八吧，如果你在天空中持续这个动作一会（执行上锁），飞机会紧急停止（一键炸机）然后掉下来。</p><p>Why is it so designed？</p><p><a href="https://kanzhaji.com/sitemaster/7341.html">说说大疆创新产品内外八字掰杆停机的那些事儿 - KANZHAJI 看炸机-无人机资讯站</a></p><p>作为一个具有物理实体的机器人系统，一定要考虑失控的问题，要有急停</p><blockquote><p>安全第一，可以把零件的强度做的很低，碰到人的时候自己会先一步坏掉。尽量不要存在伤到人的可能性。</p><p>有些工厂里的设备明明一只手就可以操作，但设计上是一定两只手同时使用，这样就不会有空余的手乱放，降低出事故的风险</p></blockquote><p>讲个故事吧：我同事在调试设备的时候没注意，设备动了起来，夹到手了。如果没有急停，手可能就废了。。。</p><blockquote><p>三星工厂发生停电事故，内存、闪存、固态硬盘价格可能要涨</p></blockquote><p>相信大家对这个应该都记忆犹新，对于一个从不关机的设备来说，停电或急停并不只是机器停下来。急停后的恢复过程可能是非常长的</p><h2 id="换吊舱，换负载，送快递，送外卖"><a href="#换吊舱，换负载，送快递，送外卖" class="headerlink" title="换吊舱，换负载，送快递，送外卖"></a>换吊舱，换负载，送快递，送外卖</h2><p>这个是成本问题：飞机和机场做的足够便宜，就不需要换吊舱</p><p>送快递和外卖也是，反正不会从 China Mainland 开始普及</p><p>这个鬼地方人工太便宜了，比机器人还便宜，除非电池技术有突破性的进展，或者人力成本突然涨起来（主要成本是电池寿命，电池是消耗品（大概 100～200 个循环，还有其他因素，充电策略，温度都会影响））</p><h2 id="未来的无人机——大疆的新挑战者-skydio"><a href="#未来的无人机——大疆的新挑战者-skydio" class="headerlink" title="未来的无人机——大疆的新挑战者 skydio"></a>未来的无人机——大疆的新挑战者 <a href="https://skydio.com/">skydio</a></h2><p>这样的自主飞行才是未来，才是真正的无人，对于周围环境实时建模（SLAM），自主规划路径</p><iframe width="560" height="315" src="https://www.youtube.com/embed/Gh5pAT1o2V8" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;这篇文章有很多没法详细说的东西，需要想象力。想象这个东西是什么样子的（最好先看几集高达）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/img/innovator/sb-video-poster.jpg&quot; alt=&quot;sbim superdock&quot;&gt;&lt;/p&gt;
&lt;h2 id</summary>
      
    
    
    
    <category term="self" scheme="https://a-wing.top/categories/self/"/>
    
    
    <category term="self" scheme="https://a-wing.top/tags/self/"/>
    
    <category term="log" scheme="https://a-wing.top/tags/log/"/>
    
    <category term="uav" scheme="https://a-wing.top/tags/uav/"/>
    
  </entry>
  
  <entry>
    <title>创新者异闻录（4/7）——多旋翼无人机的组成扩展</title>
    <link href="https://a-wing.top/self/2021/02/19/innovator/2021-02-12-innovator-4"/>
    <id>https://a-wing.top/self/2021/02/19/innovator/2021-02-12-innovator-4</id>
    <published>2021-02-19T20:20:00.000Z</published>
    <updated>2021-02-19T20:20:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>有很多零件对于多旋翼无人机来说，是可选的</p><h2 id="科普-SDR-原理"><a href="#科普-SDR-原理" class="headerlink" title="科普 SDR 原理"></a>科普 SDR 原理</h2><p>软件无线电，即 Software Defined Radio（SDR）通俗来讲，SDR 就是基于通用的硬件平台上用软件来实现各种通信模块。</p><p>由通用计算平台来处理信号，只要硬件支持这个频率就可以收发任何的信号，可以用这个来实现任何无线的通信</p><p>发送端：</p><p><img src="/assets/img/innovator/sdr_send.jpg" alt="sdr send"></p><p>接收端：</p><p><img src="/assets/img/innovator/sdr_recv.jpg" alt="sdr recv"></p><p>可以参考这篇：</p><p><a href="https://zhuanlan.zhihu.com/p/24217098">软件无线电 SDR技术原理深入解析 USRP收发原理</a></p><h2 id="全球导航卫星系统-Global-Navigation-Satellite-System（GNSS）"><a href="#全球导航卫星系统-Global-Navigation-Satellite-System（GNSS）" class="headerlink" title="全球导航卫星系统 Global Navigation Satellite System（GNSS）"></a>全球导航卫星系统 Global Navigation Satellite System（GNSS）</h2><p>GPS 显然已经成了 GNSS 的代名词，很多人提及 GPS，就想到卫星导航，实质上这是对 GPS 的一个误解，同样也是对 GNSS 的混淆。<br>GPS 是由美国国防部研制建立的一种具有全方位、全天候、全时段、高精度的卫星导航系统，能为全球用户提供低成本、高精度、有差别的 PNT（Positioning, Navigation and Timing） 服务</p><p>GPS 是美国的卫星导航系统，类似的还有中国的 BeiDou（北斗） ，欧盟的 Galileo （伽利略），俄罗斯的 GLONASS 印度的 NavIC 和 日本的 QZSS</p><h3 id="单点定位"><a href="#单点定位" class="headerlink" title="单点定位"></a>单点定位</h3><p><img src="/assets/img/innovator/gnss_detail.jpg" alt="gnss detail"></p><p>这样就可以解出所在位置</p><table><thead><tr><th>使用卫星数</th><th>定位</th><th>备注</th></tr></thead><tbody><tr><td>0</td><td>无</td><td></td></tr><tr><td>1</td><td>无</td><td>可以授时 UTC 时间和日期</td></tr><tr><td>2</td><td>无</td><td></td></tr><tr><td>3</td><td>2D Fixed</td><td>经纬度，没有海拔</td></tr><tr><td>4</td><td>3D Fixed</td><td>经纬度海拔</td></tr><tr><td>&gt;4</td><td>DGPS</td><td>需要其他频段，接收伪距信号</td></tr><tr><td></td><td></td><td></td></tr></tbody></table><p>GNSS 搜星越多定位越精确，上表不是绝对的，搜星数不等于解算时的使用数</p><p>GNSS 解算可以参照这位推友（相对论会影响 GNSS 解）</p><div class="twitter-wrapper"><blockquote class="twitter-tweet"><a href="https://twitter.com/IIInoki/status/1358750243798548482"></a></blockquote></div><script async defer src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><h3 id="误差是怎么来的"><a href="#误差是怎么来的" class="headerlink" title="误差是怎么来的"></a>误差是怎么来的</h3><p><img src="https://ardupilot.org/copter/_images/atmospheric_effects.gif" alt="https:&#x2F;&#x2F;ardupilot.org&#x2F;copter&#x2F;_images&#x2F;atmospheric_effects.gif"></p><ul><li>电离层误差：0~30 米，由于电离层对GPS信号的干扰，在不同的电离层情况下，误差在一定范围内波动</li><li>对流层误差：0~30 米，由于对流层对GPS信号的干扰，在不同的对流层情况下，误差在一定范围内波动</li><li>卫星时钟误差：0~3 米，也叫 satellite clock bias</li><li>接收时钟误差：0~10 米，也叫 receiver clock bias</li><li>多路径和非视距信号（multipath&#x2F;NLOS）：0~100米</li></ul><p><a href="https://zhuanlan.zhihu.com/p/106778915">GNSS&#x2F;GPS RTK定位 （手机，无人车定位，无人驾驶，因子图优化）</a></p><p>GNSS 精度可参考 <a href="https://mavlink.io/en/messages/common.html#GPS_FIX_TYPE">mavlink 协议对于 GPS 的定义</a> （mavlink 是开源飞控定义的一种通信协议）</p><table><thead><tr><th>GNSS 解</th><th>精度</th><th>备注</th></tr></thead><tbody><tr><td>2D position</td><td>十几米</td><td></td></tr><tr><td>3D position</td><td>米级</td><td></td></tr><tr><td>DGPS&#x2F;SBAS aided 3D position</td><td>亚米级</td><td>Differential GPS, RTD</td></tr><tr><td>RTK float</td><td>分米级</td><td></td></tr><tr><td>RTK fixed</td><td>厘米级</td><td></td></tr></tbody></table><h3 id="伪距差分-Real-Time-Differential（RTD）"><a href="#伪距差分-Real-Time-Differential（RTD）" class="headerlink" title="伪距差分 Real Time Differential（RTD）"></a>伪距差分 Real Time Differential（RTD）</h3><p>差分全球定位系统（Differential GNSS （DGNSS），不过一般都叫 DGPS 或 差分 GNSS ）</p><p>差分 GNSS 有两种：一种是 RTD，一种是 RTK，在没有 RTK 以前，只有 RTD，所以一般差分 GNSS （DGPS）都是特指 RTD</p><p><img src="https://ardupilot.org/copter/_images/gps_differential_corrections.jpg" alt="https:&#x2F;&#x2F;ardupilot.org&#x2F;copter&#x2F;_images&#x2F;gps_differential_corrections.jpg"></p><p>RTD，伪距差分。基准站上，观测所有卫星，根据基准站已知坐标和各卫星的坐标，求出每颗卫星每一时刻到基准站的真实距离。再与测得的伪距比较，得出差分改正数，将其传输至用户接收机，提高定位精度。从而实现亚米级定位。</p><p><a href="https://zhuanlan.zhihu.com/p/150435858">一篇关于GPS定位写得最详实清晰的文章（上）</a></p><p><a href="https://ardupilot.org/copter/docs/common-gps-how-it-works.html">GPS - How it Works - Copter documentation</a></p><h3 id="载波差分-Real-Time-Kinematic（RTK）"><a href="#载波差分-Real-Time-Kinematic（RTK）" class="headerlink" title="载波差分 Real Time Kinematic（RTK）"></a>载波差分 Real Time Kinematic（RTK）</h3><p>RTK（Real Time Kinematic，实时动态）载波相位差分技术，是实时处理两个测量站载波相位观测量的差分方法，将基准站采集的载波相位发给用户接收机，进行求差解算坐标。<br>这是一种新的常用的卫星定位测量方法，以前的静态、快速静态、动态测量都需要事后进行解算才能获得厘米级的精度，而 RTK 是能够在野外实时得到厘米级定位精度的测量方法，它采用了载波相位动态实时差分方法，是 GNSS 应用的重大里程碑</p><p>RTK 需要自己架设一个基站，和 GNSS 连接，把差分数据发送给 GNSS</p><p><img src="/assets/img/innovator/rtk.png" alt="rtk"></p><p>RTK 在测绘领域用的很多，有一个大家都知道的场景就是考驾照科目三，考试车就是使用 RTK 定位的</p><p><a href="https://ictjournal.itri.org.tw/Content/Messagess/contents.aspx?&MmmID=654304432061644411&CatID=654313611231473607&MSID=1035144027215166537">即時動態定位系統之整合與驗證測試</a></p><h3 id="网络-Network-RTK-（NRTK）"><a href="#网络-Network-RTK-（NRTK）" class="headerlink" title="网络  Network RTK （NRTK）"></a>网络  Network RTK （NRTK）</h3><p>由于 RTK 要自己建也给 RTK 基站（base），可以自己架设一个 RTK base 然后给很多 GNSS 使用，在传输数据时可以使用互联网</p><p><img src="/assets/img/innovator/ntrip.svg" alt="ntrip"></p><p>使用的 draw.io 绘制的，源文件在这里，可以随意使用 <a href="/assets/img/innovator/ntrip.drawio">ntrip.drawio</a></p><p>双向数据通讯模式下，用户需要向数据处理中心发播自己的概率位置。数据处理中心根据用户的概率位置选择最优的参考站，计算相应的误差改正数或生成虚拟观察值，然后回发给用户。用户根据数据处理中心回发的误差改正数进行计算改正</p><p>自建网络 RTK 服务</p><p><a href="https://github.com/nunojpg/ntripcaster">nunojpg&#x2F;ntripcaster</a></p><p>网络服务商：千寻位置（阿里投资的）</p><p>也不是打广告，这个目前还找不到第二家同类的服务商（这是个创业方向，快！就差个程序员了）</p><p>千寻在全国建了非常多的基站，需要一个土豪金主在背后支撑</p><h3 id="GNSS-天线"><a href="#GNSS-天线" class="headerlink" title="GNSS 天线"></a>GNSS 天线</h3><p>为什么要写天线？事情起因是因为做天线的厂家天天打电话推销天线，就稍微了解一下</p><p>如果你要厘米级精度的话，天线位置还真的有讲究，如果你有一根十厘米长的天线，整根天线都可以接收信号，光误差就超了厘米，而且天线位置和飞机的几何中心也要考虑进去</p><p>天线有好多种，用到 GNSS 的基本上只有：陶瓷天线和四臂螺旋天线</p><p>比较廉价的飞机用这种陶瓷天线</p><p><img src="/assets/img/innovator/ceramic_antenna.jpg" alt="ceramic antenna"></p><p>四臂螺旋天线（quadrifilar helix antenna）</p><p>四臂螺旋天线具有心形方向图、良好的前后比及优异的宽波束圆极化特性，十分适合用作卫星定位系统的接收天线</p><p>这比较高，最终是一个圆柱型（无人机最上方凸起的圆柱就是这个天线）</p><p><img src="/assets/img/innovator/quadrifilar_helix_antenna.jpg" alt="quadrifilar helix antenna"></p><p>如果对天线有兴趣可以去看这个： <a href="https://www.zhihu.com/column/c_1298331884888625152">天线理论与设计</a></p><h3 id="外置磁罗盘"><a href="#外置磁罗盘" class="headerlink" title="外置磁罗盘"></a>外置磁罗盘</h3><p>电子磁罗盘（或者说三轴磁力计，上一篇文章讲过），接收地磁信号。容易被干扰</p><blockquote><p>罗盘（磁力计）应该安装在尽可能远离通过大电流的线缆的位置，因为大电流诱发的磁场会干扰罗盘的读数。</p></blockquote><p>如果没有办法磁罗盘无法远离动力线缆，那可以使用补偿算法，磁罗盘读数与油门杆位与&#x2F;或电池电流有强相关性</p><p><img src="/assets/img/innovator/corrupted_mag.png" alt="corrupted_mag"></p><p>还有一种办法是，把磁罗盘放到不会被干扰的地方。一般情况下有两个罗盘。飞控（内置罗盘）和 GNSS（外置罗盘）</p><p>GNSS 比较容易收到干扰，要信号好要放在最高点，所以就把 GNSS 和 磁罗盘 放在一起，远离其他模块，飞机上突出的小蘑菇头就是 GNSS 模块和磁罗盘</p><p> 最常用的模块是：Ublox 的 m8p 和 f9p</p><h3 id="双-RTK-yaw-方向判断"><a href="#双-RTK-yaw-方向判断" class="headerlink" title="双 RTK yaw 方向判断"></a>双 RTK <code>yaw</code> 方向判断</h3><p>有些时候 GNSS 不止有一个，两个 GNSS 模块的飞机</p><p>因为 RTK 可以实现厘米级精度，所以可以在飞机上安装两个，来得知 <code>yaw</code> 方向（位置差来判断）</p><blockquote><p>在手动控制中使用中 <code>yaw</code> 方向偏差是很危险的，没有经过训练的人，掌握对头飞行控制还是有难度的（面对飞机头，<code>pitch</code> 和 <code>roll</code> 是完全相反的）</p></blockquote><p>还有最丧心病狂的 DJI M600 pro 用了 三个 GNSS，三重冗余</p><p>综合之上的情况：干扰，双 GNSS，一般都 <strong>把 GNSS 放到机臂上</strong>（机臂里面是三项交流电，电流干扰补偿）</p><h3 id="如何-hack-GNSS"><a href="#如何-hack-GNSS" class="headerlink" title="如何 hack GNSS"></a>如何 hack GNSS</h3><p>GNSS 本质是接收卫星信号，可以使用 SDR 模拟一个 GNSS 信号，伪造任何位置 <a href="https://github.com/osqzss/gps-sdr-sim">https://github.com/osqzss/gps-sdr-sim</a></p><p>GNSS 的信号到达地面上已经很弱了。可以用 GNSS 的频段干扰器，干扰正常的 GNSS 信号。当然 GNSS 可能同时使用 GPS，BeiDou（北斗） ， Galileo 等，干扰一个是不够的，要全频段干扰</p><h2 id="传输模块"><a href="#传输模块" class="headerlink" title="传输模块"></a>传输模块</h2><p>图像和数据是分开的，图像传输是个新东西，本来就是可选模块，无人机航拍的兴起之后，好像变成了是个飞机都要有图传</p><p>数据传输模块大部分都是双向的，原本是为了查看飞机状态和使用地面站控制飞机</p><h3 id="数据传输模块"><a href="#数据传输模块" class="headerlink" title="数据传输模块"></a>数据传输模块</h3><p>也叫电台，数传，为了无人机超视距全自动飞行而设计，配合地面站来使用（先记住这个模块，地面站这个东西之后有文章会重点讲）</p><p>大家应该都知道串口，不用过多解释，<code>tx</code>, <code>rx</code>, <code>vcc</code>, <code>gnd</code> （是一个双向通信，可以拿来传输数据）。数据传输模块本质上其实就是一个走了特殊频段的串口透传模块</p><p>433MHz 频段的比较多，也有 2.4G 和其他频段的。可以通过这样一个模块和电脑连着一起，通过地面站程序来直接控制飞机</p><h3 id="2-4G-wifi-ESP8266"><a href="#2-4G-wifi-ESP8266" class="headerlink" title="2.4G wifi ESP8266"></a>2.4G wifi ESP8266</h3><p>有相关背景的，看到 ESP8266 这东西是干什么用的了。飞机开机释放热点，手机连接上，开 APP ，看到飞机摄像头图像，控制飞机的这种（距离很近）</p><p>那些在路边几百块买的玩具无人机用的就是这种方案，就和家用 WIFI 模块一样</p><h3 id="模拟图传"><a href="#模拟图传" class="headerlink" title="模拟图传"></a>模拟图传</h3><p>模拟图像传送是指对时间（包括空间）和幅度连续变化的模拟图像信号作信源和信道处理，通过模拟信道传输或通过模拟记录装置实现存储的过程。一般用扫描拾取图像信息和压缩频带等信源处理方法得到图像基带信号，再用预均衡、调制等信道处理方法形成图像通带信号。</p><p>模拟信号，原来的电视上用的那种信号，黄色的 AV 口的那种。也是传统的图传，优点就是延时低，距离远，一般是 5.8G 图传</p><p>穿越机比赛就是用的模拟图传 FPV（First-person view）这个延时超低，可以做到 50ms  以下（大疆的新数字图传可以和延时快和模拟图传一样了）</p><p>超远距离也是模拟图传，传输几十公里的那种</p><h3 id="数字图传"><a href="#数字图传" class="headerlink" title="数字图传"></a>数字图传</h3><p>图像数字传送是指数字化的图家信号经信源编码和信道编码，通过数字信道（电缆、微波、卫星和光纤等）传输，或通过数字存储、记录装置存储的过程。数字信号在传输中的最大特点是可以多次再生恢复而不降低质量。还具有易于处理、调度灵活、高质量、高可靠、维护方便等优于模拟传输的其他特点。</p><p>这个可以分成两种， HDMI 透传 和 网络透传</p><p>可以直接吧图像数字编码直接传回来。模拟图传，HDMI 透传 有个共同特点，数据是单向传输的</p><p>由于 HDMI 透传 的实现复杂，所以目前大部分都是使用 网络透传 来模拟 HDMI 透传</p><p>网络透传。不就是一个释放 WI-FI 一个连接上吗？当然家用 WI-FI 的距离太近，把无线通信部分改一下，不过这样就和普通的  WI-FI 不兼容了，这就是私有协议了。或者我把无线部分都换成自己其他的通信模块。这样可以使通信距离很远</p><p>AR9344 路由方案：就是 MIPS 芯片的那款路由器改装的</p><p>mircohard 的路由器方案：这个把通信部分全改了。标准的 openwrt 路由器接口</p><p>如果可以使用网络通信，那就顺便把串口透传也集成进来吧（串口转 Socket，这个甚至有 <a href="https://tools.ietf.org/html/rfc2217">RFC2217</a> 文档。ser2net 就是一个不错的实现）大部分数传和图传一体化的就是基于网络透传改的</p><h3 id="大疆的图传-（基于-SDR）"><a href="#大疆的图传-（基于-SDR）" class="headerlink" title="大疆的图传 （基于 SDR）"></a>大疆的图传 （基于 SDR）</h3><p>做无线通信要受制于供应商，要用其他频率，连硬件都很难获得。大疆的图传真的是比同行高太多了，是用 SDR 来做的，这样就可以自己定制通信了</p><h3 id="传输模块的天线"><a href="#传输模块的天线" class="headerlink" title="传输模块的天线"></a>传输模块的天线</h3><p>图传上能看到的天线有大概有三种：棒状天线，蘑菇头天线，平板天线</p><p><img src="/assets/img/innovator/antenna-1.jpg" alt="antenna 1"></p><p>棒状天线，一般家用路由器上就是这种，棒状天线上下是没有信号的</p><p><img src="/assets/img/innovator/whip_antenna.jpg" alt="whip antenna"></p><p>蘑菇头天线是整个蘑菇头都是有信号的（除了下面）。也称全向天线</p><p><img src="/assets/img/innovator/antenna-2.jpg" alt="antenna 2"></p><p>平板天线是一种定向天线，要平板正对着信号才好，这个距离也是最远的</p><p><img src="/assets/img/innovator/antenna-3.jpg" alt="antenna 3"></p><p>根据平板天线的特点，可以把平板天线放到三轴云台上，根据信号自动调整角度，定向追踪天线（这也是个创业方向，快！投资人打钱）</p><p>基于这个思路：可以使用电路来实现，如果可以控制对不同角度的发射信号，一样也可以信号追踪。</p><p>先弄很多天线，组成一个阵列。我们知道它发出的是电磁波，那么可以利用它波的特性，用相位不同的波相互干涉，来实现定向的信号增强。这个东西早就有了，叫 <strong>波束赋形</strong></p><p><img src="/assets/img/innovator/beamforming.png" alt="beamforming.png"></p><p>有些高端的路由器支持 MIMO 就是这个原理，还有就是 5G 技术也用了波束赋形</p><h2 id="相机和摄像头"><a href="#相机和摄像头" class="headerlink" title="相机和摄像头"></a>相机和摄像头</h2><p>相机这个实现有意思：先从最基本的情况开始</p><h3 id="快门硬件改装"><a href="#快门硬件改装" class="headerlink" title="快门硬件改装"></a>快门硬件改装</h3><p>先来看一种最基础的情况：</p><ol><li>遥控器可以输出 PWM 信号，把一个通道给一个电路模块，使占空比高是通电，占空比低时断电</li><li>这样就有了一个开关透传（将遥控器开关映射成飞机上开关的通断）</li><li>之后把相机的拆解，把快门按钮引出两个线出来（快门本质上就是开关触发），把这两个线接到开关透传上，这样就可以使用遥控器控制相机</li></ol><p>相机控制都是基于这一原理</p><p>不同的是 PWM 转开关可以让飞控来做，飞控里面支持的航线可以扩展出相机控制（开关通断），这样就有了飞航线自动拍照</p><p>有些相机可以支持 API 来控制拍照（Go Pro 就可以），可以软件来控制拍照</p><p>大部分都是使用运动相机改装的，Go Pro，山狗，小蚁运动相机</p><h3 id="增稳云台"><a href="#增稳云台" class="headerlink" title="增稳云台"></a>增稳云台</h3><p>稳定平台，对于任务设备来说太重要了，是用来给相机增稳的部分，不然拍出的效果可能很糟糕</p><p>一般是两轴的云台，和三轴云台。比如大疆有些机型为了简化控制使用两轴的云台（没有 <code>yaw</code> 方向的自由度）。云台还是比较容易弄到的，也有完全开源的实现</p><p>云台相机一体化，大疆精灵三开始做了一体化云台，开源飞机的一体化云台没有什么好的选择方案。也没什么供应商可以选的（在我写本文的时候，很少有可以选的）</p><h3 id="摄像头和协处理器"><a href="#摄像头和协处理器" class="headerlink" title="摄像头和协处理器"></a>摄像头和协处理器</h3><p>或许可以用摄像头自己做一个相机。不过如果要航拍的话，还是去使用专业的相机吧</p><p>一般可以用几个固定位置的摄像头去实现一些其他功能。看，大疆已经把六个面都装上摄像头了</p><p>一般都是使用上位机（比如 raspberry Pi ），来专门处理图像</p><h3 id="计算机视觉避障"><a href="#计算机视觉避障" class="headerlink" title="计算机视觉避障"></a>计算机视觉避障</h3><p>双目摄像头可以得知距离的（就像人有两只眼睛一样）。再配合一些算法，比如八叉树</p><p><img src="/assets/img/innovator/octree.jpg" alt="octree"></p><h3 id="视觉里程"><a href="#视觉里程" class="headerlink" title="视觉里程"></a>视觉里程</h3><p>通过摄像头和惯性导航来的知飞行距离，有些特殊的场景下 GNSS 可能没有信号（比如：室内飞行）</p><h3 id="视觉定点"><a href="#视觉定点" class="headerlink" title="视觉定点"></a>视觉定点</h3><p>都可以计算里程了，辅助定点当然是可行的</p><h2 id="辅助定高"><a href="#辅助定高" class="headerlink" title="辅助定高"></a>辅助定高</h2><p>GNSS 可以解出高度，不过是绝对高度，但重要的相对高度，就是和地面的高度（一般来说是以起飞点作为 0M）， GNSS 得到的绝对高度用处不大</p><p>要使用那种传感器来得知高度，还是要看使用场景</p><p><a href="https://kanzhaji.com/sitemaster/22523.html">消费级无人机-我的高度你不懂 - KANZHAJI 看炸机-无人机资讯站</a></p><h3 id="气压计"><a href="#气压计" class="headerlink" title="气压计"></a>气压计</h3><p>这个应该不用过多解释，通过气压来得知高度。<br>气压计在超低高度（低于 3 米）误差偏大，而随着高度升高，气压计精度逐步上升，大于 5 米后可以做到 +-30 厘米的精度，再辅以飞控内的加速度计做数据融合，大部分场景可以把高度漂移锁定在 +-20 厘米范围内</p><h3 id="超声波"><a href="#超声波" class="headerlink" title="超声波"></a>超声波</h3><p>消费级无人机的超声波器件作用距离有限，只能在低空有效工作（超声波传感器的距离都在 10M 以下）。于是各厂家采用的高度逻辑是，只在低于特定气压高度时，才切换并启用超声波反馈提供的高度</p><blockquote><p>而液体密度还对超声波精度有较大影响，尤其水面对超声波的吸收让正常反馈大量衰减，飞机误以为正在上升，从而降低油门输出。然后就飞机就下水了</p></blockquote><h3 id="激光雷达"><a href="#激光雷达" class="headerlink" title="激光雷达"></a>激光雷达</h3><p>如果使用激光雷达判读高度的话，特指固定在飞机某个下面的激光雷达，替代超声波的那种激光雷达（价钱也高出了好多）</p><p>虽然可以用激光雷达来避障，这里特指单条线的那种</p><h3 id="毫米波雷达"><a href="#毫米波雷达" class="headerlink" title="毫米波雷达"></a>毫米波雷达</h3><p>毫米波穿透力比激光雷达强，距离比激光雷达近，在一些特殊场景下。比如农业植保，使用毫米波雷达判断离地面高度</p><h3 id="突破大疆飞机限高"><a href="#突破大疆飞机限高" class="headerlink" title="突破大疆飞机限高"></a>突破大疆飞机限高</h3><p>我们可以得知高度信息是通过 GNSS，和气压计，超声波等得出的，飞的比较高之后就只有 GNSS 和 气压计 是准确的，气压计作为主要高度信息</p><p>改装气压计，把气压计密封一下，就可以突破限制了，但还可以通过 GNSS 来判断高度。替换 GNSS 模块，因为这个模块的特性，上方不会有遮挡，可以把 GNSS 换成一个高度有问题的模块进去</p><p>当然从软件上下手更容易些，这些限制都是写在软件里的，反汇编，破解通信协议。总有一款适合你</p><h2 id="空中交通避障"><a href="#空中交通避障" class="headerlink" title="空中交通避障"></a>空中交通避障</h2><h3 id="自动相关监视广播-Automatic-Dependent-Surveillance–Broadcast-（ADS–B）"><a href="#自动相关监视广播-Automatic-Dependent-Surveillance–Broadcast-（ADS–B）" class="headerlink" title="自动相关监视广播 Automatic Dependent Surveillance–Broadcast （ADS–B）"></a>自动相关监视广播 Automatic Dependent Surveillance–Broadcast （ADS–B）</h3><p>是一种飞机监视技术，飞机通过卫星导航系统确定其位置，并进行定期广播，使其可被追踪。空中交通管制地面站可以接收这些信息并作为二次雷达的一个替代品，从而不需要从地面发送问询信号。其他飞机也可接收这些信息以提供姿态感知和进行自主规避。</p><p><img src="/assets/img/innovator/ADS-B.png" alt="ADS-B"></p><h3 id="SDR-和-ADS-B-的妙用：探测家附近有多少飞机"><a href="#SDR-和-ADS-B-的妙用：探测家附近有多少飞机" class="headerlink" title="SDR 和 ADS-B 的妙用：探测家附近有多少飞机"></a>SDR 和 ADS-B 的妙用：探测家附近有多少飞机</h3><p>目前民航也是用的 ADS-B 。这个信息是公开，无加密的。所以我可以去接受这个频段的信息。对，用 SDR 去接收消息，然后再地图上绘制出来</p><p><img src="/assets/img/innovator/skyaware.jpg" alt="skyaware"></p><p><a href="https://www.rtl-sdr.com/adsb-aircraft-radar-with-rtl-sdr/">RTL-SDR Tutorial: Cheap ADS-B Aircraft RADAR</a></p><h3 id="UAS-Unmanned-Aircraft-Systems-Traffic-Management-UTM"><a href="#UAS-Unmanned-Aircraft-Systems-Traffic-Management-UTM" class="headerlink" title="UAS  (Unmanned Aircraft Systems) Traffic Management (UTM)"></a>UAS  (Unmanned Aircraft Systems) Traffic Management (UTM)</h3><p>UTM 是 美国国家航空航天局（NASA）的无人驾驶飞机系统（UAS）的避障系统。（无人机界的 ADS-B）</p><p>目前这还是个很新的东西（目前还是概念），也许不久的将来，这个就是每个无人机上都有的东西</p><p><img src="/assets/img/innovator/utm-airspace7.jpg" alt="utm-airspace7"></p><h2 id="Power-Monitors"><a href="#Power-Monitors" class="headerlink" title="Power Monitors"></a>Power Monitors</h2><p>当然，电源监控是非常重要的，判断电压是不准的，工作时电源压降会很大。无人机上普遍使用电流计，更精确一些</p><h2 id="系留多旋翼"><a href="#系留多旋翼" class="headerlink" title="系留多旋翼"></a>系留多旋翼</h2><p>不要电池了，把电池换成电线，试想一下，多旋翼拖着一根电线飞到天上。在天空中悬停，有一些特殊场景会需要这种东西</p><h2 id="特殊的挂载"><a href="#特殊的挂载" class="headerlink" title="特殊的挂载"></a>特殊的挂载</h2><p>发挥想象力，可以挂载的东西有很多。比如特殊用途的挂载：高光谱相机，红外，喊话器，空气质量传感器。。。。还可以送外卖！</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;有很多零件对于多旋翼无人机来说，是可选的&lt;/p&gt;
&lt;h2 id=&quot;科普-SDR-原理&quot;&gt;&lt;a href=&quot;#科普-SDR-原理&quot; class=&quot;headerlink&quot; title=&quot;科普 SDR 原理&quot;&gt;&lt;/a&gt;科普 SDR 原理&lt;/h2&gt;&lt;p&gt;软件无线电，即 Softwa</summary>
      
    
    
    
    <category term="self" scheme="https://a-wing.top/categories/self/"/>
    
    
    <category term="self" scheme="https://a-wing.top/tags/self/"/>
    
    <category term="log" scheme="https://a-wing.top/tags/log/"/>
    
    <category term="uav" scheme="https://a-wing.top/tags/uav/"/>
    
  </entry>
  
  <entry>
    <title>创新者异闻录（3/7）——多旋翼无人机的组成基础</title>
    <link href="https://a-wing.top/self/2021/02/11/innovator/2021-02-12-innovator-3"/>
    <id>https://a-wing.top/self/2021/02/11/innovator/2021-02-12-innovator-3</id>
    <published>2021-02-11T20:00:03.000Z</published>
    <updated>2021-02-11T20:00:03.000Z</updated>
    
    <content type="html"><![CDATA[<p>我假定大家都上过高中，让这篇文章让有高中基础就可以看懂，看完这篇文章，就可以自己造一架属于自己无人机了，本文稍微有点硬核，这篇大概是整个系列里面最难的一篇</p><p>先科普一下 <a href="https://zh.wikipedia.org/wiki/%E5%9C%B0%E9%9D%A2%E6%95%88%E5%BA%94">地面效应</a></p><blockquote><p>当运动的飞行器距离地面（或水面）很近时，整个飞行器体的上下压力差增大，升力会陡然增加</p></blockquote><p>还有就是地面效应时产生翼尖涡流导致的造成航空器的不稳定（起降时 <code>pitch</code>，<code>roll</code> 就很危险 ）</p><p>更通俗的说就是飞机在离地面很近时 （对于 F450 大概 0~1M ，一般是机翼与地面距离低于两倍弦长）时会出现特殊的气流，和在空中不同，这个在飞机起飞和降落是会有影响，有些飞机会提供一键起飞和一键降落的功能，帮助新手完成起降</p><p>无人机的地面效应也会影响气压计，进而影响对高度的判断，最常见的现象就是降落后桨没有停止转动，飞机会跳几下再停止转动。解决方法就是在气压计传感器增加海绵，减小突变气流的干扰；增加超声波&#x2F;激光传感器综合判断高度</p><h2 id="多旋翼概况"><a href="#多旋翼概况" class="headerlink" title="多旋翼概况"></a>多旋翼概况</h2><p>多旋翼里也是分机型的，不过原理是一样的。</p><ul><li>X4（对称的4轴多旋翼（四个完全一样），典型的代表：大疆的精灵系列）</li><li>X6（和4轴一样，多了两个轴）</li><li>H4（两个两个一样，一般是前后）</li><li>Y3 这个机型也比较特殊（三个螺旋桨）</li><li>Y6 螺旋桨上下都有，方向相反，机架和 Y3 类似</li><li>V 型 也叫 BiCopter 型，电机角度会可以侧倾</li><li>等等，还有其他机型，参考各飞控支持的机型</li></ul><p>本篇使用就以大疆风火轮 F450 为例（风火轮就是哪吒的风火轮，因为还有哪吒飞控 naza），DIY 一个多旋翼无人机的最小系统</p><p>F450 是 X4 型飞机（450 指的是轴距 450mm）</p><p>其他的多旋翼都可以看成是四旋翼的变种</p><p>这是一个整体的架构图：</p><p><img src="/assets/img/innovator/autopilot.svg" alt="autopilot"></p><p>使用的 draw.io 绘制的，源文件在这里，可以随意使用 <a href="/assets/img/innovator/autopilot.drawio">autopilot.drawio</a></p><h2 id="机架"><a href="#机架" class="headerlink" title="机架"></a>机架</h2><p>就是无人机的那个骨架，一般是由塑料或碳纤维制成（F450 是塑料的）</p><p>下图：大疆 F450 （这个机架是 X4 型）</p><p><img src="/assets/img/innovator/f450.jpg" alt="f450"></p><p>轴距是多旋翼无人机中非常重要的一个参数，通常被定义为电机轴心轴围成的外接圆周的直径，单位毫米mm，对称多旋翼无人机中轴距即为对角线上的两个电机轴心的距离，轴距的大小确定了螺旋桨的尺寸上限</p><h3 id="机臂上反角"><a href="#机臂上反角" class="headerlink" title="机臂上反角"></a>机臂上反角</h3><p><img src="/assets/img/innovator/multicopter_dihedral.webp" alt="multicopter dihedral"></p><p>这个可不是摔弯的，是故意设计成这样的</p><p>这个是来自<a href="https://www.dji.com/cn/support/product/phantom-4">大疆官方的解释</a>：</p><blockquote><p>上反角是指机臂相对于机架水平面的夹角，上反角的目的是为了增强飞机在近地面的悬停性能。<br>由于在近地面悬停时的气流会翻到螺旋桨气动平面上方引起气流扰动导致飞机晃动，此时增加上反角保护飞机上方气流稳定可提升悬停稳定性能。<br>一般来说，上反角不宜过大，8° 左右即可。</p></blockquote><p>固定翼也是有的类似设计的：</p><p><img src="/assets/img/innovator/wing_dihedral.png" alt="wing dihedral"></p><h3 id="电机侧倾角"><a href="#电机侧倾角" class="headerlink" title="电机侧倾角"></a>电机侧倾角</h3><p>电机侧倾角是指沿着机臂方向看向电机，电机的侧向旋转角度。侧倾角的目的是为了增加航向角的机动性能。一般来说侧倾角不宜过大，3° 左右即可</p><h2 id="螺旋桨"><a href="#螺旋桨" class="headerlink" title="螺旋桨"></a>螺旋桨</h2><p>螺旋桨是无人机的动力系统之一，材质一般为、塑料、碳纤维等，根据飞行器的参数要求选择。<br>螺旋桨的规格型号 螺旋桨规格，一般由 4 位数字表示，前两位数表示直径，后两位表示螺距。<br>以 1060 桨为例，10 表示桨的直径是 10 英寸，60 表示桨角。（螺距，6.0 英寸，也就是 152.4mm)</p><p>按照旋转方向可以分为正桨和反桨（一般正反对称且数量相等）</p><p>桨属于消耗品，可以折叠的和不可折叠的。按安装方式分，快拆桨和普通桨，有两个桨页的话三个桨页（三个桨页的一般用于穿越机，无人机竞速比赛时用的）</p><p>注：F450 机架可以使用 9450 自锁桨，和大疆精灵系列同型号的桨</p><h3 id="高原桨"><a href="#高原桨" class="headerlink" title="高原桨"></a>高原桨</h3><p>高海拔需要相应的高原桨，在于海拔越高，空气质量越稀薄，使得桨在相同转速下，所能提供的拉力变小了，从而无法使飞机达到较好机动性能</p><h3 id="静音桨"><a href="#静音桨" class="headerlink" title="静音桨"></a>静音桨</h3><ol><li>改变桨型。通过选取一个更优的桨型，降低无人机飞行过程中电机转速，使得无人机的桨叶切割空气的速度相对而言没那么快，就能降低一部分的噪声。</li><li>桨尖特殊的结构设计。桨叶在旋转过程中，桨尖转动的线速度最大，基于这种原理，静音桨在设计的时候，桨尖稍往后掠，且变得更窄一点的结构方式能减小桨叶切割空气的声音，从而实现“静音”的效果</li></ol><p>DJI Mavic Pro 静音桨</p><p><img src="/assets/img/innovator/silent_propeller.jpeg" alt="silent propeller"></p><h2 id="无刷电机"><a href="#无刷电机" class="headerlink" title="无刷电机"></a>无刷电机</h2><h3 id="有刷电机工作原理"><a href="#有刷电机工作原理" class="headerlink" title="有刷电机工作原理"></a>有刷电机工作原理</h3><p>有刷电机是大家最早接触的一类电机，中学时物理课堂上介绍电动机也是以它为模型来展示的。有刷电机的主要结构就是定子+转子+电刷，通过旋转磁场获得转动力矩，从而输出动能。电刷与换向器不断接触摩擦，在转动中起到导电和换相作用</p><p><img src="/assets/img/innovator/brushed_motor.gif" alt="brushed motor"></p><h3 id="无刷电机工作原理"><a href="#无刷电机工作原理" class="headerlink" title="无刷电机工作原理"></a>无刷电机工作原理</h3><p>无刷电机中，换相的工作交由控制器中的控制电路（一般为霍尔传感器+控制器，更先进的技术是磁编码器）来完成。</p><p>无刷电机使用的是三相电，需要靠电子调速器把直流电转换成三项电</p><p><img src="/assets/img/innovator/brushless_motor.gif" alt="brushless motor"></p><h3 id="为什么要用无刷电机"><a href="#为什么要用无刷电机" class="headerlink" title="为什么要用无刷电机"></a>为什么要用无刷电机</h3><ol><li>无刷电机去除了电刷，最直接的变化就是没有了有刷电机运转时产生的电火花，这样就极大减少了电火花对遥控无线电设备的干扰</li><li>运转时摩擦力大大减小，运行顺畅，噪音会低许多，这个优点对于模型运行稳定性是一个巨大的支持。</li><li>少了电刷，无刷电机的磨损主要是在轴承上了，从机械角度看，无刷电机几乎是一种免维护的电动机了，必要的时候，只需做一些除尘维护即可</li><li>有刷电机调速过程是调整马达供电电源电压的高低。调整后的电压电流通过整流子及电刷地转换，改变电极产生的磁场强弱，达到改变转速的目的。这一过程被称之为变压调速</li><li>无刷电机调速过程是马达的供电电源的电压不变，改变电调的控制信号，通过微处理器再改变大功率 MOS 管的开关速率，来实现转速的改变。这一过程被称之为变频调速</li><li>航模的功率比较大，航模动力电池实现大电流比实现高压容易，而大电流对触点要求太高，有刷的话很难做到那么小巧</li></ol><p><strong>这些其实都不能成为要用无刷电机的原因</strong>，因为无刷电机组成的动力系统比有刷电机组成的动力系统成本和复杂度都高出好多</p><p>对于这个问题，我找到好多资料并没有找出答案。为此，我只能跑去求各路神仙（找了好多人，大部分人的看法是）：</p><blockquote><p>功率与重量比值高</p></blockquote><h3 id="重要参数：KV"><a href="#重要参数：KV" class="headerlink" title="重要参数：KV"></a>重要参数：KV</h3><p>以 朗宇 A2212 &#x2F; KV 980 为例</p><p>有些文章会给你讲：2212 是定子的外径和高度，A系列是 “内转子槽无刷电机“</p><p>并没有哪个标准里这样定义过（最可笑的是这些文章互相抄袭，早已丢失了上文）</p><p><code>A2212</code> 这个厂家的命名，不具备任何参考价值，因为不同的厂家命名规则不一样（朗宇是 “定子的外径和厚度”，A 是自己定义的系列名）</p><p>KV 的概念是指无刷电机工作电压每提升 1V 无刷电机所增加的转速 <code>r/min</code></p><p>无刷电机 KV 值低的有几百到 2 千 3 千的，高的到 5 千 6 千的（电机越大，扭矩越大，KV 越小）</p><p>电机 KV 值：<code>电机的转速（空载）= KV 值 * 电压</code>；例如 KV980 的电机在 10V 电压下它的转速（空载）就是 9800 RPM （<code>r/min</code> or <code>转/分钟</code>）</p><h2 id="电子调速器-Electronic-Speed-Control（ESC）"><a href="#电子调速器-Electronic-Speed-Control（ESC）" class="headerlink" title="电子调速器 Electronic Speed Control（ESC）"></a>电子调速器 Electronic Speed Control（ESC）</h2><p>这里面特指无刷电机电子调速器</p><p>输入 PWM 信号，和电源。输出三相交流电（三根火线的那种）相位相差 120°</p><p><img src="/assets/img/innovator/esc.svg" alt="esc"></p><p>使用的 draw.io 源文件在这里，可以随意使用 <a href="/assets/img/innovator/esc.drawio">esc.drawio</a></p><p>还记得上一篇讲过的多旋翼的电机转向吗？电机转向是有要求的</p><p>三相交流电的连接顺序可以改变电机的转动方向（这个是实际测试出来的，所以无桨调试是必须的）</p><p>也不一定都是 PWM 输入，现在有个趋势是用 CAN 总线输入（大疆早就用 CAN 了，开源的是 UAVCAN 标准，很多组件都在逐渐采用）</p><h3 id="FOC-（Field-Oriented-Control）"><a href="#FOC-（Field-Oriented-Control）" class="headerlink" title="FOC （Field-Oriented Control）"></a>FOC （Field-Oriented Control）</h3><p>无刷电机其实可以分为 <strong>无刷直流电机（BLDC，我们航模一般都是用这种）</strong> 和 <strong>永磁同步电机（PMSM）</strong></p><p>BLDC 接收的是方波，在低速下无法控制，PMSM 接收的是正弦波，噪音，力矩都有更好的表现，在低速下也可以控制，还可以反转</p><p>FOC 电调就是直接输出正弦波。需要配合 <strong>永磁同步电机（PMSM）</strong></p><p>FOC 的原理和实现，请看这篇 <a href="https://zhuanlan.zhihu.com/p/147659820">【自制FOC驱动器】深入浅出讲解FOC算法与SVPWM技术</a></p><h3 id="免电池电路-Battery-Eliminator-Circuitry（BEC）"><a href="#免电池电路-Battery-Eliminator-Circuitry（BEC）" class="headerlink" title="免电池电路 Battery Eliminator Circuitry（BEC）"></a>免电池电路 Battery Eliminator Circuitry（BEC）</h3><p>模型运动早期，遥控模型还没有出现电动模型（没有主控，使用接收机和遥控器混控来控制）的时代，只有接收机需要单独的供电，直接让电调实现这个功能可以更精简，这就是 BEC（免电池电路）的由来</p><p>对于无人机（电调并不是只有无人机在用），可以直接忽略掉这个参数</p><blockquote><p>BEC 大多采用线性稳压方式，线性稳压方式的优点是线路简单，体积小，只要一个稳压管就可以了，但缺点是转换效率不高，稳压的时候能量损耗大（线性稳压效率一般只有 65%-70% ），所以在工作过程中稳压管会很烫（电调发烫的主要热量来自这个稳压管，真正控制电机的 MOS 开关管其实发热量不大的）由于其效率不高，自然输出电流不可能很大，一般最大也就 1A 左右</p></blockquote><h2 id="动力系统的搭配"><a href="#动力系统的搭配" class="headerlink" title="动力系统的搭配"></a>动力系统的搭配</h2><p>一般有两种类型可以选的，无刷电机的动力系统，空心杯电机动力系统，大部分的多旋翼都是无刷电机（大疆全部都是用无刷电机的），空心杯电机一般用在小的玩具机上（价钱便宜，但结构限制很难有大号的空心杯电机）</p><p>动力系统是由 桨，电机和电调组成，但该如何知道轴距，电机型号和桨之间该如何搭配？</p><ol><li>直接找卖家，买成套动力系统套装</li><li>自己计算</li></ol><p>不过这年头造飞机不需要懂空气动力学，直接套公式算就行，甚至不需要自己算，直接通过网页版工具直接算</p><p>这个是比较专业的在线工具： <a href="https://ecalc.ch/xcoptercalc.php">ecalc.ch&#x2F;xcoptercalc</a></p><p><img src="/assets/img/innovator/ecalc_xcoptercalc.png" alt="ecalc.ch&#x2F;xcoptercalc"></p><p>北航做的在线计算工具（这个只能算多旋翼）：<a href="https://flyeval.com/">https://flyeval.com/</a></p><p>魔豆窝航模论坛做的多轴飞行器计算工具 <a href="http://www.modouwo.com/AMoDouWo/QC.html">http://www.modouwo.com/AMoDouWo/QC.html</a></p><h2 id="飞行控制器"><a href="#飞行控制器" class="headerlink" title="飞行控制器"></a>飞行控制器</h2><p>飞行控制器简称 “飞控”，可以说是多旋翼最核心的部分。没有飞控多旋翼可以飞吗？当然可以了，对遥控器通道输出设置混控，人肉控制自稳，飞控的 Acro 模式就是这样</p><h3 id="开源飞控和商业飞控"><a href="#开源飞控和商业飞控" class="headerlink" title="开源飞控和商业飞控"></a>开源飞控和商业飞控</h3><p>飞控的核心是软件，当然有开放源代码的和不开放源代码的</p><p>风火轮 F450 大疆官方的指定的是 naza（哪吒）飞控（当然，大疆早停产了）</p><p>开源飞控和 Linux 基金会下面的 Dronecode。你没有听错，就是 Linux 内核的 Linux。Dronecode 的基金会属于 Linux 基金会</p><p>飞控没有 linux 内核，飞控里使用的是实时操作系统 RTOS。ardupliot （ChibiOS），PX4 （NuttX）</p><p>讲个笑话：ROS （Robot Operating System) 不是 RTOS（Real-Time Operating System）<br>是一个机器人操作系统，可以跑在 Linux 上，但不是 RTOS，ROS 不是实时，靠下位机来保证实时的</p><p>dronecode 下的两大飞控：</p><p><a href="https://px4.io/">PX4</a> 和 <a href="https://ardupilot.org/">ardupliot</a><br>（ardupliot 后来离开了 dronecode，大概是因为 GPL 协议，赞助商们不喜欢这个，就一起投票给 T 了）</p><blockquote><p>用关系数据库做个比喻：PX4 就好像 PostgreSQL，ardupliot 就像 MySQL</p></blockquote><p>ardupliot 这个名字来自于 arduino pliot （最早是用 arduino 实现的）<br>代码就是一坨屎山（对，这句话就是我说的，配套生态代码也很糟糕，只能说 “能用” ）这个大概是历史遗留，毕竟迭代了好多年</p><p>PX4 是由苏黎世理工的计算机视觉与几何实验室的一个软硬件项目 PIXHAWK 演变而来。源码是经典。（反正我看过，传言）。。。PX4 的 4 是这个项目被重写了四次，之后开源出来的</p><p>除此之外还有穿越机的飞控比如 openpilot 的 CC3D，和适合读源码学习的 MWC （MultiWii Copter）飞控（这个代码极为精简）</p><h3 id="算法"><a href="#算法" class="headerlink" title="算法"></a>算法</h3><p>我要事先声明一下，飞控算法部分是我自己的理解，因为我没有自己做过飞控，我只用过现成的飞控</p><p>这是一个典型工业自动控制系统架构</p><p><img src="/assets/img/innovator/automatic_control_architecture.jpg" alt="automatic control architecture"></p><p>传感器拿到的数据，经过姿态解算（四元数组）给卡尔曼滤波，过滤误差值，之后给 PID 控制器，输出控制信号</p><p>姿态解算 四元数，欧拉角</p><p><img src="/assets/img/innovator/controller_diagram.svg" alt="controller diagram"></p><p>使用的 draw.io 绘制的，源文件在这里，可以随意使用 <a href="/assets/img/innovator/controller_diagram.drawio">controller_diagram.drawio</a></p><p><a href="http://www.crazepony.com/book/wiki/software-algorithm.html">可以去看这篇文章了解一下如何进行软件姿态解算了</a></p><blockquote><p>四轴的姿态解算无疑是最繁琐的步骤没有之一，但是自从 MPU6050 出现了硬件 DMP 的时候，大妈都能完成姿态解算了！</p></blockquote><p>有些传感器支持硬件解算，不需要自己写计算步骤</p><p><a href="http://www.crazepony.com/book/wiki/hardware-algorithm.html">Crazepony开源四轴飞行器</a></p><p>卡尔曼滤波 Kalman Filter</p><p>一般来说，拿到的数据不够精确（传感器会有误差）。还有多个传感器数据需要融合，使用卡尔曼滤波去除噪声和数据融合</p><p>这张图来自于维基百科：黑色的是真实值; 绿色的是滤波后的结果; 红色的是传感器采集的值</p><p><img src="https://upload.wikimedia.org/wikipedia/commons/d/da/Kalman.png" alt="https:&#x2F;&#x2F;upload.wikimedia.org&#x2F;wikipedia&#x2F;commons&#x2F;d&#x2F;da&#x2F;Kalman.png"></p><p>黑色的是真实值; 绿色的是滤波后的结果; 红色的是传感器采集的值</p><p>PID 算法，下面这张图来自于维基百科</p><p><img src="https://upload.wikimedia.org/wikipedia/commons/4/41/PID_loop.svg" alt="https:&#x2F;&#x2F;upload.wikimedia.org&#x2F;wikipedia&#x2F;commons&#x2F;4&#x2F;41&#x2F;PID_loop.svg"></p><p>PID 算法，这张图来自于维基百科</p><p>PID，就是“比例（proportional）、积分（integral）、微分（derivative）”，是一种很常见的控制算法</p><h3 id="控制模式"><a href="#控制模式" class="headerlink" title="控制模式"></a>控制模式</h3><p>几乎所有的飞机的控制模式都是差不多的（可能名字不同，功能类似）</p><ul><li>Acro 特技模式：没有自稳，飞控只做了混控（手动在空中翻滚用的就是这种模式，大疆的产品直接去掉了这种模式）</li><li>Stabilized 稳定模式：相当于特技模式加上了自稳功能，</li><li>Position 位置模式：配合支持双回中的遥控器，可以像大疆的姿态模式或 GPS 模式的操作一样</li><li>Mission 任务模式：或者说航线模式，事先定义好飞行路径，这个模式按照自动飞行</li></ul><p>Stabilized 和 Position 的区别在于油门的控制：</p><p>Stabilized 是将 <code>throttle</code> 的值直接映射个飞行器，是一个绝对值的控制（<code>throttle</code>中点不是平衡点）</p><p>Position 有中间位置，<code>throttle</code> 中点是定高，是一个相对值的控制（<code>throttle</code>中点就是平衡点）</p><p>Stabilized 操作比较复杂，一般要练习好久才能掌握，比较古老的多旋翼有使用这种模式控制的（有些便宜的玩具无人机也只有这种控制模式）</p><p>环绕飞行，一键起降本质上就是任务模式的扩展，生成一个任务让飞机飞执行</p><h3 id="单片机-Micro-Controller-Unit（MCU）"><a href="#单片机-Micro-Controller-Unit（MCU）" class="headerlink" title="单片机 Micro Controller Unit（MCU）"></a>单片机 Micro Controller Unit（MCU）</h3><p>就以 STM32F103 为例吧，反正常见的单片机都可以，AVR，msp430 之类的都可以。ardupliot 项目最早是用 arduino uno 实现的</p><p>比如有四个轴，MCU 输出四路 PWM 分别给四个电子调速器</p><p>再加上遥控器的控制，至少要五个通道（加一个控制模式切换）</p><h3 id="惯性测量单元-Inertial-Measurement-Unit（IMU）"><a href="#惯性测量单元-Inertial-Measurement-Unit（IMU）" class="headerlink" title="惯性测量单元 Inertial Measurement Unit（IMU）"></a>惯性测量单元 Inertial Measurement Unit（IMU）</h3><p>惯性测量单元是测量物体三轴姿态角（或角速率）以及加速度的装置，最好安装在被测物体的重心上，也可以把偏移量也输入进去</p><p>就是一个三轴加速度传感器和三轴陀螺仪（角度）放在一起，经典型号当然是 MPU6050 （大疆飞机里也用过这款）</p><p><img src="https://upload.wikimedia.org/wikipedia/commons/d/d5/Gyroscope_operation.gif" alt="https:&#x2F;&#x2F;upload.wikimedia.org&#x2F;wikipedia&#x2F;commons&#x2F;d&#x2F;d5&#x2F;Gyroscope_operation.gif"></p><h3 id="三轴磁力计"><a href="#三轴磁力计" class="headerlink" title="三轴磁力计"></a>三轴磁力计</h3><p>三轴磁力计是采用三个互相垂直的磁阻传感器，每个轴向上的传感器检测在该方向上的地磁场强度。</p><p>通过惯性测量单元已经可以因解出空间中的位置，为什么还有用这个？</p><p>三轴加速度和三轴陀螺仪理论是完全可行的，但实际上会有两个问题：<strong>累积误差</strong>和<strong>零点漂移</strong></p><p>任何传感器都是有误差的，根据陀螺仪原理，测得是变化量，每次都会有误差，长期运行会有<strong>累积误差</strong></p><p>**零点漂移 （zero drift）**是由于材料特性：</p><blockquote><p>晶体管是温度的敏感器件，使静态工作点发生变化，如电源电压不稳，这样就形成了零点漂移、环境温度变化等、ICBO 都将发生变化</p></blockquote><p>随着多旋翼工作，温度的变化会产生，导致测不准</p><p><img src="/assets/img/innovator/types_of_drift.jpg" alt="types of drift"></p><p>除了零点漂移还有 <a href="http://mvs-mm.blogspot.com/2013/01/reproducibility-drift-and-resolution.html">其他漂移</a></p><p>三轴磁力计就是为了修正这个误差</p><p>经典型号：HMC5883</p><p>也有把三轴加速度陀螺仪和三轴磁力计放在一个模块里面，连四元数和欧拉角直接硬件算结果的模块</p><h2 id="遥控器-Radio-Control-RC"><a href="#遥控器-Radio-Control-RC" class="headerlink" title="遥控器 Radio Control (RC)"></a>遥控器 Radio Control (RC)</h2><p>这个接收端和地面发射器一般是配套的，通信频段有 2.4G 的也有其他频段的</p><p>接收端有几路 PWM 输出 MCU 。至少要有四路 PWM 。（<code>throttle</code>，<code>pitch</code>，<code>roll</code>，<code>yaw</code>）</p><p>一般都用五路 PWM 以上的，再加一路控制飞行模式。剩下的可以控制云台</p><p>遥控器一般可以输出三种信号</p><ul><li>PWM （Pulse-Width Modulation）就是占空比，一路信号一根线</li><li>PPM （Pulse-Phase Modulation）脉冲相位调制，可以用一根线输出多路 PWM 发送的信息量</li><li>S.Bus 这个是二进制的通信协议，靠 MCU 编解码</li></ul><p>美国手 和 日本手</p><p>美国手，在航空模型刚刚兴起之时，只有一个通道可供控制，于是便用来控制旋转方向，上升下降全凭电机开关与否。由于只有一个控制摇杆，自然多数人都选择用右手控制。后来，随着技术的发展，加入了油门也就是上升下降的控制，这时便将其分配到了左手的位置。由于这种模式形成最早</p><p>国内有许多有经验的飞手都是日本手，这是由于早期的遥控器多数是日本进口，因此一旦养成了习惯便很难再改回来了。</p><p>中国手，也有人称作 “反美国手”，因为其与 “美国手” 完全相反，更像是游戏手柄的延伸</p><p>目前国内多旋翼的话基本上都是默认 “美国手”</p><p><img src="/assets/img/innovator/radio_control.svg" alt="radio control"></p><p>使用的 draw.io 源文件在这里，可以随意使用 <a href="/assets/img/innovator/radio_control.drawio">radio_control.drawio</a></p><p>还有一种说法是：日本手适合固定翼，美国手适合直升机</p><p>遥控器推荐两个品牌吧：</p><ul><li>futaba 日本的，这个领域的最好产品</li><li>frsky 这是一家中国的公司，这个支持 openTX 开源遥控器固件，产品好的和进口的一样</li></ul><h2 id="航模电池"><a href="#航模电池" class="headerlink" title="航模电池"></a>航模电池</h2><p>航模电池全称聚合物锂电池，用在飞机上的电池要极高的放电倍率</p><p>锂电的标准电压是 3.7V ，这个是化学性质决定的。要更高的电压要串联起来</p><p>将各电芯（Cell）放在一起，组成一块电池。一般有几块电芯就称之为几 <code>s</code> 的电池，<code>s</code> 指的是电芯数</p><p>容量单位一般都用 毫安时（mAh）。1000mAh 就是这个电池能保持 1000 毫安（1安培）放电一小时</p><p>对于飞机上使用的有个非常重要的参数：C （放电倍率）。如果是 1000mAh 10C，这个电池可以最大以 10A 的电流放电</p><p>无人机一般只能飞十几分钟，至少要 5C 以上的电池。（一般都用 10C 以上甚至更高，要考虑突然加速的瞬时功率）</p><h2 id="平衡充电器"><a href="#平衡充电器" class="headerlink" title="平衡充电器"></a>平衡充电器</h2><p>由于电池是多个电芯独立，充电时要分开控制电压和电流</p><p>锂电池充电器分成两种：串充和并充</p><p>锂电池有两组线：平衡头（电芯数+1 的排线），电源（两根粗线）</p><h3 id="并充"><a href="#并充" class="headerlink" title="并充"></a>并充</h3><p>独立供电的充电器，一般体积重量都比较巨大，对每个电芯都进行独立控制</p><p>由于每一路都可以独立控制，只接一组平衡头就可以了</p><h3 id="串充-（被动均衡）"><a href="#串充-（被动均衡）" class="headerlink" title="串充 （被动均衡）"></a>串充 （被动均衡）</h3><p>这个体积小，便宜。充电时要把平衡头和电源都接上，才能充电</p><p>这个是使用电源那个口进行充电，平衡头那个口用于平衡，流过全部电芯的电流是一样的。</p><p>先一起充电后放电来平衡各电芯（先充满的电池会边充边放）</p><p>这两种充电器各有优缺点，自己用其他电源一个一个电芯的充电也是可以的（抢救电芯一般都这么干）</p><p>还有使用不同的充电策略对电池寿命也是有影响的（快充会影响电池寿命）</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;我假定大家都上过高中，让这篇文章让有高中基础就可以看懂，看完这篇文章，就可以自己造一架属于自己无人机了，本文稍微有点硬核，这篇大概是整个系列里面最难的一篇&lt;/p&gt;
&lt;p&gt;先科普一下 &lt;a href=&quot;https://zh.wikipedia.org/wiki/%E5%9C%</summary>
      
    
    
    
    <category term="self" scheme="https://a-wing.top/categories/self/"/>
    
    
    <category term="self" scheme="https://a-wing.top/tags/self/"/>
    
    <category term="log" scheme="https://a-wing.top/tags/log/"/>
    
    <category term="uav" scheme="https://a-wing.top/tags/uav/"/>
    
  </entry>
  
</feed>
