分类: '精华' 的归档
区块链漫游指南
没有人会想到,这个沉默了多年的blog居然还活着,更没有人会想到,它居然还更新了。。。
也没有啥起因,一切都很随意。我最近在学习一些区块链相关的技术,看到这篇文章感觉不错,正好也在提升英语,于是就打算给他翻译出来。那既然都已经翻译了,不如就发表出来吧。
英文原文在这里,如果英文不错的话,还是建议直接看原文,他有很多“银河系漫游指南”相关的梗,很难翻译出来(主要是我也不太懂这些梗),哈哈!
=========开始的分割线=========
本文要讨论的是:区块链、加密货币、智能合约、Web3 以及 NFT(非同质化交易),对这些概念,很多人都有自己的认知,但很少人理解它们的本质以及相互的关系。你正在读的这篇文章的目标,是对这些概念以及背后的技术和实现做一个结构化的介绍。本文以三部曲的形式,分成了5个部分(这是一个梗)。
现如今,IT、数字化、经济、民主和社会相关的很多问题,都被认为可以用基于区块链的方案来解决。但和很多其他领域一样,这里也充斥着各类思想家、淘金者、白嫖党、江湖骗子和末日先知,导致鱼龙混杂。
这一系列文章,目的是以一种易于阅读的方式,为菜鸟和老手提供可靠的洞见。重点是–在这个复杂的、纠缠不清的主题中尽可能–透明地、实质性地、有条理地展示其中的利害关系。本文会以尽量幽默的方式来给大家加深印象。
此鸿篇巨著会分为以下五个层次:
- 区块链本身,一种透明、持久化地存储数据的方法(本文);
- 建立在区块链之上的以比特币和以太坊为例的加密货币,这已经被誉为金融系统的革命了(在即将发表的文章中);以及
- 智能合约,作为有条件地加密货币交易的代表,被认为简化了在线合同的创建和执行(在即将发表的文章中)。智能合约也被用于创建:
- 非同质化代币(NFT),数字艺术品将彻底改变艺术家的支付形式、艺术品贸易和版权(在即将发表的文章中);以及
- 互联网民主化的承诺,被称为Web 3.0或Web3,也是分布式自治组织的基础(DAO;在即将发表的文章中)。
这五层中的每一层,都将先分四节呈现事实和客观分析,另外两节将是一些个人见解和家庭作业。由于比较复杂,这个也不是绝对的:
- 这个技术背后有哪些希望和承诺,以及它们旨在解决什么问题。
- 对实际技术内部原理的简介,以及其可能性和局限性。
- 基于区块链的解决方案是此问题的唯一解吗?如果不是,又有哪些备选方案和替代方案?
- 承诺背后的现实是什么?
- 基于上述分析,对该技术的主观评估。
- 在投资(资金或技术上)之前,需要问自己或专家的问题清单。
准备好了吗?玩了各种我也不知道啥意思的梗之后,让我们开始吧!
第一站:基础层,区块链本身。
- 区块链的倡导者痛批当前经济体制的贪婪、低效、不透明和过度复杂。然而,这些倡导者努力建立的目标却是一个新的贪婪、效率、不透明和过度复杂性的数字化体制。
- 每当有人问我,与没有区块链的方案相比,基于区块链的技术有什么看得见的好处吗?我要么是避而不谈,要么给出的答案会前后矛盾。
尽管如此,整个领域应该有更多的洞察。区块链生态系统提出了几个重要问题,这些问题的答案与我们社会的未来以及我们与技术的互动都息息相关。十几年来,“区块链”一词在Buzzword Charts中得分很高。仅凭这点就足以成为深入分析和理解这一现象的理由了。
第一部分: 区块链自身
- “足够先进的技术与魔法是无法区分的“,但如果某些技术被作为魔法在卖弄,那就该问一下此技术是真的有真材实料,还是它只能进行声光电的魔法表演了(换句话说:如果事情看起来好得令人难以置信,它往往是真的)。
- “如果您将区块链加到垃圾流程中,那你会得到一个垃圾区块链流程”。(换句话说:即使你买到了先进的技术和人才,好的解决方案也不一定会自动出现,如果这些技术带来了新的复杂性和低效率,就更不会了)。
- 仅仅是一个好主意并不能自动成为好的解决方案的一部分。(换句话说:即使是锤子也应该结识其他朋友,而不仅仅是钉子)。
- 信任确实可以提高效率并降低复杂性。但区块链的解决方案并不需要以任何形式的信任为前提,但代价是更低效率和更高的复杂性。(换句话说:复杂性是安全、透明度、效率和信任的敌人)。
这不是“管理者摘要”,而是“非经理人”(或非决策者)的摘要,因为我认为,当面临一项被认为是革命性的技术时,决策者尤其需要了解更多细节,以便他们可以区分这是昙花一现还是有真的好处。
区块链是为解决一个非常具体但特别复杂的问题而创建的:建立一种相当于纸币的数字货币,可以防止伪造,并保持匿名性。对于这个问题,直接使用区块链可能是一个很好的解决方案。但对于大多数其他场景来说,就会是大规模的过度工程,因为即使是对前提条件做微小改变也会带来更有效的解决方案。但要知晓和理解这些因素,就需要更深入地了解,这正是本文的目标所在。
在创作过程中,本文变得比原计划的还要庞大。幸好它在可读性和可理解性方面也有所提高了。换句话说,它本是以高管和决策者为目标受众而写的,但加入了许多令人难忘的类比和与现实世界的理解,也更适合作为茶余饭后的谈资了。
我认为本文做到了:您可以随时回来浏览某个段落,并在遗忘了某些内容时回头查看。章节中经常会有大字体、高亮的概述语,来帮助此类读者定位内容。
区块链,以及比特币,出现于2008年金融危机的高峰期,作为对银行在这场危机中的作用的制衡。银行先是利用不透明的金融产品从不良抵押贷款风险中获利,然后由于其系统相关性(“大而不能倒”),消耗了大量的救助资金。因此,对中央组织(银行、州政府等)的透明度和独立性的渴望是贯穿区块链生态系统各个层面的主旋律。
这个概念带有加密无政府主义和无政府资本主义的特征,即希望避免信任任何组织和机构,尤其不信任国家机构。只有程序(“代码即法律”)或自由的(即不受监管的)市场才值得信任。这种特征贯穿各个层面,尤其在加密货币和智能合约中最为明显。
目录(略,其实是我不知道怎么做)
愿景:
由于区块链和比特币是同一时代的产物,所以区块链一词有时也被口语化,指代基于它的应用(如加密货币、智能合同等)和整个生态。我先澄清一下这几个概念。
区块链具有(或应该具有)以下四到六个要素(还未有统一的定论):
Wikipedia | Christian Cachin/IBM | MoreThanDigital | |
链 | 分布式账本 | 数据完整性 | |
去中心化存储 | 密码学 | 可靠性 | |
共识 | 分布式共识 | 快速存储 | |
数据处理 security | 业务流程 | 可分析、透明 | |
透明、保密 | — | — | |
不可篡改 | — | — |
让我们以克里斯蒂安·卡钦 (Christian Cachin)的四要素为例来进行阐述:
- 一个记录了所有历史交易的不可篡改的分布式账本。
- 用密码学来确保会计的完整性和交易的真实性。
- 关于账本内容和交易有效性形成分布式、可容错的共识:区块链上的所有参与者都应该能对最终状态(例如账户余额)达成一致,即使不是所有参与者都随时在线。
- 业务流程(“业务逻辑”),旨在确保交易的有效性并执行它们(帐户管理、智能合同……)。这个领域会放到后续的两篇文章里具体展开。
这非常的抽象,尤其是这里并没有说明区块链要实现的确切目标。由于本文旨在解构这个复杂的问题,我们需要先回答一个问题:人们到底想使用这些功能来做什么?因此,让我们看看典型的“区块链的10大典型应用”,然后以此来说明其中的好处:
- 国际金融交易:数据一致性、放篡改、跳过所有中介→更低的交易成本和更快的交易速度。
- 医疗保健:在分布式网络允许存储敏感数据:患者记录、医疗结果和临床病史。仅能由所有者授权的用户才可访问。
- 身份管理:身份证件可以安全地数字化,永不丢失且更快更安全。
- 防止洗钱:透明度、记录保存和归因。
- 保险:创建智能合约,用于保险理赔和欺诈检测。
- 供应链管理:更简单的合同,可持续地跟踪物流,透明化地记录从原产地到商店的全链路。更快/更简单/更便宜的查询。
- 租车市场:访问权和所有权的控制、车辆自动租赁+账单、安全计费,即使是电动车也适用。
- 能源市场:处理可变、可跟踪的交易(私电并网)、充电站的计费/付款。
- 数字选举:无法被操纵,每张选票可跟踪。
- 学位证书:颁发国际公认的不能伪造的学位和学历证书
这些主题涵盖了广泛的领域,从区块链本身到加密货币和智能合约。如果这能解决现代社会中这么多悬而未决的问题,那就太好了。然而这些在过去的14年里并没有真正发生,所以一切还没那么简单。
在接下来的(很详细的)技术章节中,让我们先看看用于解决上述挑战的技术和特性,然后再在现实中加以应用。由于我们在这里专注于区块链本身,我们将在本文末尾讨论数据一致性/反篡改、防丢失、透明/记录和(部分)归因/可追溯性/伪造等主题。在奠定必要的基石后,我们将在后续文章中更详细地展开其余的要点。
技术:
即使是最简单的部分,即区块链,就已经相当复杂了。在我们深入研究之前,先看这个基于比特币的概述。
比特币的区块链由一连串的区块(上图中的“Chain”)组成,会随着时间而增长(在某些特定场景下也可以分叉,后面会提到)。
目前,比特币区块链已经约有73万个区块(译者注:2022年4月是73万个块,翻译时的2022年12月,已经有76万个区块了),并且,约10分钟就会新增一个新的区块。每个区块都包含了一组交易。整个事情由构建系统的计算机(节点)共同管理,实际上每个人都可以参与其中(无需特别授权)。
为了了解其中的相互作用,让我们分几个小步骤来分析它,这些步骤一起构成了本技术章节的小目录:
- 区块链的结构和功能
- 如何在没有值班人的情况下新增一个区块(共识)?
- 节点之间如何沟通(P2P网络)
- 人们为什么会自觉这样做(激励)?
- 一个有效的区块是如何产生的(挖矿和工作量证明PoW)
- 还能如何解决(权益证明和朋友证明)?
接下来是小结,以便你可以看到森林的所有树木(译者注:总揽全局)。
在我们分析区块链之前,让我们以“用纸张的办公室”来类比。我们将看到无政府的会计师(或公证人)存了很多防篡改的活页。
类比:公证人的办公室
让我们以现实世界的类比来开始对区块链技术的介绍:无政府的会计和公证办公室(作为初级公证人,他们最初只被允许处理小额金融资产。在后面的章节中,他们也将被允许对合同和有形资产的转让进行公证。这就是为什么我们现在就称他们为“公证人”,尽管目前他们只是记账而已)。
出于效率原因,几位公证人分担了这项工作。现在,大部头的账本已被废弃,取而代之的是使用活页(无论如何,在公证人中这更受欢迎)。所以,在第一天,负责任的公证人填写完了交易表,并盖章确认了。
第二天,值班公证人重复了同样的程序。但是,为了以后没有人可以将前一天的表格换掉,他今天的表格中包括前一天黄色表格的缩减副本。他像往常一样在晚上下班前盖了公章,以确认其准确性。
第三天,第三名公证人重复该过程。还是在晚上下班前,在他的交易表上盖章,也附上了前一天表格的缩减副本,在该副本上,也可以看到前天的缩减副本,以此类推。
第三张活页,上面有第二个的副本,可以看到第一个
继续这个过程,公证员们以协助的方式生成了一个共同的文件链。在这个链条中,是无法在不修改后续表格的情况下,修改掉以前的表格内容的。因为这肯定会背发现。
但协调公证员的权利和义务,是需要以信任和权威为基础和前提的。下面我们会讨论如何在没有事先共识的情况下解决纸质区块链的信任问题(从而信任任何人),因为这也是数字区块链的密码学基础。
简单起见,截止目前我们的初级公证员都是有固定的排班表的,他们都有各自的休息时间,因此对谁负责哪一天的记账工作是没有冲突的。但随着我们越来越接近了解真正的区块链,这种情况将会发生改变。
数字版的区块链
用堆叠的乐高积木来类比区块链
让我们切换到数字版区块链,比如比特币使用的区块链就是这样。它的工作原理与上面的活页表格非常相似:每个区块都包含了一个交易列表,有人负责验证交易的真实性,这个人就是“区块的创造者”;在加密货币中,他们也被称为“矿工”。
每个区块都会计算校验和(即所谓的“哈希值”),然后将前一个区块的校验和存储在当前区块中,类似于上面公证表中前一天的缩减副本。这就意味着,新的区块不可撤销地与其前一个区块绑定了,也就间接(或传递)地与所有的祖先区块绑定了。
为了加深印象,让我们稍微调整一下我们的乐高积木:不同于常规的乐高(块与块之间都可以匹配),新的积木每个块顶部的点都构成一个独特的图案(对应于其独特的哈希),上面的块必须有匹配的图案才能叠上去(就像事锁和钥匙一样)。
哈希函数的特点是,即使世界上所有的计算机在数十亿年内不做任何其他事情,他们也无法找到具有相同哈希值的第二个区块。最终的结果是,我们有一个存储在当前区块中的前一个区块的缩减副本。一般一个区块所占据的空间是1-2MB,而哈希只用了32个字节,大约少了5万倍。
(作为对比,在纸质版的比喻中,仍有可能直接从缩减副本中读出所有细节,但仅从哈希值中不可能读出前一区块的任何内容。但纸质副本和哈希的实际目的–对先前区块的唯一识别–在这两种情况下都完全得到了满足)。
通缉:无值班公证人名册
此前我们的公证员都有一个明确的值班表。每一天,都正好有一个人负责保管账本。账本是由双方同意或由他们的老板指派而产生的。但比特币是加密无政府主义和对中央权力机构在金融危机中的行为感到愤怒的产物;因此,这里必须没有任何形式的“监督者角色”!这就是比特币。无论老板在提高协调效率和创建规则方面有多大作用,但在某些时候,老板也可能会滥用他们的权力,而系统内将没有任何机制来阻止它。
在现实世界中,确实存在罢免“滥用职权或腐败的上级”的机制,但这种罢免往往是耗时耗力的。现实世界中用以预防此事的机制包括诚实、同情和信任。这些进化的优势可以导致避免出现这种情况或迅速补救。然而,这些都需要定期互动,因此不能扩展到80亿人。而且,无论如何,它们都不符合加密无政府主义的世界观,根据这种世界观,人们不希望让自己依赖任何人。

最著名的自组织例子可能是鱼群和鸟群。
也许你们中的一些人还记得《海底总动员》中对月光鱼的夸张(因此也特别吸引人)的描述。
(adiprayogo liemena at Pexels供图)
这就只剩下公证员之间的相互协商这一条路,即上述的共识。然而,我们不仅没有了制定值班表的老板,我们也没有来雇用公证员的老板,或者在某个公证员喜欢在预定的工作日去钓鱼的时候叫他们回来。
这意味着,我们需要一个必须应对不断变化的公证员人数的系统,他们都不想参加入职测试,也不想事先宣布他们的存在或缺席。最纯粹的混乱,或者,稍微好一点,一个自我组织的系统。
人类群体远不如鱼群那么同质化;因此,几乎没有十几个人以上的自组织群体。因此,对于更大的群体来说,迟早会出现分工结构。在标准化工作流程中,这些结构减少了协调工作,从而提高了效率。但这些权力地位等体系正是加密货币想尽量避免的。正如我们接下来会看到的那样,这是以牺牲效率为代价的。
组织结构
为了让我们的纸质版公证员组成公证处,我们需要两者兼而有:
- 组织结构(本章)
- 奖惩制度(胡萝卜和大棒),即老板和工资的替代品,我们将在下一章中介绍
首先是组织:所有想成为公证员的人都会可以成为平等合作伙伴网络的一部分,也称为同行,以交换信息。在这样一个点对点的网络中,(1)要包含在区块链中的交易会被传递给每个人,(2)新创建的块(或类比中,盖章的表单)会由每个人存储。
这个P2P系统是由一组计算机(”节点”,在上图中是带字母的圆圈)和它们之间的一些用于交换信息的链接(通道或 “边”)组成的。现在,如果蓝色节点A有一个新的信息,它会把它发送给其所有的相邻节点,如蓝色箭头所示(节点B和C)。类似的B将信息发送给了C和D(红色箭头),A被排除在外,因为那是B的信息来源,显然A一定已经有了这个信息。
在B和C之间的那条边上,信息在两个方向上都会被传播一次,因为在发送时,B和C都无法知道对方是否已经知道了这个信息。
这种所谓的 “泛滥(flooding) “创造了一个(微小的)潮汐,席卷了整个信道系统,到达了从A到G的所有节点。许多参与者将多次收到信息,在最坏的情况下,你的每个邻居都会给你发一遍。效率不高,但当你不能或不愿相信任何人时,就只能这样了。
最后,对我们的公证员来说,这意味着他们可以打消希望再有任何空闲时间的念头了。虽然这不是特别有利于激励,但它对容忍个别参与者的失败(甚至是恶意)是很有效的。由于缺乏中央权威,没有仲裁机制,所以每个人都必须首先照顾自己,尽可能少地依赖其他人。从加密无政府主义的角度来看,这种浪费比信任更好;这种模式在整个生态系统中一次又一次地伴随着我们。
胡萝卜和大棒
然而,上述通过公证网络泛滥的信息只是日常工作的一小部分:我们的公证员不仅要不停地转发所有的这些信息,而且每个人还要不断地更新交易清单,因为你不相信你的同事能做好。另一方面,这也意味着从现在开始,你将没有任何空闲时间,因为你要昼夜不停地忙于工作和检查你的同事的工作。即使这么做非常低效。
但首先要回到现实世界的日常工作。让一个团队保持“正轨”的方法之一是建立一套有明确标准和奖励的激励系统。对于人类团队来说,验证是否达到标准其实是非常困难的,即使已经在诸如计件工作上尝试过;这有点像劣币驱逐良币。很难持续下去,因为数量是以牺牲质量为代价的,或者团队成员会为了让自己的表现看起来更好,而贬低同事们的表现。阴谋、隐瞒、谎言和操纵,甚至可能达到破坏。这些都是电影和小说中保证悬念的关键因素,但在现实中,它们对生产力和实现共同目标是非常不利的。
在我们的公证处,流程是简单的、重复的、标准化的,这就是为什么大多数区块链假定工作情况是客观可衡量的。因此,为比特币创建了一个货币激励系统,根据具体的、可验证的规则,如果这个区块被普遍认可,就会给予奖励(以比特币计)。这就是为什么这个程序被称为 “工作证明”(PoW)。
到目前为止都很简单。但是如果某人的工作做得不够好,你怎么能惩罚他呢?咱们让工作变得如此艰难和乏味,以至于没有人自愿去做,除非他们能期望得到慷慨的回报!
所以,激励措施是基于人类最强烈的动机之一:贪婪,对金钱和权力的贪婪。而这种激励机制也是完美的,至少在表面上是这样。正是因为有了贪婪的魔力,让人们投入了空前的创造力,去寻找和发现漏洞。在本篇和下一篇文章的“现实”章节的事实之后,会有更多关于这个问题的内容。
工作和工资
任何在比特币区块链上添加了一个新区块的人,目前都会得到6.25个比特币(₿)的奖励,用于创建(“挖矿”)新区块,目前这相当于接近25万瑞士法郎。对于10分钟的工作来说,这很吸引人,也难怪贪婪会成为驱动力。(在下一篇文章中会有更多关于金融和它在生态系统中的作用的内容)
如果你想得到那25万,你要怎么做呢?
- 收集传入的有效交易(详见下一篇关于加密货币和交易的文章)。
- 在将要创建的新区块中包含尽可能多的待确认交易。
- 处理这个区块,直到完成“足够的工作”(后面会说明)。
- 然后,这些区块的创建者(“矿工”)就将获得6.25₿的奖励,以及该区块中包含的所有交易的交易费(截至今天,一个区块收取的交易费仅约为采矿奖励的1%)。但愿它是你的!

所谓的D100骰子(或“百分之一骰子”)
它只有一百个面。仍100次平均才会有一个1。
加密的谜题需要反复、艰苦的尝试才能得到解决,一旦解决就完成了“足够的工作”。例如,比特币网络上的矿机需要不知疲倦地投120*1021亿面的骰子,直到它出现了数字1。这次成功的骰子平均需要尝试120*1021亿次后才能成功。有了这个结果,即完成了“工作证明”,矿工就可以创建区块并领取奖励了。

我们的公证人不断从国际空间站投下公章
当然,我们的公证人并不玩骰子。但他们喜欢捉迷藏游戏,是非常健忘又充满激情的太空游客。当他们完成交易收集工作后,他们将已完成的交易清单藏在地球表面5.1亿平方公里的某处,立即忘记具体位置,然后进入太空。从那他们尽可能快地将橡皮图章扔到地球表面,直到有人击中交易清单上的6×7厘米大小的盖章位(5.1亿平方公里/120*1021)≈42平方厘米。老实说!希望没人真拿地球这么玩~
计算机不会玩骰子也不掷公章,因为这俩都太简单了。因此,在尝试“挖”新块时,这个新的区块里有个特定的字段会被填充随机数,直到整个块的校验和(“hash”)值足够小;因为它每120*1021次只有一次。因此,经过无数次试验,这个足够小的hash就是我们工作的证明。(顺便说一句,如果不实际计算是无法预测块的hash值的,所以挖矿没有快捷方式)。
矿工的算力越大,这个谜题解决得就会越快。然而,比特币协议的设计决定指出,平均每10分钟才会新增一个新区块。为了确保随着整体算力的增加或减少,仍然维持在10分钟出块,就必须定期调整“足够的工作”的定义,以反映网络上可用的计算能力。在比特币网络中,这种难度调整大约每两周会进行一次。
在骰子的类比中,这意味着如果网络中有更多或更快的参与者,骰子的面数也会相应地滚动增加,所以就要更快地扔骰子,直到出现想要的点数。(就我们太空公证人而言,盖章的格子将缩小)。
结果就是:
- 谁能挖到矿,并获得出块奖励,其实很大程度上靠的是概率,为了实现更均匀、可预期的收益,矿工们聚集在矿池中,然后在矿池内分配收益。
- 有种可能的情况是,几个节点都很走运,几乎同时找到了无数个(大约1054个)可能的谜题的解之一。因此,此时几个有效的新区块会同时存在。一般来说,这些区块因其中包含的交易列表而不同,所以它们并不能互换。因此,系统必须做出选择,以决定继续使用哪个区块。详情见下。
- 如果说胡萝卜是中奖的机会(就像开彩票一样),那大棒就是对撕毁你的彩票。如果你付出了巨大的努力挖到了一个有效的区块,但比特币网络中的其他节点因为你犯了一个错误(例如记录了一个无效的交易)而不接受这个区块,那么你就被剥夺了获奖的机会。你将继续承担各种成本(电费、空调费、租金、其他摊销…),但甚至没有一丝获胜的机会。
- 只要有人认为他或她的累积总成本低于预期的区块收益,他们就会尝试扩张额外的采矿算力。所以,只要没有禁令改变规则,网络的总算力就会遵循比特币的价格而变化。
- 无论谁能以更低廉的价格获得电力(和制冷)都是有利可图的。因此,挖矿往往在电力成本低的地方进行,而且通常是火电。(除非你能免费“获得”电力)。

在事实上禁止了加密货币后,中国(黄色)的采矿算力骤降(图片:CBECI)。
剑桥比特币能源消耗指数(CBECI)估算比特币开采的年用电量约为125 TWh, Digiconomist则认为是200 TWh。CBECI假设每千瓦时电价为5美分(对于西欧来说:不切实际的),仅电力就花费62.5或100亿美元,也即每天2000万或3200万美元(!),而且这一趋势还在上升。
这种高能耗也被作为“禁止工作证明加密货币”的一个论点而被提出来。尽管非常努力,但它应该(像所有的政治决定,但不仅仅是这些)总是被视为成本/效益分析的结果。对于这一点,我随后的加密货币文章会继续展开。
区块链中的不可篡改性
支持区块链的一个重要论据是它的不可篡改性。因此,想要替换现有区块链中的某个特定的块,而不影响后续的块,是不可能的(记得活页的比喻)。也就是说,我们不能简单地把其中一个蓝紫色区块(例图中标有 “OK “的区块)”就这样直接 “替换成青色区块(”BAD”),这就是为什么它也被划成了红色❌。这是由于所使用的加密散列函数的安全性所致。
那要怎么样,才能真的像这样直接替换区块呢?
比特币网络把巨量的算力和工作量证明相结合,而且消耗的电力是世界上最强大的超级计算机的500倍。尽管有这样惊人的计算能力,整个比特币网络也要计算20亿亿年才能用另一个新区块替换一个现有区块,并使其校验和(哈希值)与原始区块相同,即它可以被接受为链上的有效成员。(至于是否真的会被接受则是另一回事)。
20亿亿年后面有N个零(到底几个0我也不知道了,译者注)。大家都喜欢与宇宙的年龄进行比较:即使自130多亿年前的大爆炸以来,这些计算机都在连续做计算,也只能完成上述工作的极其微小的一部分(十二亿分之一;又是一堆零!)。(即使是找到“关于生命、宇宙和所有其他问题”的答案,可能也比这个更快一些)。
来自IT安全界的人,谨慎地称其为“不切实际”;然而,普通人更喜欢“不可能”。
备选方案
=========未完待续的分割线=========
原文实在太长了,而且有很多俏皮的俚语,我实在翻译不下去了。。。
先发出来给大家看看吧,如果大家觉得有必要,我就继续翻译。。
PS:为了写这文章,后台临时装php-dom插件、Wordpress也临时装了几个插件、还临时改了几行CSS。。。也是醉了!
一个支持上传的简单http server
现在,很多人都知道,python里有个SimpleHTTPServer,可以拿来方便地共享文件。比如,你要发送某个文件给局域网里的同学,你只要cd到所在路径,然后执行这么一行:
python -m SimpleHTTPServer |
人家就可以访问 http://你的IP:8000 来访问你要共享的文件了。
像我早已把这个命令做了alias。
但是,某一天,你需要从同学哪里复制一个文件到本机,然后你就会跟你同学说,XX,共享下某目录。当你以为可以用http来访问他的8000端口的时候,他却告诉你,不好意思,我是windows啦~~
当然你可以选择在他windows里装个python,也可以选择使用samba、ftp等其他方式,但是有没有和之前一样简单的方式呢~
当然了,这时候,你就需要一个支持上传的简单http server,也就是我这个:SimpleHTTPServerWithUpload.py,哈哈。然后你开个服务,让人家上传即可。
其实这个就是修改自SimpleHTTPServer的,只不过我给它加上了最原始的上传功能,安全性方面没有验证过,不过理论上应该不会没人一直开着这个吧?另外,我对RFC1867的理解不一定透彻,所以,Use at your own risk!
截图如下:
代码在此,单文件、零配置,直接用python运行。
python的正则表达式 re
延伸阅读:python的 内建函数 和 subprocess 。此文是本系列的第三篇文章了,和之前一样,内容出自官方文档,但是会有自己的理解,并非单纯的翻译。所以,如果我理解有误,欢迎指正,谢谢。
本模块提供了和Perl里的正则表达式类似的功能,不关是正则表达式本身还是被搜索的字符串,都可以是Unicode字符,这点不用担心,python会处理地和Ascii字符一样漂亮。
正则表达式使用反斜杆(\
)来转义特殊字符,使其可以匹配字符本身,而不是指定其他特殊的含义。这可能会和python字面意义上的字符串转义相冲突,这也许有些令人费解。比如,要匹配一个反斜杆本身,你也许要用'\\\\'
来做为正则表达式的字符串,因为正则表达式要是\\
,而字符串里,每个反斜杆都要写成\\
。
你也可以在字符串前加上 r 这个前缀来避免部分疑惑,因为 r 开头的python字符串是 raw 字符串,所以里面的所有字符都不会被转义,比如r'\n'
这个字符串就是一个反斜杆加上一字母n,而'\n'
我们知道这是个换行符。因此,上面的'\\\\'
你也可以写成r'\\'
,这样,应该就好理解很多了。可以看下面这段:
>>> import re >>> s = '\x5c' #0x5c就是反斜杆 >>> print s \ >>> re.match('\\\\', s) #这样可以匹配 <_sre.SRE_Match object at 0xb6949e20> >>> re.match(r'\\', s) #这样也可以 <_sre.SRE_Match object at 0x80ce2c0> >>> re.match('\\', s) #但是这样不行 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python2.6/re.py", line 137, in match return _compile(pattern, flags).match(string) File "/usr/lib/python2.6/re.py", line 245, in _compile raise error, v # invalid expression sre_constants.error: bogus escape (end of line) >>> |
另外值得一提的是,re模块的方法,大多也就是RegexObject对象的方法,两者的区别在于执行效率。这个在最后再展开吧。
正则表达式语法
正则表达式(RE)指定一个与之匹配的字符集合;本模块所提供的函数,将可以用来检查所给的字符串是否与指定的正则表达式匹配。
正则表达式可以被连接,从而形成新的正则表达式;例如A和B都是正则表达式,那么AB也是正则表达式。一般地,如果字符串p与A匹配,q与B匹配的话,那么字符串pq也会与AB匹配,但A或者B里含有边界限定条件或者命名组操作的情况除外。也就是说,复杂的正则表达式可以用简单的连接而成。
正则表达式可以包含特殊字符和普通字符,大部分字符比如'A'
,'a'
和'0'
都是普通字符,如果做为正则表达式,它们将匹配它们本身。由于正则表达式可以连接,所以连接多个普通字符而成的正则表达式last
也将匹配'last'
。(后面将用不带引号的表示正则表达式,带引号的表示字符串)
下面就来介绍正则表达式的特殊字符:
'.'
点号,在普通模式,它匹配除换行符外的任意一个字符;如果指定了 DOTALL 标记,匹配包括换行符以内的任意一个字符。
'^'
尖尖号,匹配一个字符串的开始,在 MULTILINE 模式下,也将匹配任意一个新行的开始。
'$'
美元符号,匹配一个字符串的结尾或者字符串最后面的换行符,在 MULTILINE 模式下,也匹配任意一行的行尾。也就是说,普通模式下,foo.$
去搜索'foo1\nfoo2\n'
只会找到’foo2’,但是在 MULTILINE 模式,还能找到 ‘foo1’,而且就用一个 $
去搜索'foo\n'
的话,会找到两个空的匹配:一个是最后的换行符,一个是字符串的结尾,演示:
>>> re.findall('(foo.$)', 'foo1\nfoo2\n') ['foo2'] >>> re.findall('(foo.$)', 'foo1\nfoo2\n', re.MULTILINE) ['foo1', 'foo2'] >>> re.findall('($)', 'foo\n') ['', ''] |
'*'
星号,指定将前面的RE重复0次或者任意多次,而且总是试图尽量多次地匹配。
'+'
加号,指定将前面的RE重复1次或者任意多次,而且总是试图尽量多次地匹配。
'?'
问号,指定将前面的RE重复0次或者1次,如果有的话,也尽量匹配1次。
*?
, +?
, ??
从前面的描述可以看到'*'
,'+'
和'?'
都是贪婪的,但这也许并不是我们说要的,所以,可以在后面加个问号,将策略改为非贪婪,只匹配尽量少的RE。示例,体会两者的区别:
>>> re.findall('<(.*)>', '<H1>title</H1>') ['H1>title</H1'] >>> re.findall('<(.*?)>', '<H1>title</H1>') ['H1', '/H1'] |
{m}
m是一个数字,指定将前面的RE重复m次。
{m,n}
m和n都是数字,指定将前面的RE重复m到n次,例如a{3,5}
匹配3到5个连续的a。注意,如果省略m,将匹配0到n个前面的RE;如果省略n,将匹配n到无穷多个前面的RE;当然中间的逗号是不能省略的,不然就变成前面那种形式了。
{m,n}?
前面说的{m,n}
,也是贪婪的,a{3,5}
如果有5个以上连续a的话,会匹配5个,这个也可以通过加问号改变。a{3,5}?
如果可能的话,将只匹配3个a。
'\'
反斜杆,转义'*'
,'?'
等特殊字符,或者指定一个特殊序列(下面会详述)
由于之前所述的原因,强烈建议用raw字符串来表述正则。
[]
方括号,用于指定一个字符的集合。可以单独列出字符,也可以用'-'
连接起止字符以表示一个范围。特殊字符在中括号里将失效,比如[akm$]
就表示字符'a'
,'k'
,'m'
,或'$'
,在这里$也变身为普通字符了。[a-z]
匹配任意一个小写字母,[a-zA-Z0-9]
匹配任意一个字母或数字。如果你要匹配']'
或'-'
本身,你需要加反斜杆转义,或者是将其置于中括号的最前面,比如[]]
可以匹配']'
你还可以对一个字符集合取反,以匹配任意不在这个字符集合里的字符,取反
操作用一个'^'
放在集合的最前面表示,放在其他地方的'^'
将不会起特殊作用。例如[^5]
将匹配任意不是'5'
的字符;[^^]
将匹配任意不是'^'
的字符。
注意:在中括号里,+
、*
、(
、)
这类字符将会失去特殊含义,仅作为普通字符。反向引用也不能在中括号内使用。
'|'
管道符号,A和B是任意的RE,那么A|B
就是匹配A或者B的一个新的RE。任意个数的RE都可以像这样用管道符号间隔连接起来。这种形式可以被用于组中(后面将详述)。对于目标字符串,被'|'
分割的RE将自左至右一一被测试,一旦有一个测试成功,后面的将不再被测试,即使后面的RE可能可以匹配更长的串,换句话说,'|'
操作符是非贪婪的。要匹配字面意义上的'|'
,可以用反斜杆转义:\|
,或是包含在反括号内:[|]
。
(...)
匹配圆括号里的RE匹配的内容,并指定组的开始和结束位置。组里面的内容可以被提取,也可以采用\number
这样的特殊序列,被用于后续的匹配。要匹配字面意义上的'('
和')'
,可以用反斜杆转义:\(
、\)
,或是包含在反括号内:[(]
、[)]
。
(?...)
这是一个表达式的扩展符号。'?'
后的第一个字母决定了整个表达式的语法和含义,除了(?P
以外,表达式不会产生一个新的组。下面介绍几个目前已被支持的扩展:
(?iLmsux)
'i'
、'L'
、'm'
、's'
、'u'
、'x'
里的一个或多个字母。表达式不匹配任何字符,但是指定相应的标志:re.I(忽略大小写)、re.L(依赖locale)、re.M(多行模式)、re.S(.匹配所有字符)、re.U(依赖Unicode)、re.X(详细模式)。关于各个模式的区别,下面会有专门的一节来介绍的。使用这个语法可以代替在re.compile()
的时候或者调用的时候指定flag参数。
例如,上面举过的例子,可以改写成这样(和指定了re.MULTILINE
是一样的效果):
>>> re.findall('(?m)(foo.$)', 'foo1\nfoo2\n') ['foo1', 'foo2'] |
另外,还要注意(?x)
标志如果有的话,要放在最前面。
(?:...)
匹配内部的RE所匹配的内容,但是不建立组。
(?P<name>...)
和普通的圆括号类似,但是子串匹配到的内容将可以用命名的name参数来提取。组的name必须是有效的python标识符,而且在本表达式内不重名。命名了的组和普通组一样,也用数字来提取,也就是说名字只是个额外的属性。
演示一下:
>>> m=re.match('(?P<var>[a-zA-Z_]\w*)', 'abc=123') >>> m.group('var') 'abc' >>> m.group(1) 'abc' |
(?P=name)
匹配之前以name命名的组里的内容。
演示一下:
>>> re.match('<(?P<tagname>\w*)>.*</(?P=tagname)>', '<h1>xxx</h2>') #这个不匹配 >>> re.match('<(?P<tagname>\w*)>.*</(?P=tagname)>', '<h1>xxx</h1>') #这个匹配 <_sre.SRE_Match object at 0xb69588e0> |
(?#...)
注释,圆括号里的内容会被忽略。
(?=...)
如果 ...
匹配接下来的字符,才算匹配,但是并不会消耗任何被匹配的字符。例如 Isaac (?=Asimov)
只会匹配后面跟着 'Asimov'
的 'Isaac '
,这个叫做“前瞻断言”。
(?!...)
和上面的相反,只匹配接下来的字符串不匹配 ...
的串,这叫做“反前瞻断言”。
(?<=...)
只有当当前位置之前的字符串匹配 ...
,整个匹配才有效,这叫“后顾断言”。字符串'abcdef'
可以匹配正则(?<=abc)def
,因为会后向查找3个字符,看是否为abc。所以内置的子RE,需要是固定长度的,比如可以是abc
、a|b
,但不能是a*
、a{3,4}
。注意这种RE永远不会匹配到字符串的开头。举个例子,找到连字符('-'
)后的单词:
>>> m = re.search('(?<=-)\w+', 'spam-egg') >>> m.group(0) 'egg' |
(?<!...)
同理,这个叫做“反后顾断言”,子RE需要固定长度的,含义是前面的字符串不匹配 ...
整个才算匹配。
(?(id/name)yes-pattern|no-pattern)
如有由id或者name指定的组存在的话,将会匹配yes-pattern
,否则将会匹配no-pattern
,通常情况下no-pattern
也可以省略。例如:(<)?(\w+@\w+(?:\.\w+)+)(?(1)>)
可以匹配 '<user@host.com>'
和 'user@host.com'
,但是不会匹配 '<user@host.com'
。
下面列出以'\'
开头的特殊序列。如果某个字符没有在下面列出,那么RE的结果会只匹配那个字母本身,比如,\$
只匹配字面意义上的'$'
。
\number
匹配number所指的组相同的字符串。组的序号从1开始。例如:(.+) \1
可以匹配'the the'
和'55 55'
,但不匹配'the end'
。这种序列在一个正则表达式里最多可以有99个,如果number以0开头,或是有3位以上的数字,就会被当做八进制表示的字符了。同时,这个也不能用于方括号内。
\A
只匹配字符串的开始。
\b
匹配单词边界(包括开始和结束),这里的“单词”,是指连续的字母、数字和下划线组成的字符串。注意,\b
的定义是\w
和\W
的交界,所以精确的定义有赖于UNICODE
和LOCALE
这两个标志位。
\B
和\b
相反,\B
匹配非单词边界。也依赖于UNICODE
和LOCALE
这两个标志位。
\d
未指定UNICODE
标志时,匹配数字,等效于:[0-9]
。指定了UNICODE
标志时,还会匹配其他Unicode库里描述为字符串的符号。便于理解,举个例子(好不容易找的例子啊,呵呵):
#\u2076\和u2084分别是上标的6和下标的4,属于unicode的DIGIT >>> unistr = u'\u2076\u2084abc' >>> print unistr ⁶₄abc >>> print re.findall('\d+', unistr, re.U)[0] ⁶₄ |
\D
和\d
相反,不多说了。
\s
当未指定UNICODE
和LOCALE
这两个标志位时,匹配任何空白字符,等效于[ \t\n\r\f\v]
。如果指定了LOCALE
,则还要加LOCALE相关的空白字符;如果指定了UNICODE
,还要加上UNICODE空白字符,如较常见的空宽度连接空格(\uFEFF)、零宽度非连接空格(\u200B)等。
\S
和\s
相反,也不多说。
\w
当未指定UNICODE
和LOCALE
这两个标志位时,等效于[a-zA-Z0-9_]
。当指定了LOCALE
时,为[0-9_]
加上当前LOCAL指定的字母。当指定了UNICODE
时,为[0-9_]
加上UNICODE库里的所有字母。
\W
和\w
相反,不多说。
\Z
只匹配字符串的结尾。
匹配之于搜索
python提供了两种基于正则表达式的操作:匹配(match)从字符串的开始检查字符串是否个正则匹配。而搜索(search)检查字符串任意位置是否有匹配的子串(perl默认就是如此)。
注意,即使search的正则以'^'
开头,match和search也还是有许多不同的。
>>> re.match("c", "abcdef") # 不匹配 >>> re.search("c", "abcdef") # 匹配 <_sre.SRE_Match object at ...> |
模块的属性和方法
re
.compile(pattern[, flags])
把一个正则表达式pattern编译成正则对象,以便可以用正则对象的match和search方法。
得到的正则对象的行为(也就是模式)可以用flags来指定,值可以由几个下面的值OR得到。
以下两段内容在语法上是等效的:
prog = re.compile(pattern) result = prog.match(string) |
result = re.match(pattern, string) |
区别是,用了re.compile以后,正则对象会得到保留,这样在需要多次运用这个正则对象的时候,效率会有较大的提升。再用上面用过的例子来演示一下,用相同的正则匹配相同的字符串,执行100万次,就体现出compile的效率了(数据来自我那1.86G CPU的神舟本本):
>>> timeit.timeit( ... setup='''import re; reg = re.compile('<(?P<tagname>\w*)>.*</(?P=tagname)>')''', ... stmt='''reg.match('<h1>xxx</h1>')''', ... number=1000000) 1.2062149047851562 >>> timeit.timeit( ... setup='''import re''', ... stmt='''re.match('<(?P<tagname>\w*)>.*</(?P=tagname)>', '<h1>xxx</h1>')''', ... number=1000000) 4.4380838871002197 |
re
.I
re
.IGNORECASE
让正则表达式忽略大小写,这样一来,[A-Z]
也可以匹配小写字母了。此特性和locale无关。
re
.L
re
.LOCALE
让\w
、\W
、\b
、\B
、\s
和\S
依赖当前的locale。
re
.M
re
.MULTILINE
影响'^'
和'$'
的行为,指定了以后,'^'
会增加匹配每行的开始(也就是换行符后的位置);'$'
会增加匹配每行的结束(也就是换行符前的位置)。
re
.S
re
.DOTALL
影响'.'
的行为,平时'.'
匹配除换行符以外的所有字符,指定了本标志以后,也可以匹配换行符。
re
.U
re
.UNICODE
让\w
、\W
、\b
、\B
、\d
、\D
、\s
和\S
依赖Unicode库。
re
.X
re
.VERBOSE
运用这个标志,你可以写出可读性更好的正则表达式:除了在方括号内的和被反斜杠转义的以外的所有空白字符,都将被忽略,而且每行中,一个正常的井号后的所有字符也被忽略,这样就可以方便地在正则表达式内部写注释了。也就是说,下面两个正则表达式是等效的:
a = re.compile(r"""\d + # the integral part \. # the decimal point \d * # some fractional digits""", re.X) b = re.compile(r"\d+\.\d*") |
re
.search(pattern, string[, flags])
扫描string,看是否有个位置可以匹配正则表达式pattern。如果找到了,就返回一个MatchObject的实例,否则返回None,注意这和找到长度为0的子串含义是不一样的。搜索过程受flags的影响。
re
.match(pattern, string[, flags])
如果字符串string的开头和正则表达式pattern匹配的话,返回一个相应的MatchObject的实例,否则返回None
注意:要在字符串的任意位置搜索的话,需要使用上面的search()。
re
.split(pattern, string[, maxsplit=0])
用匹配pattern的子串来分割string,如果pattern里使用了圆括号,那么被pattern匹配到的串也将作为返回值列表的一部分。如果maxsplit不为0,则最多被分割为maxsplit个子串,剩余部分将整个地被返回。
>>> re.split('\W+', 'Words, words, words.') ['Words', 'words', 'words', ''] >>> re.split('(\W+)', 'Words, words, words.') ['Words', ', ', 'words', ', ', 'words', '.', ''] >>> re.split('\W+', 'Words, words, words.', 1) ['Words', 'words, words.'] |
如果正则有圆括号,并且可以匹配到字符串的开始位置的时候,返回值的第一项,会多出一个空字符串。匹配到字符结尾也是同样的道理:
>>> re.split('(\W+)', '...words, words...') ['', '...', 'words', ', ', 'words', '...', ''] |
注意,split不会被零长度的正则所分割,例如:
>>> re.split('x*', 'foo') ['foo'] >>> re.split("(?m)^$", "foo\n\nbar\n") ['foo\n\nbar\n'] |
re
.findall(pattern, string[, flags])
以列表的形式返回string里匹配pattern的不重叠的子串。string会被从左到右依次扫描,返回的列表也是从左到右一次匹配到的。如果pattern里含有组的话,那么会返回匹配到的组的列表;如果pattern里有多个组,那么各组会先组成一个元组,然后返回值将是一个元组的列表。
由于这个函数不会涉及到MatchObject之类的概念,所以,对新手来说,应该是最好理解也最容易使用的一个函数了。下面就此来举几个简单的例子:
#简单的findall >>> re.findall('\w+', 'hello, world!') ['hello', 'world'] #这个返回的就是元组的列表 >>> re.findall('(\d+)\.(\d+)\.(\d+)\.(\d+)', 'My IP is 192.168.0.2, and your is 192.168.0.3.') [('192', '168', '0', '2'), ('192', '168', '0', '3')] |
re
. finditer(pattern, string[, flags])
和上面的findall()类似,但返回的是MatchObject的实例的迭代器。
还是例子说明问题:
>>> for m in re.finditer('\w+', 'hello, world!'):
... print m.group()
...
hello
world
re
.sub(pattern, repl, string[, count])
替换,将string里,匹配pattern的部分,用repl替换掉,最多替换count次(剩余的匹配将不做处理),然后返回替换后的字符串。如果string里没有可以匹配pattern的串,将被原封不动地返回。repl可以是一个字符串,也可以是一个函数(也可以参考我以前的例子)。如果repl是个字符串,则其中的反斜杆会被处理过,比如 \n
会被转成换行符,反斜杆加数字会被替换成相应的组,比如 \6
表示pattern匹配到的第6个组的内容。
例子:
>>> re.sub(r'def\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*\(\s*\):', ... r'static PyObject*\npy_\1(void)\n{', ... 'def myfunc():') 'static PyObject*\npy_myfunc(void)\n{' |
如果repl是个函数,每次pattern被匹配到的时候,都会被调用一次,传入一个匹配到的MatchObject对象,需要返回一个字符串,在匹配到的位置,就填入返回的字符串。
例子:
>>> def dashrepl(matchobj): ... if matchobj.group(0) == '-': return ' ' ... else: return '-' >>> re.sub('-{1,2}', dashrepl, 'pro----gram-files') 'pro--gram files' |
零长度的匹配也会被替换,比如:
>>> re.sub('x*', '-', 'abcxxd') '-a-b-c-d-' |
特殊地,在替换字符串里,如果有\g
这样的写法,将匹配正则的命名组(前面介绍过的,(?P
这样定义出来的东西)。\g
这样的写法,也是数字的组,也就是说,\g<2>
一般和\2
是等效的,但是万一你要在\2
后面紧接着写上字面意义的0,你就不能写成\20
了(因为这代表第20个组),这时候必须写成\g<2>0
,另外,\g<0>
代表匹配到的整个子串。
例子:
>>> re.sub('-(\d+)-', '-\g<1>0\g<0>', 'a-11-b-22-c') 'a-110-11-b-220-22-c' |
re
.subn(pattern, repl, string[, count])
跟上面的sub()
函数一样,只是它返回的是一个元组 (新字符串, 匹配到的次数)
,还是用例子说话:
>>> re.subn('-(\d+)-', '-\g<1>0\g<0>', 'a-11-b-22-c') ('a-110-11-b-220-22-c', 2) |
re
.escape(string)
把string中,除了字母和数字以外的字符,都加上反斜杆。
>>> print re.escape('abc123_@#$') abc123\_\@\#\$ |
exception re
.error
如果字符串不能被成功编译成正则表达式或者正则表达式在匹配过程中出错了,都会抛出此异常。但是如果正则表达式没有匹配到任何文本,是不会抛出这个异常的。
正则对象
正则对象由re
.compile()返回。它有如下的属性和方法。
match(string[, pos[, endpos]])
作用和模块的match()函数类似,区别就是后面两个参数。
pos是开始搜索的位置,默认为0。endpos是搜索的结束位置,如果endpos比pos还小的话,结果肯定是空的。也就是说只有pos 到 endpos-1 位置的字符串将会被搜索。
例子:
>>> pattern = re.compile("o") >>> pattern.match("dog") # 开始位置不是o,所以不匹配 >>> pattern.match("dog", 1) # 第二个字符是o,所以匹配 <_sre.SRE_Match object at ...> |
search(string[, pos[, endpos]])
作用和模块的search()函数类似,pos和endpos参数和上面的match()函数类似。
split(string[, maxsplit=0])
findall(string[, pos[, endpos]])
finditer(string[, pos[, endpos]])
sub(repl, string[, count=0])
subn(repl, string[, count=0])
这几个函数,都和模块的相应函数一致。
flags
编译本RE时,指定的标志位,如果未指定任何标志位,则为0。
>>> pattern = re.compile("o", re.S|re.U) >>> pattern.flags 48 |
groups
RE所含有的组的个数。
groupindex
一个字典,定义了命名组的名字和序号之间的关系。
例子:
这个正则有3个组,如果匹配到,第一个叫区号,最后一个叫分机号,中间的那个未命名 >>> pattern = re.compile("(?P<quhao>\d+)-(\d+)-(?P<fenjihao>\d+)") >>> pattern.groups 3 >>> pattern.groupindex {'fenjihao': 3, 'quhao': 1} |
pattern
建立本RE的原始字符串,相当于源代码了,呵呵。
还是上面这个正则,可以看到,会原样返回:
>>> print pattern.pattern (?P<quhao>\d+)-(\d+)-(?P<fenjihao>\d+) |
Match对象
re
.MatchObject被用于布尔判断的时候,始终返回True,所以你用 if 语句来判断某个 match() 是否成功是安全的。
它有以下方法和属性:
expand(template)
用template做为模板,将MatchObject展开,就像sub()里的行为一样,看例子:
>>> m = re.match('a=(\d+)', 'a=100') >>> m.expand('above a is \g<1>') 'above a is 100' >>> m.expand(r'above a is \1') 'above a is 100' |
group([group1, ...])
返回一个或多个子组。如果参数为一个,就返回一个子串;如果参数有多个,就返回多个子串注册的元组。如果不传任何参数,效果和传入一个0一样,将返回整个匹配。如果某个groupN未匹配到,相应位置会返回None。如果某个groupN是负数或者大于group的总数,则会抛出IndexError异常。
>>> m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist") >>> m.group(0) # 整个匹配 'Isaac Newton' >>> m.group(1) # 第一个子串 'Isaac' >>> m.group(2) # 第二个子串 'Newton' >>> m.group(1, 2) # 多个子串组成的元组 ('Isaac', 'Newton') |
如果有其中有用(?P
这种语法命名过的子串的话,相应的groupN也可以是名字字符串。例如:
>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds") >>> m.group('first_name') 'Malcolm' >>> m.group('last_name') 'Reynolds' |
如果某个组被匹配到多次,那么只有最后一次的数据,可以被提取到:
>>> m = re.match(r"(..)+", "a1b2c3") # 匹配到3次 >>> m.group(1) # 返回的是最后一次 'c3' |
groups([default])
返回一个由所有匹配到的子串组成的元组。default参数,用于给那些没有匹配到的组做默认值,它的默认值是None
例如:
>>> m = re.match(r"(\d+)\.(\d+)", "24.1632") >>> m.groups() ('24', '1632') |
default的作用:
>>> m = re.match(r"(\d+)\.?(\d+)?", "24") >>> m.groups() # 第二个默认是None ('24', None) >>> m.groups('0') # 现在默认是0了 ('24', '0') |
groupdict([default])
返回一个包含所有命名组的名字和子串的字典,default参数,用于给那些没有匹配到的组做默认值,它的默认值是None,例如:
>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds") >>> m.groupdict() {'first_name': 'Malcolm', 'last_name': 'Reynolds'} |
start([group])
end([group])
返回的是:被组group匹配到的子串在原字符串中的位置。如果不指定group或group指定为0,则代表整个匹配。如果group未匹配到,则返回 -1
。
对于指定的m和g,m.group(g)
和m.string[m.start(g):m.end(g)]
等效。
注意:如果group匹配到空字符串,m.start(group)和m.end(group)将相等。
例如:
>>> m = re.search('b(c?)', 'cba') >>> m.start(0) 1 >>> m.end(0) 2 >>> m.start(1) 2 >>> m.end(1) 2 |
下面是一个把email地址里的“remove_this”去掉的例子:
>>> email = "tony@tiremove_thisger.net" >>> m = re.search("remove_this", email) >>> email[:m.start()] + email[m.end():] 'tony@tiger.net' |
span([group])
返回一个元组: (m.start(group), m.end(group))
pos
就是传给RE对象的search()或match()方法的参数pos,代表RE开始搜索字符串的位置。
endpos
就是传给RE对象的search()或match()方法的参数endpos,代表RE搜索字符串的结束位置。
lastindex
最后一次匹配到的组的数字序号,如果没有匹配到,将得到None。
例如:(a)b
、((a)(b))
和((ab))
正则去匹配'ab'
的话,得到的lastindex为1。而用(a)(b)
去匹配'ab'
的话,得到的lastindex为2。
lastgroup
最后一次匹配到的组的名字,如果没有匹配到或者最后的组没有名字,将得到None。
re
得到本Match对象的正则表达式对象,也就是执行search()或match()的对象。
string
传给search()或match()的字符串。
后面的例子就略了吧,文中已经加了很多我自己的例子了,需要更多例子的话,参照英文原文吧。
最后,感谢我的老婆辛苦地帮我校对,哈哈。
《SED单行脚本快速参考》的 awk 实现
sed和awk都是linux下常用的流编辑器,他们各有各的特色,本文并不是要做什么对比,而是权当好玩,把《SED单行脚本快速参考》这文章,用awk做了一遍~
至于孰好孰坏,那真是很难评论了。一般来说,sed的命令会更短小一些,同时也更难读懂;而awk稍微长点,但是if、while这样的,逻辑性比较强,更加像“程序”。到底喜欢用哪个,就让各位看官自己决定吧!
PS: 貌似这个配色,单行的代码多了以后,拖动的时候会有点眼花的感觉,将就下吧,呵呵。
文本间隔:
——–
# 在每一行后面增加一空行
sed G |
awk '{printf("%s\n\n",$0)}' |
# 将原来的所有空行删除并在每一行后面增加一空行。
# 这样在输出的文本中每一行后面将有且只有一空行。
sed '/^$/d;G' |
awk '!/^$/{printf("%s\n\n",$0)}' |
# 在每一行后面增加两行空行
sed 'G;G' |
awk '{printf("%s\n\n\n",$0)}' |
# 将第一个脚本所产生的所有空行删除(即删除所有偶数行)
sed 'n;d' |
awk '{f=!f;if(f)print $0}' |
# 在匹配式样“regex”的行之前插入一空行
sed '/regex/{x;p;x;}' |
awk '{if(/regex/)printf("\n%s\n",$0);else print $0}' |
# 在匹配式样“regex”的行之后插入一空行
sed '/regex/G' |
awk '{if(/regex/)printf("%s\n\n",$0);else print $0}' |
# 在匹配式样“regex”的行之前和之后各插入一空行
sed '/regex/{x;p;x;G;}' |
awk '{if(/regex/)printf("\n%s\n\n",$0);else print $0}' |
编号:
——–
# 为文件中的每一行进行编号(简单的左对齐方式)。这里使用了“制表符”
# (tab,见本文末尾关于’\t’的用法的描述)而不是空格来对齐边缘。
sed = filename | sed 'N;s/\n/\t/' |
awk '{i++;printf("%d\t%s\n",i,$0)}' |
# 对文件中的所有行编号(行号在左,文字右端对齐)。
sed = filename | sed 'N; s/^/ /; s/ *\(.\{6,\}\)\n/\1 /' |
awk '{i++;printf("%6d %s\n",i,$0)}' |
# 对文件中的所有行编号,但只显示非空白行的行号。
sed '/./=' filename | sed '/./N; s/\n/ /' |
awk '{i++;if(!/^$/)printf("%d %s\n",i,$0);else print}' |
# 计算行数 (模拟 “wc -l”)
sed -n '$=' |
awk '{i++}END{print i}' |
文本转换和替代:
——–
# Unix环境:转换DOS的新行符(CR/LF)为Unix格式。
sed 's/.$//' # 假设所有行以CR/LF结束 sed 's/^M$//' # 在bash/tcsh中,将按Ctrl-M改为按Ctrl-V sed 's/\x0D$//' # ssed、gsed 3.02.80,及更高版本 |
awk '{sub(/\x0D$/,"");print $0}' |
# Unix环境:转换Unix的新行符(LF)为DOS格式。
sed "s/$/`echo -e \\\r`/" # 在ksh下所使用的命令 sed 's/$'"/`echo \\\r`/" # 在bash下所使用的命令 sed "s/$/`echo \\\r`/" # 在zsh下所使用的命令 sed 's/$/\r/' # gsed 3.02.80 及更高版本 |
awk '{printf("%s\r\n",$0)}' |
# DOS环境:转换Unix新行符(LF)为DOS格式。
sed "s/$//" # 方法 1 sed -n p # 方法 2 |
DOS环境的略过 |
# DOS环境:转换DOS新行符(CR/LF)为Unix格式。
# 下面的脚本只对UnxUtils sed 4.0.7 及更高版本有效。要识别UnxUtils版本的
# sed可以通过其特有的“–text”选项。你可以使用帮助选项(“–help”)看
# 其中有无一个“–text”项以此来判断所使用的是否是UnxUtils版本。其它DOS
# 版本的的sed则无法进行这一转换。但可以用“tr”来实现这一转换。
sed "s/\r//" infile >outfile # UnxUtils sed v4.0.7 或更高版本 tr -d \r <infile >outfile # GNU tr 1.22 或更高版本 |
DOS环境的略过 |
# 将每一行前导的“空白字符”(空格,制表符)删除
# 使之左对齐
sed 's/^[ \t]*//' # 见本文末尾关于'\t'用法的描述 |
awk '{sub(/^[ \t]+/,"");print $0}' |
# 将每一行拖尾的“空白字符”(空格,制表符)删除
sed 's/[ \t]*$//' # 见本文末尾关于'\t'用法的描述 |
awk '{sub(/[ \t]+$/,"");print $0}' |
# 将每一行中的前导和拖尾的空白字符删除
sed 's/^[ \t]*//;s/[ \t]*$//' |
awk '{sub(/^[ \t]+/,"");sub(/[ \t]+$/,"");print $0}' |
# 在每一行开头处插入5个空格(使全文向右移动5个字符的位置)
sed 's/^/ /' |
awk '{printf(" %s\n",$0)}' |
# 以79个字符为宽度,将所有文本右对齐
# 78个字符外加最后的一个空格
sed -e :a -e 's/^.\{1,78\}$/ &/;ta' |
awk '{printf("%79s\n",$0)}' |
# 以79个字符为宽度,使所有文本居中。在方法1中,为了让文本居中每一行的前
# 头和后头都填充了空格。 在方法2中,在居中文本的过程中只在文本的前面填充
# 空格,并且最终这些空格将有一半会被删除。此外每一行的后头并未填充空格。
sed -e :a -e 's/^.\{1,77\}$/ & /;ta' # 方法1 sed -e :a -e 's/^.\{1,77\}$/ &/;ta' -e 's/\( *\)\1/\1/' # 方法2 |
awk '{for(i=0;i<39-length($0)/2;i++)printf(" ");printf("%s\n",$0)}' #相当于上面的方法二 |
# 在每一行中查找字串“foo”,并将找到的“foo”替换为“bar”
sed 's/foo/bar/' # 只替换每一行中的第一个“foo”字串 sed 's/foo/bar/4' # 只替换每一行中的第四个“foo”字串 sed 's/foo/bar/g' # 将每一行中的所有“foo”都换成“bar” sed 's/\(.*\)foo\(.*foo\)/\1bar\2/' # 替换倒数第二个“foo” sed 's/\(.*\)foo/\1bar/' # 替换最后一个“foo” |
awk '{gsub(/foo/,"bar");print $0}' # 将每一行中的所有“foo”都换成“bar” |
# 只在行中出现字串“baz”的情况下将“foo”替换成“bar”
sed '/baz/s/foo/bar/g' |
awk '{if(/baz/)gsub(/foo/,"bar");print $0}' |
# 将“foo”替换成“bar”,并且只在行中未出现字串“baz”的情况下替换
sed '/baz/!s/foo/bar/g' |
awk '{if(/baz$/)gsub(/foo/,"bar");print $0}' |
# 不管是“scarlet”“ruby”还是“puce”,一律换成“red”
sed 's/scarlet/red/g;s/ruby/red/g;s/puce/red/g' #对多数的sed都有效 gsed 's/scarlet\|ruby\|puce/red/g' # 只对GNU sed有效 |
awk '{gsub(/scarlet|ruby|puce/,"red");print $0}' |
# 倒置所有行,第一行成为最后一行,依次类推(模拟“tac”)。
# 由于某些原因,使用下面命令时HHsed v1.5会将文件中的空行删除
sed '1!G;h;$!d' # 方法1 sed -n '1!G;h;$p' # 方法2 |
awk '{A[i++]=$0}END{for(j=i-1;j>=0;j--)print A[j]}' |
# 将行中的字符逆序排列,第一个字成为最后一字,……(模拟“rev”)
sed '/\n/!G;s/\(.\)\(.*\n\)/&\2\1/;//D;s/.//' |
awk '{for(i=length($0);i>0;i--)printf("%s",substr($0,i,1));printf("\n")}' |
# 将每两行连接成一行(类似“paste”)
sed '$!N;s/\n/ /' |
awk '{f=!f;if(f)printf("%s",$0);else printf(" %s\n",$0)}' |
# 如果当前行以反斜杠“\”结束,则将下一行并到当前行末尾
# 并去掉原来行尾的反斜杠
sed -e :a -e '/\\$/N; s/\\\n//; ta' |
awk '{if(/\\$/)printf("%s",substr($0,0,length($0)-1));else printf("%s\n",$0)}' |
# 如果当前行以等号开头,将当前行并到上一行末尾
# 并以单个空格代替原来行头的“=”
sed -e :a -e '$!N;s/\n=/ /;ta' -e 'P;D' |
awk '{if(/^=/)printf(" %s",substr($0,2));else printf("%s%s",a,$0);a="\n"}END{printf("\n")}' |
# 为数字字串增加逗号分隔符号,将“1234567”改为“1,234,567”
gsed ':a;s/\B[0-9]\{3\}\>/,&/;ta' # GNU sed sed -e :a -e 's/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/;ta' # 其他sed |
#awk的正则没有后向匹配和引用,搞的比较狼狈,呵呵。 awk '{while(match($0,/[0-9][0-9][0-9][0-9]+/)){$0=sprintf("%s,%s",substr($0,0,RSTART+RLENGTH-4),substr($0,RSTART+RLENGTH-3))}print $0}' |
# 为带有小数点和负号的数值增加逗号分隔符(GNU sed)
gsed -r ':a;s/(^|[^0-9.])([0-9]+)([0-9]{3})/\1\2,\3/g;ta' |
#和上例差不多 awk '{while(match($0,/[^\.0-9][0-9][0-9][0-9][0-9]+/)){$0=sprintf("%s,%s",substr($0,0,RSTART+RLENGTH-4),substr($0,RSTART+RLENGTH-3))}print $0}' |
# 在每5行后增加一空白行 (在第5,10,15,20,等行后增加一空白行)
gsed '0~5G' # 只对GNU sed有效 sed 'n;n;n;n;G;' # 其他sed |
awk '{print $0;i++;if(i==5){printf("\n");i=0}}' |
选择性地显示特定行:
——–
# 显示文件中的前10行 (模拟“head”的行为)
sed 10q |
awk '{print;if(NR==10)exit}' |
# 显示文件中的第一行 (模拟“head -1”命令)
sed q |
awk '{print;exit}' |
# 显示文件中的最后10行 (模拟“tail”)
sed -e :a -e '$q;N;11,$D;ba' |
#用awk干这个有点亏,得全文缓存,对于大文件肯定很慢 awk '{A[NR]=$0}END{for(i=NR-9;i<=NR;i++)print A[i]}' |
# 显示文件中的最后2行(模拟“tail -2”命令)
sed '$!N;$!D' |
awk '{A[NR]=$0}END{for(i=NR-1;i<=NR;i++)print A[i]}' |
# 显示文件中的最后一行(模拟“tail -1”)
sed '$!d' # 方法1 sed -n '$p' # 方法2 |
#这个比较好办,只存最后一行了。 awk '{A=$0}END{print A}' |
# 显示文件中的倒数第二行
sed -e '$!{h;d;}' -e x # 当文件中只有一行时,输出空行 sed -e '1{$q;}' -e '$!{h;d;}' -e x # 当文件中只有一行时,显示该行 sed -e '1{$d;}' -e '$!{h;d;}' -e x # 当文件中只有一行时,不输出 |
#存两行呗(当文件中只有一行时,输出空行) awk '{B=A;A=$0}END{print B}' |
# 只显示匹配正则表达式的行(模拟“grep”)
sed -n '/regexp/p' # 方法1 sed '/regexp/!d' # 方法2 |
awk '/regexp/{print}' |
# 只显示“不”匹配正则表达式的行(模拟“grep -v”)
sed -n '/regexp/!p' # 方法1,与前面的命令相对应 sed '/regexp/d' # 方法2,类似的语法 |
awk '!/regexp/{print}' |
# 查找“regexp”并将匹配行的上一行显示出来,但并不显示匹配行
sed -n '/regexp/{g;1!p;};h' |
awk '/regexp/{print A}{A=$0}' |
# 查找“regexp”并将匹配行的下一行显示出来,但并不显示匹配行
sed -n '/regexp/{n;p;}' |
awk '{if(A)print;A=0}/regexp/{A=1}' |
# 显示包含“regexp”的行及其前后行,并在第一行之前加上“regexp”所在行的行号 (类似“grep -A1 -B1”)
sed -n -e '/regexp/{=;x;1!p;g;$!N;p;D;}' -e h |
awk '{if(F)print;F=0}/regexp/{print NR;print b;print;F=1}{b=$0}' |
# 显示包含“AAA”、“BBB”和“CCC”的行(任意次序)
sed '/AAA/!d; /BBB/!d; /CCC/!d' # 字串的次序不影响结果 |
awk '{if(match($0,/AAA/) && match($0,/BBB/) && match($0,/CCC/))print}' |
# 显示包含“AAA”、“BBB”和“CCC”的行(固定次序)
sed '/AAA.*BBB.*CCC/!d' |
awk '{if(match($0,/AAA.*BBB.*CCC/))print}' |
# 显示包含“AAA”“BBB”或“CCC”的行 (模拟“egrep”)
sed -e '/AAA/b' -e '/BBB/b' -e '/CCC/b' -e d # 多数sed gsed '/AAA\|BBB\|CCC/!d' # 对GNU sed有效 |
awk '/AAA/{print;next}/BBB/{print;next}/CCC/{print}' awk '/AAA|BBB|CCC/{print}' |
# 显示包含“AAA”的段落 (段落间以空行分隔)
# HHsed v1.5 必须在“x;”后加入“G;”,接下来的3个脚本都是这样
sed -e '/./{H;$!d;}' -e 'x;/AAA/!d;' |
awk 'BEGIN{RS=""}/AAA/{print}' awk -vRS= '/AAA/{print}' |
# 显示包含“AAA”“BBB”和“CCC”三个字串的段落 (任意次序)
sed -e '/./{H;$!d;}' -e 'x;/AAA/!d;/BBB/!d;/CCC/!d' |
awk -vRS= '{if(match($0,/AAA/) && match($0,/BBB/) && match($0,/CCC/))print}' |
# 显示包含“AAA”、“BBB”、“CCC”三者中任一字串的段落 (任意次序)
sed -e '/./{H;$!d;}' -e 'x;/AAA/b' -e '/BBB/b' -e '/CCC/b' -e d gsed '/./{H;$!d;};x;/AAA\|BBB\|CCC/b;d' # 只对GNU sed有效 |
awk -vRS= '/AAA|BBB|CCC/{print "";print}' |
# 显示包含65个或以上字符的行
sed -n '/^.\{65\}/p' |
cat ll.txt | awk '{if(length($0)>=65)print}' |
# 显示包含65个以下字符的行
sed -n '/^.\{65\}/!p' # 方法1,与上面的脚本相对应 sed '/^.\{65\}/d' # 方法2,更简便一点的方法 |
awk '{if(length($0)<=65)print}' |
# 显示部分文本——从包含正则表达式的行开始到最后一行结束
sed -n '/regexp/,$p' |
awk '/regexp/{F=1}{if(F)print}' |
# 显示部分文本——指定行号范围(从第8至第12行,含8和12行)
sed -n '8,12p' # 方法1 sed '8,12!d' # 方法2 |
awk '{if(NR>=8 && NR<12)print}' |
# 显示第52行
sed -n '52p' # 方法1 sed '52!d' # 方法2 sed '52q;d' # 方法3, 处理大文件时更有效率 |
awk '{if(NR==52){print;exit}}' |
# 从第3行开始,每7行显示一次
gsed -n '3~7p' # 只对GNU sed有效 sed -n '3,${p;n;n;n;n;n;n;}' # 其他sed |
awk '{if(NR==3)F=1}{if(F){i++;if(i%7==1)print}}' |
# 显示两个正则表达式之间的文本(包含)
sed -n '/Iowa/,/Montana/p' # 区分大小写方式 |
awk '/Iowa/{F=1}{if(F)print}/Montana/{F=0}' |
选择性地删除特定行:
——–
# 显示通篇文档,除了两个正则表达式之间的内容
sed '/Iowa/,/Montana/d' |
awk '/Iowa/{F=1}{if(!F)print}/Montana/{F=0}' |
# 删除文件中相邻的重复行(模拟“uniq”)
# 只保留重复行中的第一行,其他行删除
sed '$!N; /^\(.*\)\n\1$/!P; D' |
awk '{if($0!=B)print;B=$0}' |
# 删除文件中的重复行,不管有无相邻。注意hold space所能支持的缓存大小,或者使用GNU sed。
sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P' #bones7456注:我这里此命令并不能正常工作 |
awk '{if(!($0 in B))print;B[$0]=1}' |
# 删除除重复行外的所有行(模拟“uniq -d”)
sed '$!N; s/^\(.*\)\n\1$/\1/; t; D' |
awk '{if($0==B && $0!=l){print;l=$0}B=$0}' |
# 删除文件中开头的10行
sed '1,10d' |
awk '{if(NR>10)print}' |
# 删除文件中的最后一行
sed '$d' |
#awk在过程中并不知道文件一共有几行,所以只能通篇缓存,大文件可能不适合,下面两个也一样 awk '{B[NR]=$0}END{for(i=0;i<=NR-1;i++)print B[i]}' |
# 删除文件中的最后两行
sed 'N;$!P;$!D;$d' |
awk '{B[NR]=$0}END{for(i=0;i<=NR-2;i++)print B[i]}' |
# 删除文件中的最后10行
sed -e :a -e '$d;N;2,10ba' -e 'P;D' # 方法1 sed -n -e :a -e '1,10!{P;N;D;};N;ba' # 方法2 |
awk '{B[NR]=$0}END{for(i=0;i<=NR-10;i++)print B[i]}' |
# 删除8的倍数行
gsed '0~8d' # 只对GNU sed有效 sed 'n;n;n;n;n;n;n;d;' # 其他sed |
awk '{if(NR%8!=0)print}' |head |
# 删除匹配式样的行
sed '/pattern/d' # 删除含pattern的行。当然pattern可以换成任何有效的正则表达式 |
awk '{if(!match($0,/pattern/))print}' |
# 删除文件中的所有空行(与“grep ‘.’ ”效果相同)
sed '/^$/d' # 方法1 sed '/./!d' # 方法2 |
awk '{if(!match($0,/^$/))print}' |
# 只保留多个相邻空行的第一行。并且删除文件顶部和尾部的空行。
# (模拟“cat -s”)
sed '/./,/^$/!d' #方法1,删除文件顶部的空行,允许尾部保留一空行 sed '/^$/N;/\n$/D' #方法2,允许顶部保留一空行,尾部不留空行 |
awk '{if(!match($0,/^$/)){print;F=1}else{if(F)print;F=0}}' #同上面的方法2 |
# 只保留多个相邻空行的前两行。
sed '/^$/N;/\n$/N;//D' |
awk '{if(!match($0,/^$/)){print;F=0}else{if(F<2)print;F++}}' |
# 删除文件顶部的所有空行
sed '/./,$!d' |
awk '{if(F || !match($0,/^$/)){print;F=1}}' |
# 删除文件尾部的所有空行
sed -e :a -e '/^\n*$/{$d;N;ba' -e '}' # 对所有sed有效 sed -e :a -e '/^\n*$/N;/\n$/ba' # 同上,但只对 gsed 3.02.*有效 |
awk '/^.+$/{for(i=l;i<NR-1;i++)print "";print;l=NR}' |
# 删除每个段落的最后一行
sed -n '/^$/{p;h;};/./{x;/./p;}' |
#很长,很ugly,应该有更好的办法 awk -vRS= '{B=$0;l=0;f=1;while(match(B,/\n/)>0){print substr(B,l,RSTART-l-f);l=RSTART;sub(/\n/,"",B);f=0};print ""}' |
特殊应用:
——–
# 移除手册页(man page)中的nroff标记。在Unix System V或bash shell下使
# 用’echo’命令时可能需要加上 -e 选项。
sed "s/.`echo \\\b`//g" # 外层的双括号是必须的(Unix环境) sed 's/.^H//g' # 在bash或tcsh中, 按 Ctrl-V 再按 Ctrl-H sed 's/.\x08//g' # sed 1.5,GNU sed,ssed所使用的十六进制的表示方法 |
awk '{gsub(/.\x08/,"",$0);print}' |
# 提取新闻组或 e-mail 的邮件头
sed '/^$/q' # 删除第一行空行后的所有内容 |
awk '{print}/^$/{exit}' |
# 提取新闻组或 e-mail 的正文部分
sed '1,/^$/d' # 删除第一行空行之前的所有内容 |
awk '{if(F)print}/^$/{F=1}' |
# 从邮件头提取“Subject”(标题栏字段),并移除开头的“Subject:”字样
sed '/^Subject: */!d; s///;q' |
awk '/^Subject:.*/{print substr($0,10)}/^$/{exit}' |
# 从邮件头获得回复地址
sed '/^Reply-To:/q; /^From:/h; /./d;g;q' |
#好像是输出第一个Reply-To:开头的行?From是干啥用的?不清楚规则。。 awk '/^Reply-To:.*/{print;exit}/^$/{exit}' |
# 获取邮件地址。在上一个脚本所产生的那一行邮件头的基础上进一步的将非电邮地址的部分剃除。(见上一脚本)
sed 's/ *(.*)//; s/>.*//; s/.*[:<] *//' |
#取尖括号里的东西吧? awk -F'[<>]+' '{print $2}' |
# 在每一行开头加上一个尖括号和空格(引用信息)
sed 's/^/> /' |
awk '{print "> " $0}' |
# 将每一行开头处的尖括号和空格删除(解除引用)
sed 's/^> //' |
awk '/^> /{print substr($0,3)}' |
# 移除大部分的HTML标签(包括跨行标签)
sed -e :a -e 's/<[^>]*>//g;/</N;//ba' |
awk '{gsub(/<[^>]*>/,"",$0);print}' |
# 将分成多卷的uuencode文件解码。移除文件头信息,只保留uuencode编码部分。
# 文件必须以特定顺序传给sed。下面第一种版本的脚本可以直接在命令行下输入;
# 第二种版本则可以放入一个带执行权限的shell脚本中。(由Rahul Dhesi的一
# 个脚本修改而来。)
sed '/^end/,/^begin/d' file1 file2 ... fileX | uudecode # vers. 1 sed '/^end/,/^begin/d' "$@" | uudecode # vers. 2 |
#我不想装个uudecode验证,大致写个吧 awk '/^end/{F=0}{if(F)print}/^begin/{F=1}' file1 file2 ... fileX |
# 将文件中的段落以字母顺序排序。段落间以(一行或多行)空行分隔。GNU sed使用
# 字元“\v”来表示垂直制表符,这里用它来作为换行符的占位符——当然你也可以
# 用其他未在文件中使用的字符来代替它。
sed '/./{H;d;};x;s/\n/={NL}=/g' file | sort | sed '1s/={NL}=//;s/={NL}=/\n/g' gsed '/./{H;d};x;y/\n/\v/' file | sort | sed '1s/\v//;y/\v/\n/' |
awk -vRS= '{gsub(/\n/,"\v",$0);print}' ll.txt | sort | awk '{gsub(/\v/,"\n",$0);print;print ""}' |
# 分别压缩每个.TXT文件,压缩后删除原来的文件并将压缩后的.ZIP文件
# 命名为与原来相同的名字(只是扩展名不同)。(DOS环境:“dir /b”
# 显示不带路径的文件名)。
echo @echo off >zipup.bat dir /b *.txt | sed "s/^\(.*\)\.TXT/pkzip -mo \1 \1.TXT/" >>zipup.bat |
DOS环境再次略过,而且我觉得这里用bash的参数 ${i%.TXT}.zip 替换更帅。 |
下面的一些SED说明略过,需要的朋友自行查看原文。
python 内建函数
说明:本文内容全部出自python官方文档,但是会有自己的理解,并非单纯的翻译。文章较长,如有错误之处,还请大家指正。
abs(x)
返回x的绝对值;当x是复数时,返回x的模。没错,python内建支持复数,见下面的complex()函数。
all(iterable)
当iterable里的每项都为真时,才返回真,等效于:
def all(iterable): for element in iterable: if not element: return False return True |
any(iterable)
只要iterable里有一项为真,就返回真,等效于:
def any(iterable): for element in iterable: if element: return True return False |
basestring()
这是 str 和 unicode 的抽象类,它不能被调用也不能被实例化,但是可以用在 isinstance 函数里进行判断,isinstance(obj, basestring) 等效于 isinstance(obj, (str, unicode)).
>>> isinstance(123, basestring) False >>> isinstance("123", basestring) True >>> isinstance(u"一二三", basestring) True |
bin(x)
如果x是一个整数,则返回一个与x等值的二进制python表达式;如果x不是一个整数类型,则x的类需要有一个可以返回一个整数的__index__()函数。
bool([x])
返回一个布尔型的值,如果x为False或者没传x参数的时候返回False,否则返回True。
callable(object)
判断object是否可调用,如果object是 函数、类、或者含有__call__()的类对象的话,将返回True。
chr(i)
返回一个单个字符的字符串,此字符的ascii码值为i(0<=i<=255),此函数是ord函数的反函数。如果参数大于255而想得到一个unicode字符的话,需要使用unichr()
classmethod(function)
返回一个类的方法(类的方法有别于实例的方法,是不需要实例化也可以通过类名访问的方法),定义一个类的方法需要用这样的形式:
class C: @classmethod def f(cls, arg1, arg2, ...): ... |
cmp(x, y)
比较两个对象x和y。如果x小于y,返回负数;大于返回正数;等于返回0。
compile(source, filename, mode[, flags[, dont_inherit]])
把source字符串编译成一个AST对象,暂时用不到,先略过。
complex([real[, imag]])
用传入的实部和虚部创建一个复数对象。
delattr(object, name)
删除对象的属性,相当于 del object.name ,可以和setattr配合使用。
dict([arg])
建立一个新的字典型数据,可以从参数里获取数据。
>>> dict({"a":"b","c":"d"}) {'a': 'b', 'c': 'd'} |
dir([object])
如果不加参数,返回当前执行环境下的变量名的列表。
如果加了object参数,则会根据复杂的规则得到object的属性名列表,需要注意的是,当object定义了__dir__()或者 __getattr__()方法时,返回的结果并不一定正确。
示例:
>>> dir() ['__builtins__', '__doc__', '__name__', '__package__'] >>> t=[1,2] >>> dir() ['__builtins__', '__doc__', '__name__', '__package__', 't'] >>> dir(t) ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] |
divmod(a, b)
通常返回a和b的商和余数组成的元组: (a // b, a % b)。参数不能是复数。
enumerate(sequence[, start=0])
返回一个列举后的对象,sequence要支持迭代。返回的对象支持next()方法,此方法依次返回一个从start开始增长的序数和sequence里的元素组成的元组。看以下的例子:
>>> enu=enumerate(['Spring', 'Summer', 'Fall', 'Winter']) >>> enu.next() (0, 'Spring') >>> enu.next() (1, 'Summer') >>> for i, season in enu: ... print i, season ... 2 Fall 3 Winter |
eval(expression[, globals[, locals]])
执行expression表达式,可以用globals和locals来限制expression能访问的变量。
值得注意的是,expression不仅可以是明文的字符串,还可以是compile()函数返回的代码对象。
execfile(filename[, globals[, locals]])
此函数类似exec表达式。只是从文件里读取表达式。它和import的区别在于,execfile会无条件地读取文件,而且不会生成新的模块。
globals和locals的用法和上面的eval同理。
file(filename[, mode[, bufsize]])
File类型的构造函数,参数的作用和下面提到的open()函数是一样的。
值得注意的是,open()函数更适合于打开一个文件,而file函数更适用于类型测试,例如: isinstance(f, file)
filter(function, iterable)
构造一个function(iterable)为true的list。当然iterable为字符串或者tuple的时候,返回的类型也是字符串或者tuple,否则返回list。
>>> filter(lambda c: c in 'abc', 'abcdcba') 'abccba' >>> filter(lambda i: i < 3, (1, 2, 3, 2, 1)) (1, 2, 2, 1) >>> filter(lambda i: i < 3, [1, 2, 4, 2, 1]) [1, 2, 2, 1] |
如果function为None,则iterable为false的元素将被剔除。也就是说,function不为None的时候,filter(function, iterable)
等效于[item for item in iterable if function(item)]
,否则等效于[item for item in iterable if item]
float([x])
传入一个字符串或者整数或者float,返回一个float数据。
format(value[, format_spec])
根据format_spec格式化输出value的值,实际上只是调用了value.__format__(format_spec)
,很多内建类型都有标准的输出函数。
frozenset([iterable])
由iterable创建一个frozenset对象,frozenset是set的一个子类,它和set的区别在于它不支持某些可以修改set的操作,例如:add、remove、pop、clear等。可以理解为一个set的常量。
getattr(object, name[, default])
获得对象的属性值,name必须是字符串,如果name是object的属性,则getattr(x, 'foobar')
相当于x.foobar
,如果name不是object的属性,则返回default,如果没有default就会抛出AttributeError意外。
globals()
返回一个包含当前“全局符号表”的dict。
hasattr(object, name)
参数是一个对象和一个字符串,如果object对象有名为name的属性,则返回True,否则返回False。在执行getattr(object, name)
之前,可以以此来检测属性的存在性。
hash(object)
如果可能的话,返回object的hash值,hash值是一个整型的数字,用于快速比较两个对象。两个相等的数字型对象将有相同的hash值,比如:
>>> hash(1) == hash(1.0) True |
help([object])
调用内建的帮助系统(交互式)。
如果省略参数,则会进入帮助控制台,出现help>
的提示符,输入相应内容就可以查看相应的帮助。
如果参数是字符串,则在模块名、函数名、类名、方法名、关键字及文档和帮助主题里搜索此字符串,并显示。
如果参数是其他类型的对象,则显示此对象的帮助信息。
hex(x)
将任何长度的整型数字转化为16进制的字符串。
如果转换浮点数为16进制,则须使用float.hex()
方法。
id(object)
返回一个整型(或者长整型)的object的唯一标识符。注意:两个生命周期没有交叉的对象,也许会返回同一个标识符。(在CPython里,其实就是返回object的地址)
input([prompt])
等效于 eval(raw_input(prompt))
返回用户输入的python表达式的值,一句话:注意安全。
int([x[, base]])
根据x的值返回一个整数,x可以是一个含有数字信息的字符串或者数字类型(整型/浮点型/长整型/复数)。可选的base参数,代表进制,可以是2~36之间的数字或者0。如果base的值为0,将会根据x的值选取适当的基数。如果不提供任何参数,将返回0。
isinstance(object, classinfo)
如果object是classinfo或者classinfo的子类的实例,或者是和classinfo同类的对象,则返回True。classinfo也可以是类或者对象组成的tuple,这时候,object只要是classinfo里的一者就返回True:
>>> isinstance(1, (int,float) ) True >>> isinstance(1.0, (int,float) ) True >>> isinstance("1.0", (int,float) ) False |
issubclass(class, classinfo)
如果class是classinfo的直接或者间接之类的话,就返回True。一个类也被视为自己的之类。同上例,classinfo也可以是tuple。
iter(o[, sentinel])
返回一个“迭代器”对象,根据sentinel的设置不停地对第一个参数进线取值。当忽略第二个参数时,o必须是一个支持__iter__()或者__getitem__()方法的对象,否则将会抛出TypeError例外。如果提供了sentinel参数,o必须是一个可调用的对象,这时将不停地调用此方法,并返回迭代器的项,知道返回的值等于sentinel为止,这时将抛出StopIteration。
第二种形式特别适用于打开一个文件,一行行处理文本,知道遇到特定的行:
with open("mydata.txt") as fp: for line in iter(fp.readline, "STOP"): process_line(line) |
len(s)
返回s的长度,也就是项数。自建会调用__len__函数取值。
list([iterable])
返回一个含有所有iterable中的元素的list对象。如果参数为空,则返回空的list。
locals()
和上面的globals()
对应,返回一个包含当前“局部符号表”的dict。在函数里调用的时候,将排除在类中声明的变量。
long([x[, base]])
根据字符串或者数字类型的参数,返回一个长整型的数字。参数的含义和上面的int
类似。
map(function, iterable, …)
对iterable里的每项执行function函数,并把结果以一个list的形式返回。如果有3个以上的参数,则后面的参数也需要是可迭代的,map会把额外的参数传给function,例如,这样可以把两个tuple一一相加得到一个list:
>>> map(lambda x, add: x + add, (2, 4, 5), (1, 3, 6) ) [3, 7, 11] |
如迭代器的长度不一致,缺失的项将用None代替:
>>> map(lambda x, add: x + add, (2, 4, 5), (1, 3)) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 1, in <lambda> TypeError: unsupported operand type(s) for +: 'int' and 'NoneType' |
如果function为None,将用 identity function 代替(好像就是直入直出)。
max(iterable[, args…][, key])
如果只给一个参数,就返回iterable里最大的项;如果是多个参数的话,则返回参数里最大的项。
>>> max("abcd") 'd' >>> max(1, 2, 3) 3 |
额外的key参数,是用于比较的函数,比如,下面这个可以得到各项除3的余数最大的一个:
>>> max([1, 2, 3, 4], key=lambda x: x % 3) 2 |
min(iterable[, args…][, key])
同上,求最小值。
next(iterator[, default])
依次返回迭代器iterator的项。当iterator没有更多的项时,如果有default参数,则返回default,否则抛出StopIteration例外。
>>> a = iter(range(3)) >>> next(a) 0 >>> next(a) 1 >>> next(a) 2 >>> next(a) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration >>> next(a, "No More Item...") 'No More Item...' |
object()
返回一个空的对象,但是此对象会有一些公有的属性:
>>> o = object() >>> dir(o) ['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] |
oct(x)
将任意精度的十进制整数x转换成八进制。
open(filename[, mode[, bufsize]])
打开文件,返回一个文件对象,如果文件打不开,将抛出IOError
错误。
filename参数,是要打开的文件名。
mode参数是打开方式,通常是'r'
表示读,'w'
表示写(如果已存在则会覆盖),'a'
表示追加。缺省为'r'
。另外,缺省使用的是文本模式,会把'\n'
转成系统相关的换行符,如果要避免这个引起的问题,需要在各个模式后面加一个'b'
表示使用二进制模式。另外还有些’+uU’之类的模式,不常用,也就不介绍了吧。
可选的bufsize参数表示缓冲区的大小。0表示不缓冲,1表示行缓冲,其他正数表示近视的缓冲区字节数,负数表示使用系统默认值。默认是0。
ord(c)
给定一个长度为1的字符串或者unicode字符,返回该字符的ascii码或者unicode码,前一种情况是chr()
的反函数,后一种情况是unichr()
的反函数。
pow(x, y[, z])
返回x的y次方,也就是x**y
。如果有z的话,返回x的y次方除z得到的余数(这个比pow(x, y) % z
更高效,这点可以看我写的欧拉工程第48题的代码,之前很慢,现在很快)。
如果第二个参数是负数的话,将返回浮点型的数据,而且这个时候不能有z。
print([object, …][, sep=’ ‘][, end=’\n’][, file=sys.stdout])
输出一个或多个object到file,中间用sep间隔,并在结尾加上end。
后3个参数如果给出的话,必须用keyword arguments的形式,也就是必须指定参数名,否则将一概被视为object的一部分而被输出。
需要注意的是和python 2.6前的print关键字的区别。
property([fget[, fset[, fdel[, doc]]]])
返回一个属性,参数分别是获取、设置和删除的函数外加doc string,看例子吧:
>>> class C(object): ... def __init__(self): ... self._x = None ... def getx(self): ... print "OK. give you:", self._x ... return self._x ... def setx(self, value): ... print "Now x is:", value ... self._x = value ... def delx(self): ... del self._x ... x = property(getx, setx, delx, "I'm the 'x' property.") ... >>> a = C() >>> a.x = 123 Now x is: 123 >>> print a.x OK. give you: 123 123 >>> help(a.x) OK. give you: 123 >>> help(C.x) #这里可以看到I'm the 'x' property. |
range([start], stop[, step])
方便地产生一个包含等差数列的list,如果忽略start,则默认为0;如果忽略step,则默认为1。经常被用于for循环里。注意返回的结果并不包含stop。
raw_input([prompt])
从输入读入一行字符串,结尾的回车将被去掉。如果提供了prompt参数,将做为输入的提示符。
reduce(function, iterable[, initializer])
将两个参数的function函数循环应用到迭代器的各项,例如reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])
相当于((((1+2)+3)+4)+5)
。如果提供了可选的initializer参数,则会将它放在迭代器的前面进行运算。
reload(module)
重新加载之前已经导入的模块。当你在设计一个模块,并用外部编辑器更新了它的代码时,可以用reload重新导入此模块,来验证模块的正确性。
reload执行时候的具体细节这里就不描述了。
repr(object)
返回一个尽量包含object的信息的字符串,其实交互式python解释器,在输入一个对象回车的时候,就是返回对象的repr值。
对于很多常见的对象,返回的值都尽可能地使得能够被eval
解释并返回对象本身;另外的就尽量包含所在的域信息和类型或者地址等。
一个类可以通过__repr__()方法自定义repr的返回值。
reversed(seq)
返回一个倒序的迭代器。seq要么支持 __reversed__() 方法,要么支持取项的操作(也就是支持__len__()方法和从0开始的整数值的__getitem__()方法)。
例子:
>>> reversed(range(5)) <listreverseiterator object at 0x80a658c> >>> [i for i in reversed(range(5))] [4, 3, 2, 1, 0] |
round(x[, n])
将浮点数x四舍五入取整到小数点后n位小数。n的默认值是0,也就是取整。
set([iterable])
由迭代器iterable返回一个集合对象,集合中的元素是随机顺序,但是不重复的。此函数在去掉列表的重复项的时候,特别有用:
>>> l = [1, 2, 3, 2, 4, 3] >>> set(l) set([1, 2, 3, 4]) >>> list(set(l)) [1, 2, 3, 4] >>> ''.join(set("hello")) 'helo' |
setattr(object, name, value)
此函数和getattr()
配合使用,setattr(x, 'foobar', 123)
相当于x.foobar = 123
。
slice([start], stop[, step])
返回一个分片对象,分片对象就只包含了start, stop, step这3个信息,它在python内部和一些第三方库中广泛被使用,其实类似a[1:3]这样的操作也会生成分片对象。如果省略start和step,将默认为None。
可以看到下面两者其实是等效的:
>>> range(5)[slice(1, 4, 2)] [1, 3] >>> range(5)[1:4:2] [1, 3] |
sorted(iterable[, cmp[, key[, reverse]]])
返回一个排序后的列表,用于排序的元素来自iterable,后面的参数控制排序的过程。
cmp是自定义的比较函数,接受两个参数,返回负数表示第一个参数较小,返回0表示两者一样大,返回正数表示第一个参数较大。
key可以理解为每个参数的求值函数。如果提供了key,则在比较前,先对每个先用key进线求职,对结果再进行排序,但是返回的排序后的结果还是之前的值。
reverse如果是True,则按降序排列,默认是从小到大的升序。
看例子:
#正常的排序 >>> sorted([1, 5, 3, 4, 6]) [1, 3, 4, 5, 6] #倒序 >>> sorted([1, 5, 3, 4, 6], reverse=True) [6, 5, 4, 3, 1] #提供了key,结果是除3的余数谁最小,谁就排前 >>> sorted([1, 5, 3, 4, 6], key=lambda x: x%3) [3, 6, 1, 4, 5] #用cmp实现的版本 >>> sorted([1, 5, 3, 4, 6], cmp=lambda x,y: x%3 - y%3) [3, 6, 1, 4, 5] |
值得注意的是,虽然cmp和key都可以实现上面的除3余数排列,但是因为cmp要对每次比较的两个元素都调用一次函数,所以,效率不如key来得高。
staticmethod(function)
返回一个静态方法function
要声明一个静态方法,需要使用如下的语法:
class C: @staticmethod def f(arg1, arg2, ...): ... |
静态方法可以被类本身调用(例如:C.f()
)也可以被类的对象调用(例如:C().f()
)。
str([object])
返回一个精确可打印的字符串,来说明object。和repr(object)
不同,str(object)
返回的字符串不一定能被eval()执行来得到对象本身,str(object)
的目标只是可打印和可读。
sum(iterable[, start])
对iterable在start做为初值的基础上进行累加。start的默认值为0。
注意此方法不能对字符串进行相加(连接)操作,连接字符串还是用''.join(sequence)
好了。另外,sum(range(n), m)
等价于reduce(operator.add, range(n), m)
,要更精确地对浮点数进行累加,请使用math.fsum()。
super(type[, object-or-type])
返回一个指代type的父类或者兄弟类的对象,可以用这个对象间接地调用父类或者兄弟类的方法。在有复杂的类继承关系结构的时候,会很有用。用到的时候可以自行研究下这文章。
tuple([iterable])
返回一个tuple对象(元组),元素来自iterable。如果省略参数,将返回空的元组。
type(object)
返回object的类型,返回值本身是个“类型对象”。注意,进行类型判断建议使用isinstance()
函数。
>>> type(1) <type 'int'> >>> type(type(1)) <type 'type'> >>> type(1) == int #非常不建议这样的使用方法。 True >>> isinstance(1,int) #建议这样使用。 True |
type(name, bases, dict)
不同于上面那个一个参数的type,这个方法用于快速构造一个类,传入的3个参数将分别转化为所得到的类的__name__,__bases__和__dict__。
例如,下面这两个X是等价的:
>>> class X(object): ... a = 1 ... >>> X = type('X', (object,), dict(a=1)) |
unichr(i)
返回一个单个字符的unicode串,此字符的unicode码值为i。对于Unicode,此函数也是ord()的反函数。i的范围由python解释器的编译环境决定。
unicode([object[, encoding[, errors]]])
返回一个代表object的unicode字符串。
如果给定了encoding和/或errors,将用ascii或者encoding指定的编码对object进行解码,在遇到解码错误的时候,errors的值将影响函数的下一步动作:如果errors的值是'strict'
(默认值),将会抛出ValueError错误;如果errors的值是'ignore'
将会忽略错误,继续解码;如果errors是'replace'
,将使用U+FFFD来替换当前字符。
看个例子,我的utf8环境下:
>>> unicode('我是bones7456', encoding='utf8') u'\u6211\u662fbones7456' >>> print unicode('我是bones7456', encoding='utf8') #可见解码成功 我是bones7456 >>> unicode('我是bones7456') #不指定编码方式,将默认使用ascii解码,失败了。 Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 0: ordinal not in range(128) >>> unicode('我是bones7456', errors='ignore') #忽略失败,可以得到英文数字部分 u'bones7456' >>> unicode('我是bones7456', errors='replace') #替换的话,会加上一堆???哈哈。 u'\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdbones7456' >>> print unicode('我是bones7456', errors='replace') ������bones7456 |
如果没有后面的俩参数,unicode()
的行为类似于str()
,只不过返回的unicode字符串而已。
如果,object对象提供了__unicode__()方法,将调用此方法来返回一个可被用户自定义的unicode串。
vars([object])
如果省略object,vars()
和locals()类似,如果object是模块、类、类的对象或者其他还有__dict__属性的对象的话,就返回它的__dict__。
xrange([start], stop[, step])
此函数和range()非常类似,但是返回的不是一个列表,而是一个xrange对象。xrange对象在被引用时,也能生成列表的各项,但是这些项不是同时存在于内存里的。xrange和range比的优势是更小巧,更快。
zip([iterable, …])
哈,说到这个函数,我还给python官方文档提过一个bug,因为之前版本的文档的示例代码有点小问题,前因后果可以看这里。
zip函数返回一个元组的列表,第i个元组,就包含了每个iterable的第i项。如果参数的各iterable不一样长,会别截取到最短的值,这个值也就是结果列表的长度。
然后,zip内如果有个 *
开头,将会执行逆运算(unzip),示例:
>>> x = [1, 2, 3] >>> y = [4, 5, 6] >>> zipped = zip(x, y) >>> zipped [(1, 4), (2, 5), (3, 6)] >>> x2, y2 = zip(*zipped) >>> x == list(x2) and y == list(y2) True |
__import__(name[, globals[, locals[, fromlist[, level]]]])
此函数被import语句调用。代码中很少会用到这个函数,除非你要import的模块名是运行时才可知的。就不详述了。
我架设的ubuntu源
9月19日,是2009年的软件自由日,在这个比较特殊的日子,我要送给广大ubuntu爱好者一份礼物──一个新的ubuntu源。
正如我刚才的那篇文章所说,这个源的特点就是能保证全和新。至于速度还要看大家的测试结果(应该不会太差)。
这个源是在杭州电信的,百兆共享带宽,不知道网通用户的速度如何。
希望在9.10发布的时候,就能够加到ubuntu官方源列表里。这样用起来就更方便了。
其他就先不说了,大家可以通过这几个域名访问:
http://ubuntu.srt.cn/
http://ubuntu.hzlug.org/
http://u.srt.cn/
谷歌音乐下载器
之前有很多下载baidu mp3的程序,有bash的,java的,python的,其中也包括我这个.
但是baidu的歌曲都是用程序收集自网络的,所以排行榜的歌曲质量就没有保障了,下载下来的歌曲ID3信息可谓一塌糊涂,而且还可能下载到网友自己翻唱的歌或者其他杂七杂八的东西,严重影响我们的听觉神经.
而谷歌(不是google)最近推出了谷歌音乐搜索,联合top100,也推出了类似百度榜单的音乐排行榜.但不同于百度的是,谷歌里的歌曲都是收集整理过的,不会有死链,质量也很不错,而且,对于最终非商业用途的个人用户而言,是不存在版权问题的(但我不确定批量下载下来的有没有版权问题.请用户自行考虑.).
所以我顺势就推出了这个谷歌音乐下载器 .
目前程序还很简陋,没有图形界面,也没有很多可以设置的地方.运行程序只会把”华语新歌”这个榜单的100首歌下载到本地当前目录.所以仅供有兴趣尝鲜的同学测试使用.但是以后,我打算把这个程序做成有图形界面的,可以试听/下载/播放的一个整合工具,哈哈.请大家多多关注吧.
PS:有人说这类工具还是不要发布出来,小范围流传下比较好,因为发布出来以后,很可能遭到google的封杀.这说法其实也有些道理,但是我想想,如果谷歌真的因为这个来封杀我,我也够有面子的,嘿嘿.所以我还是按照Google的Project hosting页面所说的做了: Release early, release often
e-file 根据文件名查询gentoo包的脚本
一直想在gentoo下实现一个类似ubuntu的apt-file的功能,幸好已经有 http://www.portagefilelist.de 这个网站了,就花了2小时写了个小脚本直接到这个站取数据了,感觉效果还可以,先发出来,算是预览版吧,以后会再完善的.
输出格式参考了 eix ,代码如下:
#!/bin/sh #AUTHOR: bones7456 (bones7456<at>gmail<dot>com) #VERSION: 20081120 ##License: GPL #e-file is like apt-file for gentoo, but data is online if [[ $# -ne 1 ]];then echo "Usage: `basename $0` filename" exit 1 fi URL="http://www.portagefilelist.de/index.php/Special:PFLQuery2?file=$1&searchfile=lookup&lookup=file#result" w3m -dump -cols 3000 $URL | awk ' BEGIN{ FLAG=0 FOUND=0 } { if($1=="dir" && $2=="package"){ FLAG=1 next } if($1=="Retrieved" && $2=="from"){ if(FOUND){ for(i in ver){ split(i,ii,/\//) printf("%c[%d;%d;%dm* ",27,2,0,32) printf("%c[0m%s/",27,ii[1]) printf("%c[%d;%d;%dm%s\n",27,2,1,29,ii[2]) printf("%c[%d;%d;%dm\t",27,2,0,32) printf("Available versions:\t%s\n",ver[i]) printf("\tMatched File:\t\t") printf("%c[0m",27) printf("%s\n\n",file[i]) } }else{ print "No matches found." } exit } if(FLAG==1 && NF!=0){ ver[$1 "/" $2]=$NF " " ver[$1 "/" $2] file[$1 "/" $2]=$3 "/" $4 FOUND=1 } }' |
截个图:

e-file截图
脚本下载地址
python多处理器编程
由于上次做的那个一维随机游走程序,虽然简单,但是大数据的时候很费CPU,而且我注意到我的双核处理器始终只有一核是处于满负荷工作,另一个核的性能没有得到发挥.而且我也试过把同样的程序放到一个8核的服务器上运行,结果解题的速度也只是比我本本快那么一点点,估计也只是那服务器的CPU主频(2GHz)比我的1.86GHz略高而已,完全没有发挥出他8核的优势.
所以马上想到了python有没有多处理机的机制,上网google一下,发现由于python是解释型的语言,而Python解释器使用GIL(全局解释器锁)来在内部禁止并行执行,正是这个GIL限制你在多核处理器上同一时间也只能执行一条字节码指令.猜想这个GIL也是当初为了设计解释器方便而搞的吧.而且据说python 3.0 里面已经改进了,默认有了多处理器编程的库了.但是毕竟现在python3.0还没有流行起来,那么现在有没有变通的方法呢?
当然有~不然我就不会写这文章了嘛~
Parallel Python 这个库,正是为了解决我们的问题而设计的,而且它不仅可以多核处理器协同工作,还可以通过网络集群运行呢,嘿嘿.
下面的中文介绍来自这里:
1 简介
PP 是一个Python模块,提供了在SMP(多CPU或多核)和集群(通过网络连接的多台计算机)上并行执行Python代码的机制。轻量级,易于安装,并集成了其他软件。PP也是一个用纯Python代码实现的跨平台,开放源码模块。
2 功能
* 在SMP和集群上并行执行Python代码
* 易于理解和实现的基于工作的并行机制,便于把穿行应用转换成并行的
* 自动构造最佳配置(默认时工作进程数量等同于系统处理器数量)
* 动态处理器分配(允许运行时改变工作处理器数量)
* 函数的工作缓存(透明的缓存机制确保后续调用降低负载)
* 动态负载均衡(任务被动态的分配到各个处理器上)
* 基于SHA的连接加密认证
* 跨平台移植(Windows/Linux/Unix)
* 开放源代码3 开发动机
现代Python程序已经广泛的应用在商业逻辑,数据分析和科学计算等方面。其中广泛应用着SMP(多处理器或多核)和集群(通过网络连接的多台计算机),市场需要并行的执行的Python代码。
在SMP计算机上编写并行程序最简单的方法是使用多线程。尽管如此,使用 ‘thread’ 和 ‘threading’ 模块仍然无法在字节码一级实现并行。因为Python解释器使用GIL(全局解释器锁)来在内部禁止并行执行。这个GIL限制你在SMP机器上同一时间也只能执行一条字节码指令。
PP 模块正是为了解决这个问题而来,提供简单的方式实现并行Python应用。 ppsmp 在内部使用 进程 和 IPC (进程间通信)来组织并行计算。并处理了所有内部的细节和复杂性,你的应用程序只需要提交工作任务并取回结果就可以了。这也是编写并行程序的最简单的方法。
为了更好的实现,所有使用 PP 的软件通过网络来连接和协作。跨平台和动态负载均衡使得 PP 可以轻松组织多平台、异构的集群计算环境。4 安装
任何平台:下载模块压缩包,解压,运行setup脚本:
python setup.py install
Windows:下载和执行安装包。
另外,debian和ubuntu用户,也可以通过apt直接下载安装,包名是 python-pp ,但是由于版本比较老,是 1.5.4 版本的,而最新的是 1.5.6 ,所以官方页面上的示例代码可能运行不了,会出现以下错误提示:
Traceback (most recent call last):
File “testpp.py”, line 46, in
job_server = pp.Server(ppservers=ppservers)
File “/var/lib/python-support/python2.5/pp.py”, line 312, in __init__
raise ValueError(“secret must be set using command-line option or configuration file”)
ValueError: secret must be set using command-line option or configuration file
原因是代码的不兼容性,解决办法就是找到 pp.Server 那行,多加一个参数,如下:
job_server = pp.Server(ppservers=ppservers,secret="") |
由于这个库,包装得不错,所以用起来也比较简单,基本上看了示例代码,就会了,使用方面也就不多介绍了,如果有可能的话,我倒是想写个gentoo的ebuild文件,嘿嘿.
给 iPod 转视频的脚本
由于新买了个 iPod touch,这几天认真钻研了下它的视频格式,搞了个nautilus脚本,使用mencoder做后端.
以后想把某个视频文件转成iPod的格式的话,直接在 nautilus 里面右键点文件-脚本-toIpod 就可以搞定了.
脚本的特点:
* 借助mplayer的强大,支持N多的源格式(已测试: avi rmvb mov flv).
* 支持srt/ass格式的外挂字幕.
* 自动缩放画面比例到适合ipod touch的480*320,如果是 ipod shuffle 之类的话,可以自己修改下脚本.
* 可视化的进度提示
* 转换速度较快
* 默认保存到当前目录,可修改脚本,输出到统一目录,方便管理.会自动加上 _ipod.mp4 的后缀名.
使用方法,保存以下脚本到 ~/.gnome2/nautilus-scripts/toIpod ,并加可执行权限…或者这里下载
PS: 如果压缩出来的字幕有乱码,请参照我以前的文章,建个 ~/.mplayer/mencoder.conf 文件,写上一行 subcp=cp936 就好了.
#!/bin/bash #filename: ~/.gnome2/nautilus-scripts/toIpod #Copyright (c) 2008 bones7456 (bones7456<A>gmail<D>com) #License: GPL #version 20081101 #用于将视频转成 ipod touch / iphone 格式.右击文件使用 SAVEDIR=`pwd` #SAVEDIR="/data/movie" INFILE="$1" PWD=`pwd` CMD="mencoder -of lavf -lavfopts format=mp4 -oac lavc -ovc lavc " CMD+="-lavcopts aglobal=1:vglobal=1:vcodec=mpeg4:vbitrate=600:acodec=libfaac:abitrate=128 " CMD+="-af lavcresample=22050 -vf dsize=480:320:0,scale=0:0,expand=480:320,harddup -ofps 25 -srate 22050 " P="没有找到对应的字幕." if [[ -f "${INFILE%.*}.srt" ]];then CMD+=" -sub ""\"$PWD/${INFILE%.*}.srt\"" P="找到字幕文件: ""${INFILE%.*}.srt" elif [[ -f "${INFILE%.*}.ssa" ]];then CMD+=" -sub ""\"$PWD/${INFILE%.*}.ssa\"" P="找到字幕文件: ""${INFILE%.*}.ssa" elif [[ -f "${INFILE%.*}.aas" ]];then CMD+=" -sub ""\"$PWD/${INFILE%.*}.aas\"" P="找到字幕文件: ""${INFILE%.*}.aas" fi CMD+=" -o ""\"$SAVEDIR/${INFILE%.*}_ipod.mp4\""" ""\"$PWD/$INFILE\"" P+="\n\n保存目录: $SAVEDIR" P+="\n\n是否继续?" #echo "$CMD" >> ~/toIpod.log if ! zenity --question --text "$P" ; then exit 0; fi eval "$CMD 2>&1" |\ while read line; do echo $line |\ awk -F '[ :\(\)%]+' '/^Pos/{print "# 速度:",$5,"(点\"取消\"转入后台运行.)";print $4}' ; done |\ zenity --progress --title "正在转换..." --percentage=0 --auto-close --width=500 |
截图: