PB小助手3.5发布

终于结束了!在完成打包上传之后,我长吁了一口气。高兴是自然的,但更多的一种如释重负的轻松。从有升级 的想法到现在,前后大约半年多的时间,本来只想加一个语法高亮的功能,结果一发不可收拾,零零碎碎的小功能加了不少。功能增多了,渐渐发现原来写的一些代 码的结构和设计有问题,可读性和扩展性都不好,所以又重新refactory,结果整出很多regression的问题,一度弄得我十分狼狈,有点焦头烂 额的味道,毕竟时间长了,很多代码都搞不清当初为什么这么写,一边抓脑袋一边又重温了N遍关于注释的重要性,正所谓知易行难,好习惯的培养确非一日之功。 就这么改改停停,停停走走,今天终于结束了,其中有一些功能都是经常使用本软件的用户提出的,也借用了很多网友提供的现成代码,在这里一并表示感谢,也希 望PowerBuilder一路走好,毕竟这小软件是为她写的。

– 作者: 渔民 2006年01月3日, 星期二 09:47

PowerBuilder历史

最近看到一篇国外的文章,觉得很有意思,所以花了些时间翻译了一下,贴在这里,也好让大家了解一下PB昔日的辉煌 🙂

PowerBuilder历史–一个澳裔加拿大人眼中的传奇
作者: Chris Pollach
原文:http://pbdj.sys-con.com/read/124571.htm
译者:PB助手

Sybase从什么时候开始开发PowerBuilder这个工具?PB又是如何演变成今天的模样?我曾经多次被不同的客户和学生问起这样的问题, 而且一些IT界人士也对此表现出浓厚的兴趣,因此我不得不经常重复这个故事,并答复许多关于这方面的email。所以我决定将这篇东西正式地发布在 PBDJ(注:pbdj.sys-con.com)上,以便使更多的后来人能了解PB的故事。下面所说的完全是我个人的一些观点及经历,某些事件发生的时 间或地点可能会有些偏差(在我这样的年纪,记性是越来越差了)。OK,让我们从头开始吧。

 

第一幕 孕育
在那遥远的群星中……,早在Sybase之前……,PowerBuilder的产品原型被一家位于波士顿(美国马萨诸塞州)的 名为Cullinet的数据库公司开发出来。Cullinet也是IDMS关系型数据库和ADS-Online应用开发系统的拥有者。IDMS数据库最开 始是BF Goodrich开发的,这是一家位于加拿大安大略省伦敦市的轮胎公司,然后卖给了Cullinane(也就是后来的Cullinet)。 Cullinet同时还有一种名叫”Golden Gate“的基于PC的开发工具,它可以被用来开发连接大型数据库主机的Client/Server图形化界面(GUI)应用程序,它原本是DOS下的开 发环境,后来才被移植到MS Windows。1984年的时候,Cullinet认识到对基于PC的数据库及开发工具的需求即将兴起,他们考虑是否能参考ADS-Oline这个运行 在327X机上并获得巨大成功的快速开发工具(RAD)开发出另一个基于PC的GUI开发工具。ADS-Oline的主要特性包括:快速开发,集成设计、 编码、编译及调试,交互式原型生成等。它同时也拥有集中的数据字典,以及对不同的CASE工具的接口,可以直接从开发环境生成产品交付使用。

1984年,Cullinet开始了它的PC版的ADSO的原型工作,那时我是Cullinet加拿大的技术支持经理。这个项目组的头叫作Dave Litwack,他负责ADSO产品及IDMS-DC(一种类似于远程处理及客户信息控制系统的产品)。由于在Cullinet的多年经验,Dave对快 速开发工具及通讯有着深刻的理解。这个开发中的新产品将拥有与ADSO一样的关键特性(值得一提的是,早在1985年的PB原型中,ORCA已经基本可以 工作,这正是因为ADSO/IDMS拥有这项功能),同时还将新增一项重要功能:”一个强大的数据感知对象“。当时Cullinet正在试验一个叫作 LRF(逻辑记录能力)及数据库存储过程的特性,这个新的对象将可以封装数据处理过程,使之从应用中剥离,而且它应该位于客户端,不依赖于任何服务端的数 据库实现。

在这个原型的最初阶段,Dave选择了一名真正的C语言高手,名叫Kim Sheffield,同时他又从渥太华(安大略)招募了John Griffin–我的一个朋友,John是一名出色的大型机汇编程序员,那时他正准备转型到C语言。Dave让他编写了Menu画板 (painter)。后来,John和另一名Cullinet的程序员Julie结了婚,Julie帮他一起重写了Menu画板以及在PB里加入对 EAServer组件的远程调试功能。

Dave想要打造一个完全面向对象的全新工具,当时,C++正强力推出,但SmallTalk才是面向对象编程的真正主流。Dave希望PowerBuilder采用SmallTalk的面向对象思想,但同时要更易于被那些商业程序的开发者所使用。

在1985年的时候,这个工具的基本原型在Cullinet公司内部展示,它的潜力很快被高管层所明了,这些人中包括Bobby Orr–曲棍球传奇[以后我会说的另一个有趣的故事],那时他在Cullinet董事会任职。但很不幸的是,当时Cullinet正面临着被其他公司收 购的严峻挑战,这其中包括CA(Computer Associates)公司。当时CA已经收购了DataCom并想要收购Cullinet以获得IDMS,CA的想法是用钱来阻止竞争,卖掉所有非关键 业务,然后通过对主要客户的产品维护来谋利。因为没有或仅仅保留少量开发人员(裁员),公司便可大量节省开支并获得更多利润。在1986年,CA终于成功 地完成了对Cullinet的恶意收购,这个新的PC开发工具被认为是无足轻重的,与之相关的所有开发人员都被遣散。(这也就是为什么到现在我也从不购买 任何CA产品的原因!)

 

第二幕 重生
1986年的时候,PowerSoft公司为VAX平台开发商业应用程序,同时它也认识到基于PC的应用开发即将引爆,于是开 始四处寻找具有领先水平的GUI开发工具。他们雇佣了一名独立咨询师来提供相关建议,此人正是Dave Litwack。当时,Gupta公司的SQLWindows是市面上唯一真正的GUI开发工具,而不象其他的产品那样,你必须在开发过程中编写若干C代 码。而这一点也正是PowerSoft希望在商业应用开发中所能避免的。Dave提到了他在Cullinet的工作以及那个最后的原型,于是 PowerSoft找到CA,问是否能得到那个用C写成的PowerBuilder原型的源代码。CA答复说他们看过了那个原型而且认为它不会有前途 (咄)–所以给点钱拿走吧,祝你们好运!

1988年,也就是PowerBuilder原型完成三年之后,PowerSoft拿到了它的源码,Dave终于又可以开始继续他的工作,他加入的 PowerSoft,并重新征召当年的原班人员,幸运的是,他们当时也正在寻找一些富有挑战性的工作。然后PowerSoft把这个产品命名为 “PowerBuilder”并开始完善以及增强它的功能。由于PowerSoft本身就是一家商业应用开发公司,他们开始在内部使用 PowerBuilder来重新开发并替代原有的VAX平台下的产品,因此测试工作得以在真实的开发环境中进行并得到加强。为了使这个新工具的开发获得更 多的资金支持,PowerSoft和HP建立了伙伴关系,HP在看过PowerBuilder的演示后给了他们一大笔钱(这也是象“~Onn”这样的八进 制表示法被引入的原因–当年HP还是8位的机器)。PowerBuilder成为了HP内部应用系统开发的标准。

与此同时,在微软雷蒙德的办公室,负责开发内部应用系统的工程师们也面临着与当年PowerSoft同样的问题–他们需要一个专门的应用系统开发 工具。他们询问那些在HP工作的朋友,被告之:PowerBuilder是唯一一个值得一看而且很有前途的工具。于是在1989年的早些时候,MS购买了 PowerBuilder的使用许可并成为PB的第二家全球用户。而“澳洲皇家空军”则是PB的第一家官方用户–很荣幸的是,我正是一个来自 Cooma,新南威尔士的澳洲人。

在我4岁的时候,我父亲曾带我到悉尼大学,在那里他使用一台SILLIAC I(伊利诺斯州大学计算机ILLIAC的悉尼版本)–第一台商用计算机,曾被制造用来在雪山水力发电站计算大坝的压力及形变(甚至在今天,这仍然是世界上最大的水力发电站http://www.snowyhydro.com.au)。 也是在这里,我平生第一次见到了“程序调试员”……这是另外一个故事。这个雪山电站到现在仍然使用着PowerBuilder开发的程序。微软也使用过 PowerBuilder来开发他们的库存管理系统,微软大学排课系统等等,他们被它强大的数据处理能力所折服–尤其是那个被叫 作”DataWindow”的新对象(感谢Kim取的名字)
第三幕 威力
1989年的时候,我代表加拿大税收和财政委员会为加拿大政府做一个项目,我的任务是评估不断涌现的关系型数据库技术 并给出排名前三的产品,他们将被推荐给所有的政府部门使用。我在89年夏天的晚些时候完成了这个项目。其中一个竞争者是微软的SQLServer(这是 Sybase SQLServer 4.x向OS/2平台移植的结果)。为了验证最后基准数据的准确,每一个数据库厂家都派出一名代表来协助我配置他们的环境并确认我的测试方法及结果。

最后,微软的工程师从新汉普郡来到渥太华。他给了我很大帮助,并问我下一步要做什么。我告诉他下一步是为这前三名的数据库产品比较并推荐GUI开发 工具。他的回答令我震惊,他说我应该给一个叫“PowerSoft”的新公司打电话向他们要PowerBuilder看看,因为他和他在雷蒙德的同事们都 在用它(当然,微软的Sales从不说这些)。我打电话给PowerSoft,但他们说不能给我一个评估版,我必须买一个,如果在试用后发现不合适可以退 货(如果你曾经为美国或加拿大政府工作过,你就会知道这样的答复是多么愚蠢)。但幸运的是,他们告诉了我PB的由来,以及他们从一个被CA收购的波士顿数 据库公司那里招聘了一批开发人员的事情。我谢过这位销售人员,接着挂机,重拨,找Dave Litwack,电话马上就通了,令我惊喜的是,Dave问我“qaStaH nuq? Chris!” (行话,意思是”What’s happening,Chris?” www.kli.org/tlh/phrases.html),第二天,我就拿到了PB的一份拷贝(在这里再次感谢Dave)

我把这个版本(0.8 Alpha,当时是2张软盘)给了我的同事们,他们用了3天的时间来考验它,并连续地高速访问我们准备好的那些数据库环境。相比于其他的我们所接触过的工 具而言,PB的DataWindow带来的数据处理及SQL生成能力就象是“一阵新鲜空气”,而且速度也非常接近于用C所编写的代码,相比而 言,SQLWindows慢得象牛一样(个人观点)

PB成为了我们推荐给加拿大政府的三款开发工具中的首选。税务局使用PB开发了GST(政府营业税征管系统),用来征收和管理所有的税收记录,甚至 在今天仍在使用–可见系统的重要性。其他部门紧随其后,今天,绝大多数的加拿大政府部门使用PB来开发他们的重要工作系统:在进入加拿大境内时你的汽车 牌照被扫描(与一个PB管理的后台数据库进行对比),你的护照被扫描并检查(这些都是PB作的);如果你的私人飞机停在加拿大的飞机跑道上,一个用PB实 现的24×7的计费系统将和雷达系统一道对您的飞机进行计费;如果你要领取一笔养老金(前后端均由PB实现,该系统在2002年1月投入使用),向最高法 院登记一个案例或者访问他们的网站http://www.scc-csc.gc.ca,向税务法院提出一个诉讼,登记枪械,部署联合国部队,接受一项国防部的安全检查,联邦选举,这样的系统很多,很多。

在90年代早期,SQLWindows和一个新的工具??Delphi,在性能方面对PB发起冲击。SQLWindows开始支持生成C语言编译 码,而Delphi从一开始就是这样。这使得SQLWindows变得和PB一样快,但Delphi在循环操作方面比PB要快很多。但 DataWindow??这个用90%的C代码以及10%的汇编写成的对象,仍然具有无可比拟的优势(我有一个很好关于VB.NET vs. PB9的例子,在去年,为一个政府部门开发基于MSSQL数据库的应用系统,PB以约40倍的性能优势胜出!但PB的表现似乎比我过去的经验要差……)

很多年以来,Dave, Bill Rabkin(最初的PB技术传道士),和我一直在探讨关于编译器的话题。我们也经常评论起那些在沃特卢(安大略省)的家伙在开发最先进的线性编译器的时 候是多么地高效。也正是在这个时候,Gupta(开发SQLWindows的公司)开始在他们的产品中捆绑自己的数据库SQLBase。 PowerSoft也想要这样做来操持竞争力,他们开始在PB(应该是2.0)中捆绑WatCom公司的SQL数据库,这对我来说毫不奇怪。就象 Vitor kiam说的:我太喜欢它了我要买下它。PowerSoft在PB第三版时买下了这个公司,此后,这次收购的另一个好处开始显现,Dave对那些原 WatCom公司中写C编译器的家伙说:你们能把PB的P-code拿掉并生成纯C编译码吗?(那时PB刚从C语言平台迁移到C++)。他们说:没问题, 而且在一周之类完成了工作。这次,PB在性能上的提升让Delphi开始坐立不安,加上DataWindow的出色表现,PB让竞争者落后很多(到今天依 然如此)。

也正是在这个时候,Bill Gates来到渥太华发表他对加拿大政府的重要演讲。我和他见了面(1994年),他告诉我一些有趣的事情:微软使用WatCom的C编译器来开发VB的 JET引擎,Access的部分功能以及整个FoxPro for Windows。微软无法把这些产品转换为使用他们自己的编译器,因为这会使这些产品的性能慢400倍,而这是广大用户不可接受的。我的一个朋友,他离开 了原来在多伦多的工作去了雷蒙德,他告诉我Bill希望把VB改为纯面向对象的语言并且已经有了一个原型(1993年4月),但是在作原型演示时,那些重 要客户都表示无法接受:为了得到纯面向对象的好处,而不得不全部重新改写他们的代码(就象现在的从VB6到VB.NET一样)。他们告诉微软 PowerBuilder让他们感觉更好。而最近,许多渥太华VB.NET的开发者也告诉我,他们正建议他们所在的开发部门转向 PowerBuilder,因为它在面向对象方面更友好且容易学习得多。(难道你不认为这很有趣吗?)

 

第四幕 危机
在90年代中期,Gupta被Oracle多次的恶意收购的企图所击垮。Oracle想要和PB竞争,但是PowerSoft 那时的财务还很稳健,Oracle无从下手,所以Oracle象当年的SQLWindows一样重新开发了自己的开发套件SQL Forms等。甚至在今天,任何一个曾经使用过SQL Forms的我的学生在见到PB后,无不象抛掉一块烫手的山芋一样抛弃了SQL Forms。这里有一个最近的例子:我接手了一个新的系统,这个系统曾经由2个使用Oracle的开发者在Oracle数据库上开发,但他们用了9个月的 时间却无法拿出一个象样的原型。我用PB7/8开发了大约3个月的时间,并且让整个新系统在3个月之内投入了运行。很明显Oracle输了,甚至是使用他 们自己的数据库。这个系统在几个月前被升级到PB10.2,目前正在使用。

Dave Litwack和当时的PowerSoft执行层对Oracle的行动很紧张(CA收购Cu的阴影),他们想要联合另一家大公司来确保Cullinet的 故事不再重演。Sybase曾经将自己的SQLServer产品转移到MS-NT平台上,因而也知道PB的威力(甚至今天仍有约63%的基于Oracle 的系统使用PB来作为开发工具),他们正好缺少一个好的GUI开发工具。两个公司一拍即合,在技术上互为补充,WatCom也随PowerSoft一同并 入Sybase,并被更名为iAnywhere Solutions.

Dave Litwack和Kim Sheffield在此后不久便离开Sybase去开发一个真正“cool”的产品,也就是SilverStream(http://jdj.sys-con.com/read/36628.htm)。 同样的,这个工具也拥有一个开放IDE所应具备的关键特性:集成开发,连接各种不同的数据库,集中管理的数据字典,方便部署,快速原型开发,服务提供对象 等。Sybase本也不应该忘记这些作为一个好工具的必要特性。最近,Microsoft终于在VS2005里学到了这个昂贵的教训(差不多用去10年的 时间)。我用过SilverStram这个产品,这个新Java开发工具的功能在1998-99年末的时候得到验证。一些加拿大联邦政府的代理机构开始使 用它,我们也用它开发过一个很好的网站门户。Kim对这个工具所增加的一项关键特性就是一个与DataWindow相似的对象,并且支持树形结构显示 (PB10.5让人伤心,直到今天才加入这个功能)。这些组件可以在Java本地或网站开发中使用。所有这些特性都使得SilverStream远胜过 PowerBuilder,以及同时出品的PowerJ。在事后看来,当年John Chen(程守宗)同意Dave他们离开而不是继续从事在PB,以及目前Sybase的新产品“WorkSpace”(一个基于Eclipse的集成开发 环境)上的工作,这实在是一个重大失误。由于在2000年早期过分关注Java,Sybase失去了发展PB的重要契机。
在2003年的时候,SilverStream被Novell收购,Dave Litwack目前仍在这家公司。Kim Sheffield则离开了Novell,现在是fyiReporting软件公司(www.fyireporting.com/company.html) 的主要所有者。他有一套用C#开发的令人感兴趣的报表产品,可以当作插件在PB程序中使用(又绕回来了……)。因为我本人非常尊敬作为一名程序员的 Kim,所以当看到他放弃Java转而投向.NET时觉得这里面很有意思。也许Sybase应该对象Kim这样的重要的开发者保持关注,因为他们反应了当 前市场上的流行趋势(本人拙见)。Dave现在是Novell的高级副总裁兼总经理,负责身份认证驱动产品系统(www.novell.com/company/bios/litwack.html)。Bill Rabkin也离开Sybase去了Rational,但在与IBM合并后不久又辞职了,现在是Idiom科技的WorldServer产品的积极传道者。

 

第五幕 反击
早在与Sybase合并之初,PowerSoft便丧失了“工具创新”的方向(我相信这是因为“服务端”产品的思维左右了高管 层)。但最近,我们看到了这种精神的在PB10,11等产品上的重新振作,包括PB12的远景蓝图,Java的低迷(对过去2年的最新统计显示:约60% 的美国公司去年所进行的Java项目最终被放弃),针对Window CE(Pocket PC)的PocketBuilder产品的推出等。

有趣的是,PocketBuilder产品的开发是由Reed Shilts(一个资深的PowerBuilder导师)以及John Griffin所领导的,后者是渥太华人,从最初的Cullinet时期便开始从事PowerBuilder开发,直到今天,仍然不断地在PB的产品线中 加入一些加拿大人的东西:)。Sybase下面的iAnyWhere子公司(原来的WatCom),现在位于加拿大的沃特卢,最近发布不少好东西,同时也 在不断地加强他们的数据库产品(我认为这是当前市面上最好的中小型DBMS)。也许这才是我最近所发现的Sybase公司在重新审视开发方向的努力上的真 正关键转变。不过,这应该是另一个故事(全文完)

 

– 作者: 渔民 2006年03月7日, 星期二 13:32

UNIX初学

一个点害死人呀,/apache/bin$./apachectl start和/apache/bin$apachectl start有区别吗?到今天才明白,原来UNIX并不象DOS那样缺省时先在当前目录下找,必须显式指明,否则,就完全按PATH设置里的顺序来查找。用 了多少次了,也没觉出差别来。直到昨天在一台装了两个APACHE的机器上,配了半天,启动却没反应,折腾了一天,才发现没加点”./”,执行了另一个 apache,唉,冤啊。

    以前也总听人说,最好加”./”,也没细问缘由,今日方知,也算是没有白费工夫。

 

– 作者: tigerchamp 2004年09月26日, 星期日 16:38

谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词

很好的文章。对字符集有困惑的同学可以好好读一读。

谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词

这是一篇程序员写给程序员的趣味读物。所谓趣味是指可以比较轻松地了解一些原来不清楚的概念,增进知识,类似于打RPG游戏的升级。整理这篇文章的动机是两个问题:

问题一:
使用Windows记事本的“另存为”,可以在GBK、Unicode、Unicode big endian和UTF-8这几种编码方式间相互转换。同样是txt文件,Windows是怎样识别编码方式的呢?

我很早前就发现Unicode、Unicode big endian和UTF-8编码的txt文件的开头会多出几个字节,分别是FF、FE(Unicode),FE、FF(Unicode big endian),EF、BB、BF(UTF-8)。但这些标记是基于什么标准呢?

问题二:
最近在网上看到一个ConvertUTF.c,实现了UTF-32、UTF-16和UTF-8这三种编 码方式的相互转换。对于Unicode(UCS2)、GBK、UTF-8这些编码方式,我原来就了解。但这个程序让我有些糊涂,想不起来UTF-16和 UCS2有什么关系。

查了查相关资料,总算将这些问题弄清楚了,顺带也了解了一些Unicode的细节。写成一篇文章,送给有过类似疑问的朋友。本文在写作时尽量做到通俗易懂,但要求读者知道什么是字节,什么是十六进制。

0、big endian和little endian

big endian和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前 面?如果将6C写在前面,就是big endian。还是将49写在前面,就是little endian。

“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。

我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。

1、字符编码、内码,顺带介绍汉字编码

字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用7位的ASCII编码,为了处理汉字,程序员设计了用于简体中文的GB2312和用于繁体中文的big5。

GB2312(1980年)一共收录了7445个字符,包括6763个汉字和682个其它符号。汉字区的内码范围高字节从B0-F7,低字节从A1-FE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。

GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号,它分为汉字区和图形符 号区。汉字区包括21003个字符。2000年的GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字,同时还收录了藏文、蒙 文、维吾尔文等主要的少数民族文字。现在的PC平台必须支持GB18030,对嵌入式产品暂不作要求。所以手机、MP3一般只支持GB2312。

从ASCII、GB2312、GBK到GB18030,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有 相同的编码,后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称 呼,GB2312、GBK到GB18030都属于双字节字符集 (DBCS)。

有的中文Windows的缺省内码还是GBK,可以通过GB18030升级包升级到GB18030。不过GB18030相对GBK增加的字符,普通人是很难用到的,通常我们还是用GBK指代中文Windows内码。

这里还有一些细节:

  • GB2312的原文还是区位码,从区位码到内码,需要在高字节和低字节上分别加上A0。
  • 在DBCS中,GB内码的存储格式始终是big endian,即高位在前。
  • GB2312的两个字节的最高位都是1。但符合这个条件的码位只有128*128=16384个。所以GBK和 GB18030的低字节最高位都可能不是1。不过这不影响DBCS字符流的解析:在读取DBCS字符流时,只要遇到高位为1的字节,就可以将下两个字节作 为一个双字节编码,而不用管低字节的高位是什么。

2、Unicode、UCS和UTF

前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容(更准确地说,是与ISO-8859-1兼容),与GB码不兼容。例如“汉”字的Unicode编码是6C49,而GB码是BABA。

Unicode也是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。 Unicode的学名是”Universal Multiple-Octet Coded Character Set”,简称为UCS。UCS可以看作是”Unicode Character Set”的缩写。

根据维基百科全书(http://zh.wikipedia.org/wiki/)的记载:历史上存在两个试图独立 设计Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开发了ISO 10646项目,Unicode协会开发了Unicode项目。

在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO 10646-1相同的字库和字码。

目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode 4.1.0。ISO的最新标准是10646-3:2003。

UCS规定了怎么用多个字节表示各种文字。怎样传输这些编码,是由UTF(UCS Transformation Format)规范规定的,常见的UTF规范包括UTF-8、UTF-7、UTF-16。

IETF的RFC2781和RFC3629以RFC的一贯风格,清晰、明快又不失严谨地描述了UTF-16和 UTF-8的编码方法。我总是记不得IETF是Internet Engineering Task Force的缩写。但IETF负责维护的RFC是Internet上一切规范的基础。

3、UCS-2、UCS-4、BMP

 

UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。下面让我们做一些简单的数学游戏:

UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。

UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个 plane。每个plane根据第3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。

group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。

将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。

4、UTF编码

 

UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下:

 

UCS-2编码(16进制) UTF-8 字节流(二进制)
0000 – 007F 0xxxxxxx
0080 – 07FF 110xxxxx 10xxxxxx
0800 – FFFF 1110xxxx 10xxxxxx 10xxxxxx

例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。

读者可以用记事本测试一下我们的编码是否正确。

UTF-16以16位为单元对UCS进行编码。对于小于0x10000的UCS码,UTF-16编码就等于UCS码 对应的16位无符号整数。对于不小于0x10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,或者UCS4的BMP必然小于 0x10000,所以就目前而言,可以认为UTF-16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不 得不考虑字节序的问题。

5、UTF的字节序和BOM

UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本 前,首先要弄清楚每个编码单元的字节序。例如收到一个“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。如果我们收到 UTF-16字节流“594E”,那么这是“奎”还是“乙”?

Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一个有点小聪明的想法:

在UCS编码中有一个叫做”ZERO WIDTH NO-BREAK SPACE”的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输 字符”ZERO WIDTH NO-BREAK SPACE”。

这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符”ZERO WIDTH NO-BREAK SPACE”又被称作BOM。

UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符”ZERO WIDTH NO-BREAK SPACE”的UTF-8编码是EF BB BF(读者可以用我们前面介绍的编码方法验证一下)。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。

Windows就是使用BOM来标记文本文件的编码方式的。

6、进一步的参考资料

本文主要参考的资料是 “Short overview of ISO-IEC 10646 and Unicode” (http://www.nada.kth.se/i18n/ucs/unicode-iso10646-oview.html)。

我还找了两篇看上去不错的资料,不过因为我开始的疑问都找到了答案,所以就没有看:

  1. “Understanding Unicode A general introduction to the Unicode Standard” (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&item_id=IWS-Chapter04a)
  2. “Character set encoding basics Understanding character set encodings and legacy encodings” (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&item_id=IWS-Chapter03)

我写过UTF-8、UCS-2、GBK相互转换的软件包,包括使用Windows API和不使用Windows API的版本。以后有时间的话,我会整理一下放到我的个人主页上(http://fmddlmyy.home4u.china.com)。

我是想清楚所有问题后才开始写这篇文章的,原以为一会儿就能写好。没想到考虑措辞和查证细节花费了很长时间,竟然从下午1:30写到9:00。希望有读者能从中受益。

 

 

 

– 作者: 渔民 2005年05月10日, 星期二 17:56

字符编码及Python相关问题

碰到编码问题,搜到一篇好文,先记下

ZT: http://www.cnblogs.com/huxi/archive/2010/12/05/1897271.html

1. 字符编码简介

1.1. ASCII

ASCII(American Standard Code for Information Interchange),是一种单字节的编码。计算机世界里一开始只有英文,而单字节可以表示256个不同的字符,可以表示所有的英文字符和许多的控制符号。不过ASCII只用到了其中的一半(\x80以下),这也是MBCS得以实现的基础。

1.2. MBCS

然而计算机世界里很快就有了其他语言,单字节的ASCII已无法满足需求。后来每个语言就制定了一套自己的编码,由于单字节能表示的字符太少,而且同时也需要与ASCII编码保持兼容,所以这些编码纷纷使用了多字节来表示字符,如GBxxx、BIGxxx等等,他们的规则是,如果第一个字节是\x80以下,则仍然表示ASCII字符;而如果是\x80以上,则跟下一个字节一起(共两个字节)表示一个字符,然后跳过下一个字节,继续往下判断。

这里,IBM发明了一个叫Code Page的概念,将这些编码都收入囊中并分配页码,GBK是第936页,也就是CP936。所以,也可以使用CP936表示GBK。

MBCS(Multi-Byte Character Set)是这些编码的统称。目前为止大家都是用了双字节,所以有时候也叫做DBCS(Double-Byte Character Set)。必须明确的是,MBCS并不是某一种特定的编码,Windows里根据你设定的区域不同,MBCS指代不同的编码,而Linux里无法使用MBCS作为编码。在Windows中你看不到MBCS这几个字符,因为微软为了更加洋气,使用了ANSI来吓唬人,记事本的另存为对话框里编码ANSI就是MBCS。同时,在简体中文Windows默认的区域设定里,指代GBK。

1.3. Unicode

后来,有人开始觉得太多编码导致世界变得过于复杂了,让人脑袋疼,于是大家坐在一起拍脑袋想出来一个方法:所有语言的字符都用同一种字符集来表示,这就是Unicode。

最初的Unicode标准UCS-2使用两个字节表示一个字符,所以你常常可以听到Unicode使用两个字节表示一个字符的说法。但过了不久有人觉得256*256太少了,还是不够用,于是出现了UCS-4标准,它使用4个字节表示一个字符,不过我们用的最多的仍然是UCS-2。

UCS(Unicode Character Set)还仅仅是字符对应码位的一张表而已,比如”汉”这个字的码位是6C49。字符具体如何传输和储存则是由UTF(UCS Transformation Format)来负责。

一开始这事很简单,直接使用UCS的码位来保存,这就是UTF-16,比如,”汉”直接使用\x6C\x49保存(UTF-16-BE),或是倒过来使用\x49\x6C保存(UTF-16-LE)。但用着用着美国人觉得自己吃了大亏,以前英文字母只需要一个字节就能保存了,现在大锅饭一吃变成了两个字节,空间消耗大了一倍……于是UTF-8横空出世。

UTF-8是一种很别扭的编码,具体表现在他是变长的,并且兼容ASCII,ASCII字符使用1字节表示。然而这里省了的必定是从别的地方抠出来的,你肯定也听说过UTF-8里中文字符使用3个字节来保存吧?4个字节保存的字符更是在泪奔……(具体UCS-2是怎么变成UTF-8的请自行搜索)

另外值得一提的是BOM(Byte Order Mark)。我们在储存文件时,文件使用的编码并没有保存,打开时则需要我们记住原先保存时使用的编码并使用这个编码打开,这样一来就产生了许多麻烦。(你可能想说记事本打开文件时并没有让选编码?不妨先打开记事本再使用文件 -> 打开看看)而UTF则引入了BOM来表示自身编码,如果一开始读入的几个字节是其中之一,则代表接下来要读取的文字使用的编码是相应的编码:

BOM_UTF8 ‘\xef\xbb\xbf’
BOM_UTF16_LE ‘\xff\xfe’
BOM_UTF16_BE ‘\xfe\xff’

并不是所有的编辑器都会写入BOM,但即使没有BOM,Unicode还是可以读取的,只是像MBCS的编码一样,需要另行指定具体的编码,否则解码将会失败。

你可能听说过UTF-8不需要BOM,这种说法是不对的,只是绝大多数编辑器在没有BOM时都是以UTF-8作为默认编码读取。即使是保存时默认使用ANSI(MBCS)的记事本,在读取文件时也是先使用UTF-8测试编码,如果可以成功解码,则使用UTF-8解码。记事本这个别扭的做法造成了一个BUG:如果你新建文本文件并输入”姹塧”然后使用ANSI(MBCS)保存,再打开就会变成”汉a”,你不妨试试 :)

2. Python2.x中的编码问题

2.1. str和unicode

str和unicode都是basestring的子类。严格意义上说,str其实是字节串,它是unicode经过编码后的字节组成的序列。对UTF-8编码的str’汉’使用len()函数时,结果是3,因为实际上,UTF-8编码的’汉’ == ‘\xE6\xB1\x89’。

unicode才是真正意义上的字符串,对字节串str使用正确的字符编码进行解码后获得,并且len(u’汉’) == 1。

再来看看encode()和decode()两个basestring的实例方法,理解了str和unicode的区别后,这两个方法就不会再混淆了:

1
2
3
4
5
6
7
8
9
10
11
12
13
# coding: UTF-8
u = u'汉'
print repr(u) # u'\u6c49'
s = u.encode('UTF-8')
print repr(s) # '\xe6\xb1\x89'
u2 = s.decode('UTF-8')
print repr(u2) # u'\u6c49'
# 对unicode进行解码是错误的
# s2 = u.decode('UTF-8')
# 同样,对str进行编码也是错误的
# u2 = s.encode('UTF-8')

需要注意的是,虽然对str调用encode()方法是错误的,但实际上Python不会抛出异常,而是返回另外一个相同内容但不同id的str;对unicode调用decode()方法也是这样。很不理解为什么不把encode()和decode()分别放在unicode和str中而是都放在basestring中,但既然已经这样了,我们就小心避免犯错吧。

2.2. 字符编码声明

源代码文件中,如果有用到非ASCII字符,则需要在文件头部进行字符编码的声明,如下:

1
#-*- coding: UTF-8 -*-

实际上Python只检查#、coding和编码字符串,其他的字符都是为了美观加上的。另外,Python中可用的字符编码有很多,并且还有许多别名,还不区分大小写,比如UTF-8可以写成u8。参见http://docs.python.org/library/codecs.html#standard-encodings

另外需要注意的是声明的编码必须与文件实际保存时用的编码一致,否则很大几率会出现代码解析异常。现在的IDE一般会自动处理这种情况,改变声明后同时换成声明的编码保存,但文本编辑器控们需要小心 :)

2.3. 读写文件

内置的open()方法打开文件时,read()读取的是str,读取后需要使用正确的编码格式进行decode()。write()写入时,如果参数是unicode,则需要使用你希望写入的编码进行encode(),如果是其他编码格式的str,则需要先用该str的编码进行decode(),转成unicode后再使用写入的编码进行encode()。如果直接将unicode作为参数传入write()方法,Python将先使用源代码文件声明的字符编码进行编码然后写入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# coding: UTF-8
f = open('test.txt')
s = f.read()
f.close()
print type(s) # <type 'str'>
# 已知是GBK编码,解码成unicode
u = s.decode('GBK')
f = open('test.txt', 'w')
# 编码成UTF-8编码的str
s = u.encode('UTF-8')
f.write(s)
f.close()

另外,模块codecs提供了一个open()方法,可以指定一个编码打开文件,使用这个方法打开的文件读取返回的将是unicode。写入时,如果参数是unicode,则使用open()时指定的编码进行编码后写入;如果是str,则先根据源代码文件声明的字符编码,解码成unicode后再进行前述操作。相对内置的open()来说,这个方法比较不容易在编码上出现问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# coding: GBK
import codecs
f = codecs.open('test.txt', encoding='UTF-8')
u = f.read()
f.close()
print type(u) # <type 'unicode'>
f = codecs.open('test.txt', 'a', encoding='UTF-8')
# 写入unicode
f.write(u)
# 写入str,自动进行解码编码操作
# GBK编码的str
s = '汉'
print repr(s) # '\xba\xba'
# 这里会先将GBK编码的str解码为unicode再编码为UTF-8写入
f.write(s)
f.close()

2.4. 与编码相关的方法

sys/locale模块中提供了一些获取当前环境下的默认编码的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# coding:gbk
import sys
import locale
def p(f):
    print '%s.%s(): %s' % (f.__module__, f.__name__, f())
# 返回当前系统所使用的默认字符编码
p(sys.getdefaultencoding)
# 返回用于转换Unicode文件名至系统文件名所使用的编码
p(sys.getfilesystemencoding)
# 获取默认的区域设置并返回元祖(语言, 编码)
p(locale.getdefaultlocale)
# 返回用户设定的文本数据编码
# 文档提到this function only returns a guess
p(locale.getpreferredencoding)
# \xba\xba是'汉'的GBK编码
# mbcs是不推荐使用的编码,这里仅作测试表明为什么不应该用
print r"'\xba\xba'.decode('mbcs'):", repr('\xba\xba'.decode('mbcs'))
#在笔者的Windows上的结果(区域设置为中文(简体, 中国))
#sys.getdefaultencoding(): gbk
#sys.getfilesystemencoding(): mbcs
#locale.getdefaultlocale(): ('zh_CN', 'cp936')
#locale.getpreferredencoding(): cp936
#'\xba\xba'.decode('mbcs'): u'\u6c49'

3.一些建议

3.1. 使用字符编码声明,并且同一工程中的所有源代码文件使用相同的字符编码声明。

这点是一定要做到的。

3.2. 抛弃str,全部使用unicode。

按引号前先按一下u最初做起来确实很不习惯而且经常会忘记再跑回去补,但如果这么做可以减少90%的编码问题。如果编码困扰不严重,可以不参考此条。

3.3. 使用codecs.open()替代内置的open()。

如果编码困扰不严重,可以不参考此条。

3.4. 绝对需要避免使用的字符编码:MBCS/DBCS和UTF-16。

这里说的MBCS不是指GBK什么的都不能用,而是不要使用Python里名为’MBCS’的编码,除非程序完全不移植。

Python中编码’MBCS’与’DBCS’是同义词,指当前Windows环境中MBCS指代的编码。Linux的Python实现中没有这种编码,所以一旦移植到Linux一定会出现异常!另外,只要设定的Windows系统区域不同,MBCS指代的编码也是不一样的。分别设定不同的区域运行2.4小节中的代码的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#中文(简体, 中国)
#sys.getdefaultencoding(): gbk
#sys.getfilesystemencoding(): mbcs
#locale.getdefaultlocale(): ('zh_CN', 'cp936')
#locale.getpreferredencoding(): cp936
#'\xba\xba'.decode('mbcs'): u'\u6c49'
#英语(美国)
#sys.getdefaultencoding(): UTF-8
#sys.getfilesystemencoding(): mbcs
#locale.getdefaultlocale(): ('zh_CN', 'cp1252')
#locale.getpreferredencoding(): cp1252
#'\xba\xba'.decode('mbcs'): u'\xba\xba'
#德语(德国)
#sys.getdefaultencoding(): gbk
#sys.getfilesystemencoding(): mbcs
#locale.getdefaultlocale(): ('zh_CN', 'cp1252')
#locale.getpreferredencoding(): cp1252
#'\xba\xba'.decode('mbcs'): u'\xba\xba'
#日语(日本)
#sys.getdefaultencoding(): gbk
#sys.getfilesystemencoding(): mbcs
#locale.getdefaultlocale(): ('zh_CN', 'cp932')
#locale.getpreferredencoding(): cp932
#'\xba\xba'.decode('mbcs'): u'\uff7a\uff7a'

可见,更改区域后,使用mbcs解码得到了不正确的结果,所以,当我们需要使用’GBK’时,应该直接写’GBK’,不要写成’MBCS’。

UTF-16同理,虽然绝大多数操作系统中’UTF-16’是’UTF-16-LE’的同义词,但直接写’UTF-16-LE’只是多写3个字符而已,而万一某个操作系统中’UTF-16’变成了’UTF-16-BE’的同义词,就会有错误的结果。实际上,UTF-16用的相当少,但用到的时候还是需要注意。

–END–

 

正则表达式参考

经常在网上查,这篇说得不错.
还有一个专门的英文站, E文好的可以看看 http://www.regular-expressions.info/reference.html

揭开正则表达式的神秘面纱

 

[原创文章,转载请保留或注明出处:http://www.regexlab.com/zh/regref.htm]

引言

正则表达式(regular expression)就是用一个“字符串”来描述一个特征,然后去验证另一个“字符串”是否符合这个特征。比如 表达式“ab+” 描述的特征是“一个 ‘a’ 和 任意个 ‘b’ ”,那么 ‘ab’, ‘abb’, ‘abbbbbbbbbb’ 都符合这个特征。

正则表达式可以用来:(1)验证字符串是否符合指定特征,比如验证是否是合法的邮件地址。(2)用来查找字符串,从一个长的文本中查找符合指定特征的字符串,比查找固定字符串更加灵活方便。(3)用来替换,比普通的替换更强大。

正则表达式学习起来其实是很简单的,不多的几个较为抽象的概念也很容易理解。之所以很多人感觉正则表达式比较复杂,一方面是因为大多数的文档没有做到由浅入深地讲解,概念上没有注意先后顺序,给读者的理解带来困难;另一方面,各种引擎自带的文档一般都要介绍它特有的功能,然而这部分特有的功能并不是我们首先要理解的。

文章中的每一个举例,都可以点击进入到测试页面进行测试。闲话少说,开始。

[ 点击下载 chm 版本] – DEELX 正则语法,包含其他高级语法的 chm 版本。


1. 正则表达式规则

1.1 普通字符

字母、数字、汉字、下划线、以及后边章节中没有特殊定义的标点符号,都是”普通字符”。表达式中的普通字符,在匹配一个字符串的时候,匹配与之相同的一个字符。

举例1:表达式 “c”,在匹配字符串 “abcde” 时,匹配结果是:成功;匹配到的内容是:”c”;匹配到的位置是:开始于2,结束于3。(注:下标从0开始还是从1开始,因当前编程语言的不同而可能不同)

举例2:表达式 “bcd”,在匹配字符串 “abcde” 时,匹配结果是:成功;匹配到的内容是:”bcd”;匹配到的位置是:开始于1,结束于4。


1.2 简单的转义字符

一些不便书写的字符,采用在前面加 “\” 的方法。这些字符其实我们都已经熟知了。

 

表达式 可匹配
\r, \n 代表回车和换行符
\t 制表符
\\ 代表 “\” 本身

 

还有其他一些在后边章节中有特殊用处的标点符号,在前面加 “\” 后,就代表该符号本身。比如:^, $ 都有特殊意义,如果要想匹配字符串中 “^” 和 “$” 字符,则表达式就需要写成 “\^” 和 “\$”。

 

表达式 可匹配
\^ 匹配 ^ 符号本身
\$ 匹配 $ 符号本身
\. 匹配小数点(.)本身

 

这些转义字符的匹配方法与 “普通字符” 是类似的。也是匹配与之相同的一个字符。

举例1:表达式 “\$d”,在匹配字符串 “abc$de” 时,匹配结果是:成功;匹配到的内容是:”$d”;匹配到的位置是:开始于3,结束于5。


1.3 能够与 ‘多种字符’ 匹配的表达式

正则表达式中的一些表示方法,可以匹配 ‘多种字符’ 其中的任意一个字符。比如,表达式 “\d” 可以匹配任意一个数字。虽然可以匹配其中任意字符,但是只能是一个,不是多个。这就好比玩扑克牌时候,大小王可以代替任意一张牌,但是只能代替一张牌。

 

表达式 可匹配
\d 任意一个数字,0~9 中的任意一个
\w 任意一个字母或数字或下划线,也就是 A~Z,a~z,0~9,_ 中任意一个
\s 包括空格、制表符、换页符等空白字符的其中任意一个
. 小数点可以匹配除了换行符(\n)以外的任意一个字符

 

举例1:表达式 “\d\d“,在匹配 “abc123” 时,匹配的结果是:成功;匹配到的内容是:”12″;匹配到的位置是:开始于3,结束于5。

举例2:表达式 “a.\d“,在匹配 “aaa100” 时,匹配的结果是:成功;匹配到的内容是:”aa1″;匹配到的位置是:开始于1,结束于4。


1.4 自定义能够匹配 ‘多种字符’ 的表达式

使用方括号 [ ] 包含一系列字符,能够匹配其中任意一个字符。用 [^ ] 包含一系列字符,则能够匹配其中字符之外的任意一个字符。同样的道理,虽然可以匹配其中任意一个,但是只能是一个,不是多个。

 

表达式 可匹配
[ab5@] 匹配 “a” 或 “b” 或 “5” 或 “@”
[^abc] 匹配 “a”,”b”,”c” 之外的任意一个字符
[f-k] 匹配 “f”~”k” 之间的任意一个字母
[^A-F0-3] 匹配 “A”~”F”,”0″~”3″ 之外的任意一个字符

 

举例1:表达式 “[bcd][bcd]” 匹配 “abc123” 时,匹配的结果是:成功;匹配到的内容是:”bc”;匹配到的位置是:开始于1,结束于3。

举例2:表达式 “[^abc]” 匹配 “abc123” 时,匹配的结果是:成功;匹配到的内容是:”1″;匹配到的位置是:开始于3,结束于4。


1.5 修饰匹配次数的特殊符号

前面章节中讲到的表达式,无论是只能匹配一种字符的表达式,还是可以匹配多种字符其中任意一个的表达式,都只能匹配一次。如果使用表达式再加上修饰匹配次数的特殊符号,那么不用重复书写表达式就可以重复匹配。

使用方法是:”次数修饰”放在”被修饰的表达式”后边。比如:”[bcd][bcd]” 可以写成 “[bcd]{2}”。

 

表达式 作用
{n} 表达式重复n次,比如:“\w{2}” 相当于 “\w\w”“a{5}” 相当于 “aaaaa”
{m,n} 表达式至少重复m次,最多重复n次,比如:“ba{1,3}”可以匹配 “ba”或”baa”或”baaa”
{m,} 表达式至少重复m次,比如:“\w\d{2,}”可以匹配 “a12″,”_456”,”M12344″…
? 匹配表达式0次或者1次,相当于 {0,1},比如:“a[cd]?”可以匹配 “a”,”ac”,”ad”
+ 表达式至少出现1次,相当于 {1,},比如:“a+b”可以匹配 “ab”,”aab”,”aaab”…
* 表达式不出现或出现任意次,相当于 {0,},比如:“\^*b”可以匹配 “b”,”^^^b”…

 

举例1:表达式 “\d+\.?\d*” 在匹配 “It costs $12.5” 时,匹配的结果是:成功;匹配到的内容是:”12.5″;匹配到的位置是:开始于10,结束于14。

举例2:表达式 “go{2,8}gle” 在匹配 “Ads by goooooogle” 时,匹配的结果是:成功;匹配到的内容是:”goooooogle”;匹配到的位置是:开始于7,结束于17。


1.6 其他一些代表抽象意义的特殊符号

一些符号在表达式中代表抽象的特殊意义:

 

表达式 作用
^ 与字符串开始的地方匹配,不匹配任何字符
$ 与字符串结束的地方匹配,不匹配任何字符
\b 匹配一个单词边界,也就是单词和空格之间的位置,不匹配任何字符

 

进一步的文字说明仍然比较抽象,因此,举例帮助大家理解。

举例1:表达式 “^aaa” 在匹配 “xxx aaa xxx” 时,匹配结果是:失败。因为 “^” 要求与字符串开始的地方匹配,因此,只有当 “aaa” 位于字符串的开头的时候,”^aaa” 才能匹配,比如:”aaa xxx xxx”

举例2:表达式 “aaa$” 在匹配 “xxx aaa xxx” 时,匹配结果是:失败。因为 “$” 要求与字符串结束的地方匹配,因此,只有当 “aaa” 位于字符串的结尾的时候,”aaa$” 才能匹配,比如:”xxx xxx aaa”

举例3:表达式 “.\b.” 在匹配 “@@@abc” 时,匹配结果是:成功;匹配到的内容是:”@a”;匹配到的位置是:开始于2,结束于4。
进一步说明:”\b” 与 “^” 和 “$” 类似,本身不匹配任何字符,但是它要求它在匹配结果中所处位置的左右两边,其中一边是 “\w” 范围,另一边是 非”\w” 的范围。

举例4:表达式 “\bend\b” 在匹配 “weekend,endfor,end” 时,匹配结果是:成功;匹配到的内容是:”end”;匹配到的位置是:开始于15,结束于18。

一些符号可以影响表达式内部的子表达式之间的关系:

 

表达式 作用
| 左右两边表达式之间 “或” 关系,匹配左边或者右边
( ) (1). 在被修饰匹配次数的时候,括号中的表达式可以作为整体被修饰
(2). 取匹配结果的时候,括号中的表达式匹配到的内容可以被单独得到

 

举例5:表达式 “Tom|Jack” 在匹配字符串 “I’m Tom, he is Jack” 时,匹配结果是:成功;匹配到的内容是:”Tom”;匹配到的位置是:开始于4,结束于7。匹配下一个时,匹配结果是:成功;匹配到的内容是:”Jack”;匹配到的位置时:开始于15,结束于19。

举例6:表达式 “(go\s*)+” 在匹配 “Let’s go go go!” 时,匹配结果是:成功;匹配到内容是:”go go go”;匹配到的位置是:开始于6,结束于14。

举例7:表达式 “(\d+\.?\d*)” 在匹配 “$10.9,¥20.5” 时,匹配的结果是:成功;匹配到的内容是:”¥20.5″;匹配到的位置是:开始于6,结束于10。单独获取括号范围匹配到的内容是:”20.5″。


2. 正则表达式中的一些高级规则

2.1 匹配次数中的贪婪与非贪婪

在使用修饰匹配次数的特殊符号时,有几种表示方法可以使同一个表达式能够匹配不同的次数,比如:”{m,n}”, “{m,}”, “?”, “*”, “+”,具体匹配的次数随被匹配的字符串而定。这种重复匹配不定次数的表达式在匹配过程中,总是尽可能多的匹配。比如,针对文本 “dxxxdxxxd”,举例如下:

 

表达式 匹配结果
(d)(\w+) “\w+” 将匹配第一个 “d” 之后的所有字符 “xxxdxxxd”
(d)(\w+)(d) “\w+” 将匹配第一个 “d” 和最后一个 “d” 之间的所有字符 “xxxdxxx”。虽然 “\w+” 也能够匹配上最后一个 “d”,但是为了使整个表达式匹配成功,”\w+” 可以 “让出” 它本来能够匹配的最后一个 “d”

 

由此可见,”\w+” 在匹配的时候,总是尽可能多的匹配符合它规则的字符。虽然第二个举例中,它没有匹配最后一个 “d”,但那也是为了让整个表达式能够匹配成功。同理,带 “*” 和 “{m,n}” 的表达式都是尽可能地多匹配,带 “?” 的表达式在可匹配可不匹配的时候,也是尽可能的 “要匹配”。这 种匹配原则就叫作 “贪婪” 模式 。

非贪婪模式:

在修饰匹配次数的特殊符号后再加上一个 “?” 号,则可以使匹配次数不定的表达式尽可能少的匹配,使可匹配可不匹配的表达式,尽可能的 “不匹配”。这种匹配原则叫作 “非贪婪” 模式,也叫作 “勉强” 模式。如果少匹配就会导致整个表达式匹配失败的时候,与贪婪模式类似,非贪婪模式会最小限度的再匹配一些,以使整个表达式匹配成功。举例如下,针对文本 “dxxxdxxxd” 举例:

 

表达式 匹配结果
(d)(\w+?) “\w+?” 将尽可能少的匹配第一个 “d” 之后的字符,结果是:”\w+?” 只匹配了一个 “x”
(d)(\w+?)(d) 为了让整个表达式匹配成功,”\w+?” 不得不匹配 “xxx” 才可以让后边的 “d” 匹配,从而使整个表达式匹配成功。因此,结果是:”\w+?” 匹配 “xxx”

 

更多的情况,举例如下:

举例1:表达式 “<td>(.*)</td>” 与字符串 “<td><p>aa</p></td> <td><p>bb</p></td>” 匹配时,匹配的结果是:成功;匹配到的内容是 “<td><p>aa</p></td> <td><p>bb</p></td>” 整个字符串, 表达式中的 “</td>” 将与字符串中最后一个 “</td>” 匹配。

举例2:相比之下,表达式 “<td>(.*?)</td>” 匹配举例1中同样的字符串时,将只得到 “<td><p>aa</p></td>”, 再次匹配下一个时,可以得到第二个 “<td><p>bb</p></td>”。


2.2 反向引用 \1, \2…

表达式在匹配时,表达式引擎会将小括号 “( )” 包含的表达式所匹配到的字符串记录下来。在获取匹配结果的时候,小括号包含的表达式所匹配到的字符串可以单独获取。这一点,在前面的举例中,已经多次展示了。在实际应用场合中,当用某种边界来查找,而所要获取的内容又不包含边界时,必须使用小括号来指定所要的范围。比如前面的 “<td>(.*?)</td>“。

其实,”小括号包含的表达式所匹配到的字符串” 不仅是在匹配结束后才可以使用,在匹配过程中也可以使用。表达式后边的部分,可以引用前面 “括号内的子匹配已经匹配到的字符串”。引用方法是 “\” 加上一个数字。”\1″ 引用第1对括号内匹配到的字符串,”\2″ 引用第2对括号内匹配到的字符串……以此类推,如果一对括号内包含另一对括号,则外层的括号先排序号。换句话说,哪一对的左括号 “(” 在前,那这一对就先排序号。

举例如下:

举例1:表达式 “(|)(.*?)(\1)” 在匹配 ” ‘Hello’, “World” ” 时,匹配结果是:成功;匹配到的内容是:” ‘Hello’ “。再次匹配下一个时,可以匹配到 ” “World” “。

举例2:表达式 “(\w)\1{4,}” 在匹配 “aa bbbb abcdefg ccccc 111121111 999999999” 时,匹配结果是:成功;匹配到的内容是 “ccccc”。再次匹配下一个时,将得到 999999999。这个表达式要求 “\w” 范围的字符至少重复5次,注意与 “\w{5,}” 之间的区别

举例3:表达式 “<(\w+)\s*(\w+(=(|).*?\4)?\s*)*>.*?</\1>” 在匹配 “<td id=’td1′ style=”bgcolor:white”></td>” 时,匹配结果是成功。如果 “<td>” 与 “</td>” 不配对,则会匹配失败;如果改成其他配对,也可以匹配成功。


2.3 预搜索,不匹配;反向预搜索,不匹配

前面的章节中,我讲到了几个代表抽象意义的特殊符号:”^”,”$”,”\b”。它们都有一个共同点,那就是:它们本身不匹配任何字符,只是对 “字符串的两头” 或者 “字符之间的缝隙” 附加了一个条件。理解到这个概念以后,本节将继续介绍另外一种对 “两头” 或者 “缝隙” 附加条件的,更加灵活的表示方法。

正向预搜索:”(?=xxxxx)”,”(?!xxxxx)”

格式:”(?=xxxxx)”,在被匹配的字符串中,它对所处的 “缝隙” 或者 “两头” 附加的条件是:所在缝隙的右侧,必须能够匹配上 xxxxx 这部分的表达式。因为它只是在此作为这个缝隙上附加的条件,所以它并不影响后边的表达式去真正匹配这个缝隙之后的字符。这就类似 “\b”,本身不匹配任何字符。”\b” 只是将所在缝隙之前、之后的字符取来进行了一下判断,不会影响后边的表达式来真正的匹配。

举例1:表达式 “Windows (?=NT|XP)” 在匹配 “Windows 98, Windows NT, Windows 2000” 时,将只匹配 “Windows NT” 中的 “Windows “,其他的 “Windows ” 字样则不被匹配。

举例2:表达式 “(\w)((?=\1\1\1)(\1))+” 在匹配字符串 “aaa ffffff 999999999” 时,将可以匹配6个”f”的前4个,可以匹配9个”9″的前7个。这个表达式可以读解成:重复4次以上的字母数字,则匹配其剩下最后2位之前的部分。当然,这个表达式可以不这样写,在此的目的是作为演示之用。

格式:”(?!xxxxx)”,所在缝隙的右侧,必须不能匹配 xxxxx 这部分表达式。

举例3:表达式 “((?!\bstop\b).)+” 在匹配 “fdjka ljfdl stop fjdsla fdj” 时,将从头一直匹配到 “stop” 之前的位置,如果字符串中没有 “stop”,则匹配整个字符串。

举例4:表达式 “do(?!\w)” 在匹配字符串 “done, do, dog” 时,只能匹配 “do”。在本条举例中,”do” 后边使用 “(?!\w)” 和使用 “\b” 效果是一样的。

反向预搜索:”(?<=xxxxx)”,”(?<!xxxxx)”

这两种格式的概念和正向预搜索是类似的,反向预搜索要求的条件是:所在缝隙的 “左侧”,两种格式分别要求必须能够匹配和必须不能够匹配指定表达式,而不是去判断右侧。与 “正向预搜索” 一样的是:它们都是对所在缝隙的一种附加条件,本身都不匹配任何字符。

举例5:表达式 “(?<=\d{4})\d+(?=\d{4})” 在匹配 “1234567890123456” 时,将匹配除了前4个数字和后4个数字之外的中间8个数字。由于 JScript.RegExp 不支持反向预搜索,因此,本条举例不能够进行演示。很多其他的引擎可以支持反向预搜索,比如:Java 1.4 以上的 java.util.regex 包,.NET 中System.Text.RegularExpressions 命名空间,以及本站推荐的最简单易用的 DEELX 正则引擎


3. 其他通用规则

还有一些在各个正则表达式引擎之间比较通用的规则,在前面的讲解过程中没有提到。

3.1 表达式中,可以使用 “\xXX” 和 “\uXXXX” 表示一个字符(”X” 表示一个十六进制数)

 

形式 字符范围
\xXX 编号在 0 ~ 255 范围的字符,比如:空格可以使用 “\x20” 表示
\uXXXX 任何字符可以使用 “\u” 再加上其编号的4位十六进制数表示,比如:“\u4E2D”

 

3.2 在表达式 “\s”,”\d”,”\w”,”\b” 表示特殊意义的同时,对应的大写字母表示相反的意义

 

表达式 可匹配
\S 匹配所有非空白字符(”\s” 可匹配各个空白字符)
\D 匹配所有的非数字字符
\W 匹配所有的字母、数字、下划线以外的字符
\B 匹配非单词边界,即左右两边都是 “\w” 范围或者左右两边都不是 “\w” 范围时的字符缝隙

 

3.3 在表达式中有特殊意义,需要添加 “\” 才能匹配该字符本身的字符汇总

 

字符 说明
^ 匹配输入字符串的开始位置。要匹配 “^” 字符本身,请使用 “\^”
$ 匹配输入字符串的结尾位置。要匹配 “$” 字符本身,请使用 “\$”
( ) 标记一个子表达式的开始和结束位置。要匹配小括号,请使用 “\(” 和 “\)”
[ ] 用来自定义能够匹配 ‘多种字符’ 的表达式。要匹配中括号,请使用 “\[” 和 “\]”
{ } 修饰匹配次数的符号。要匹配大括号,请使用 “\{” 和 “\}”
. 匹配除了换行符(\n)以外的任意一个字符。要匹配小数点本身,请使用 “\.”
? 修饰匹配次数为 0 次或 1 次。要匹配 “?” 字符本身,请使用 “\?”
+ 修饰匹配次数为至少 1 次。要匹配 “+” 字符本身,请使用 “\+”
* 修饰匹配次数为 0 次或任意次。要匹配 “*” 字符本身,请使用 “\*”
| 左右两边表达式之间 “或” 关系。匹配 “|” 本身,请使用 “\|”

 

3.4 括号 “( )” 内的子表达式,如果希望匹配结果不进行记录供以后使用,可以使用 “(?:xxxxx)” 格式

举例1:表达式 “(?:(\w)\1)+” 匹配 “a bbccdd efg” 时,结果是 “bbccdd”。括号 “(?:)” 范围的匹配结果不进行记录,因此 “(\w)” 使用 “\1” 来引用。

3.5 常用的表达式属性设置简介:Ignorecase,Singleline,Multiline,Global

 

表达式属性 说明
Ignorecase 默认情况下,表达式中的字母是要区分大小写的。配置为 Ignorecase 可使匹配时不区分大小写。有的表达式引擎,把 “大小写” 概念延伸至 UNICODE 范围的大小写。
Singleline 默认情况下,小数点 “.” 匹配除了换行符(\n)以外的字符。配置为 Singleline 可使小数点可匹配包括换行符在内的所有字符。
Multiline 默认情况下,表达式 “^” 和 “$” 只匹配字符串的开始 ① 和结尾 ④ 位置。如:

①xxxxxxxxx②\n
③xxxxxxxxx④

配置为 Multiline 可以使 “^” 匹配 ① 外,还可以匹配换行符之后,下一行开始前 ③ 的位置,使 “$” 匹配 ④ 外,还可以匹配换行符之前,一行结束 ② 的位置。

Global 主要在将表达式用来替换时起作用,配置为 Global 表示替换所有的匹配。

 

 


 

4. 其他提示

4.1 如果想要了解高级的正则引擎还支持那些复杂的正则语法,可参见本站 DEELX 正则引擎的说明文档

4.2 如果要要求表达式所匹配的内容是整个字符串,而不是从字符串中找一部分,那么可以在表达式的首尾使用 “^” 和 “$”,比如:”^\d+$” 要求整个字符串只有数字。

4.3 如果要求匹配的内容是一个完整的单词,而不会是单词的一部分,那么在表达式首尾使用 “\b”,比如:使用 “\b(if|while|else|void|int……)\b” 来匹配程序中的关键字

4.4 表达式不要匹配空字符串。否则会一直得到匹配成功,而结果什么都没有匹配到。比如:准备写一个匹配 “123”、”123.”、”123.5″、”.5″ 这几种形式的表达式时,整数、小数点、小数数字都可以省略,但是不要将表达式写成:”\d*\.?\d*“,因为如果什么都没有,这个表达式也可以匹配成功。更好的写法是:”\d+\.?\d*|\.\d+

4.5 能匹配空字符串的子匹配不要循环无限次。如果括号内的子表达式中的每一部分都可以匹配 0 次,而这个括号整体又可以匹配无限次,那么情况可能比上一条所说的更严重,匹配过程中可能死循环。虽然现在有些正则表达式引擎已经通过办法避免了这种情况出现死循环了,比如 .NET 的正则表达式,但是我们仍然应该尽量避免出现这种情况。如果我们在写表达式时遇到了死循环,也可以从这一点入手,查找一下是否是本条所说的原因。

4.6 合理选择贪婪模式与非贪婪模式,参见话题讨论

4.7 或 “|” 的左右两边,对某个字符最好只有一边可以匹配,这样,不会因为 “|” 两边的表达式因为交换位置而有所不同。

使用python抓取网页

粗略学习Python中,看到一篇好文 http://crocodile.iteye.com/blog/1460227,果断转帖

 

前一段时间写的小东西,一直没工夫把他系统写出来,今天眼睛疼,就写写吧~~(原来博主不蛋疼时也会更新博客的哈~)

python抓取网页基础

python自己带有很多网络应用相关的模块,如:ftplib用于FTP相关操作,smtplib和poplib用于收发电子邮件等等,利用这些 模块自己写一个FTP软件或是邮件客户端类软件完全是可能的,我就简单的试过完全用python脚本收发邮件和操作自己的FTP服务器。当然,这都不是今 天的主角,我们今天要用到的几个模块是:urllib,urllib2,cookielib,BeautifulSoup,我们先来简单介绍下。 urllib和urllib2自然都是处理URL相关的操作,urllib可以从指定的URL下载文件,或是对一些字符串进行编码解码以使他们成为特定的 URL串,而urllib2则比urllib更2一点,哦不对,是更牛逼一点。它有各种各样的Handler啊,Processor啊可以处理更复杂的问 题,比如网络认证,使用代理服务器,使用cookie等等。第三个cookielib,顾名思义,专门处理cookie相关的操作,其他的我也没深入过。 而最后一个包,BeautifulSoup,“美丽的汤”,它是一个第三方的包,专门用于解析HTML和XML文件,使用非常之傻瓜,我们就要靠它来解析 网页源代码了,可以从这里下载,当然你也可以用easy_install安装它,这里还有它的中文使用文档,有多例子是相当不错的。

我们先来看看最简单的网页抓取,其实网页抓取就是将所要的网页源代码文件下载下来,然后对其分析以提取对自己有用的信息。最简单的抓取用一句话就可以搞定:

1
2
import urllib
html_src = urllib.urlopen('http://www.baidu.com').read()

这样就会打印出百度首页的HTML源码了,还是很easy的。urllib的urlopen函数会返回一个“类文件”对象,所以直接调用 read()即可获取里面的内容了。但是这样打印得到的内容是一团糟,排版不好看,编码问题也没解决,所以是看不到中文字符的。这就需要我们的 BeautifulSoup包了,我们可以使用上面得到的源代码字符串html_src来初始化一个BeautifulSoup对象:

1
2
from BeautifulSoup import BeautifulSoup
parser = BeautifulSoup(html_src)

这样,后续处理HTML源码的工作交给parser变量来负责就好,我们可以简单的调用parser的prettify函数来相对美观的显示源码, 可以看到这样就能看到中文字符了,因为BeautifulSoup能自动处理字符问题,并将返回结果都转化为Unicode编码格式。另 外,BeautifulSoup还能迅速定位到满足条件的指定标签,后面我们会用到~

 

抓取人人网的新鲜事

前面讲的是最简单的抓取情形了,但通常我们需要面对更复杂的情形,拿人人网来说,需要登录自己的账号才能显示新鲜事,这样我们就只能求助于更2一点 的urllib2模块了。试想,我们需要一个opener来打开你的人人网首页,而为了进入首先就需要认证,而这个登录认证需要cookie的支持,那我 们就需要在这个opener之上搭建一个处理cookie的Handler:

1
2
3
4
import urllib,urllib2,cookielib
from BeautifulSoup import BeautifulSoup
myCookie = urllib2.HTTPCookieProcessor(cookielib.CookieJar());
opener = urllib2.build_opener(myCookie)

首先import我们需要的所有模块,然后使用urllib2模块的HTTPCookieProcessor搭建一个处理cookie的 Handler,传入cookielib模块的CookieJar函数作为参数,这个这个函数处理HTTP的cookie,简单地说,它从HTTP请求中 提取cookie,然后将其返回给HTTP响应。然后利用urllib2的build_opener来建立我们需要的opener,这个opener已经 满足我们处理cookie的要求了。

那么这个Handler怎么工作呢,前面说了,它需要捕捉到HTTP请求才行,也就是说,我们需要知道,登录人人的时候发了什么数据包出去,牛逼的 人可以使用各种命令行抓包工具如Tcpdump一类的,我等小民还是图形化的吧。这里我们借助于Firefox的HttpFox来实现,可以从这里添 加这个牛逼的插件。安装好该插件以后,我们用Firefox登录人人网,然后输入自己的账号和密码,然后,别急,不要按登录按钮,从状态栏打开 HttpFox插件,点击Start按钮开始抓包,然后点击人人网的登陆,登陆过程完成后点击HttpFox的Stop停止抓包,如果一切正常的话应该可 以看到如下信息了。


我们可以看到登陆人人的过程中浏览器向人人的服务器发送POST请求数据,有四项,其中两项是你的账号和密码。下面我们就利用代码模拟发出同样的请求就可以啦。

1
2
3
4
5
6
7
8
9
post_data = {
    'email':'xxxxx',
    'password':'xxxxx',
    'origURL':'http://www.renren.com/Home.do',
    'domain':'renren.com'
}
req = urllib2.Request('http://www.renren.com/PLogin.do', urllib.urlencode(post_data))
html_src = openner.open(req).read()
parser = BeautifulSoup(html_src)

首先,构造一个字典来存储我们刚才抓到的POST数据,然后通过urllib2的Request类来自己构造一个“请求”对象,请求的递交网址就是 上面捕捉的POST请求的URL部分,后面跟着我们的POST数据,注意,这里需要使用urllib的urlencode方法重新编码,以使其成为合法的 URL串。下面的过程就跟前面提到的简单抓取过程一样,只不过这里打开的不是简单的网址,而是将网址和POST数据封装后的Request请求,同样,我 们将源码赋给BeautifulSoup以便后面处理。

现在我们的parser里存储的就是含有好友新鲜事的网页源码了,我们怎样提取有用的信息呢?分析网页这种粗活还是交给Firefox的FireBug(这里下载)来玩吧。登入你的人人网,随意右击一下新鲜事里某个人的状态,选择“查看元素”,就会蹦出如下窗口,显示出你所点击的部分对应的源码:

我们可以看到,每个新鲜事对应着article标签,我们再仔细看看article标签的详细内容:

里面的h3标签包含了好友的姓名和状态,当然,还有一些肥猪流的表情地址,接触过HTML的对这个应该不陌生吧。所以我们要抓的就是这个h3啦!

BeautifulSoup抓取标签内容

下面就是我们的parser露脸的时候了,BeautifulSoup提供了很多定位标签的方法,简单的说,主要就是find函数和findAll 函数了,常用的参数就是name和attrs,一个是标签的名字,一个是标签的属性,name是个字符串,而attrs是个字典,比如说 find(‘img’,{‘src’:’abc.jpg’})就会返回类似这样的标签:<img  src=”abc.jpg”>。而find与findAll的区别就是,find只返回第一个满足要求的标签,而findAll返回所有符合要求的 标签的列表。获取标签后,还有很多方便的方法获取子标签,例如,通过点操作符,tag.a可以获取tag所代表的标签下的子标签a,再如 tag.contents可以获取其所有孩子的列表,更多应用可以查看它的文档。下面就具体到本例来看怎么抓取我们想要的内容。

1
2
3
4
5
6
7
8
9
10
11
article_list = parser.find('div','feed-list').findAll('article')
for my_article in article_list:
    state = []
    for my_tag in my_article.h3.contents:
        factor = my_tag.string
        if factor != None:
            factor = factor.replace(u'\xa0','')
            factor = factor.strip(u'\r\n')
            factor = factor.strip(u'\n')
            state.append(factor)
    print ' '.join(state)

这里,我们通过find(‘div’,’feed-list’).findAll(‘article’)获取class属性为‘feed- list’的div标签,第二个参数直接为一个字符串时代表的就是CSS的class属性了,然后获取其所有article的列表,对照上面的图,这句其 实就是获得了所有新鲜事的列表。然后我们遍历这个列表,对于每个article标签再获取其h3标签,并提取内容,如果标签中直接含有文本,则可以通过 string属性获得,最后,我们去掉一些控制字符,如换行一类的。最终将结果打印出来,当然这只能获取一小部分,“更多新鲜事”的功能还不能达到,有兴 趣的继续研究吧,我觉得通过HttpFox是不难实现的吧。

团购信息聚合小工具

用同样的知识,我们还可以做一些有趣的应用,像现在很火的团购信息聚合,其实思路还是很容易的,就是分析网站的源代码,提取团购的标题,图片和价格 就好了。这里放出源码文件,有兴趣的可以研究下!用PyQt做的界面,按钮的功能还没有实现,只能提取“美团”,“糯米网”,“QQ团购”三个网站,从下 拉列表框里选择就可显示,图片会保存在本地目录里,来个截图看看吧~

打叉的按钮功能没有实现哦~这里是文件下载,一个pyw主窗体文件,两外两个py文件一个是UI,一个是Resource。

来源:http://pinkyjie.com/?p=376&replytocom=390

ZT: 远程桌面被自动断开的解决方法

自己一台VM碰到这样的问题,Google千百度,总算找到一解决方案,记下备查。如下:

远程桌面被自动断开的解决方法

自上学期开始,这个问题一直困扰着我。在机房上课的时候,总要远程控制自己宿舍的电脑,干点不正经的事…
悲剧的是每次连上没几分钟就会自动断开,提示是有另外一个用户强制登陆把我给T下来了。我有设密码,所以比可能被其他人T的,而且无论在哪里,用哪台机子都出现这样的情况!
因为我经常通过远程写代码的,所以这种不爽应该能够体会得到,早上4节课下来没重连个50次是不可能的。
上网找了很多Google,都没有遇见这种情况的!
后来我受不了了,装了个64位的WIN7。用WIN7优化大师简单配置一下,到学校机房,还是一样?!老是自动断开。
突然我想到了可能是我设置自动登录的问题!对于新装的系统,我只是简单配置了一下,开机时自动登录,并且注销后也会自动登录。这么一想就想通了
打开WIN7优化大师,还是让他自动登录,只不过取消了注销后自动登录,就行了…
再加上自己查的

Win7小技巧:用户账户自动登录方法汇总 – Windows7之家,Win7之家
http://www.win7china.com/html/16358.html

ZT: Top 10 DOS Batch tips (Yes, DOS Batch…)

转自:http://weblogs.asp.net/jgalloway/archive/2006/11/20/top-10-dos-batch-tips-yes-dos-batch.aspx

PowerShell’s great. I’m fired up about the opportunity to use .NET objects from simple scripts. I’ll admit I’m still getting up to speed with it, but I’m totally sold on PowerShell.

However, it’s not installed on a lot of servers I work with, and I still do a lot of my “clumsy developer attempting DBA and network admin” tasks from DOS Batch files. You can do quite a bit with DOS Batch – the silly fact is the my most popular posts (by a huge margin) are some batch scripts to allow running IE7 and IE6 on the same computer.

So, by way of tribute to the dying art of the DOS Batch file, I present my top ten batch file tricks:

  1. Use PUSHD / POPD to change directories
    Read Scott Hanselman’s writeup on PUSHD. The basic idea is that it keeps a stack, so at the simplest level you can do something like this:

    PUSHD “C:\Working Directory\” ::DO SOME WORK POPD

    That allows you to call the batch file from any directory and return to the original directory when you’re done. The cool thing is that PUSHD can be nested, so you can move all over the place within your scripts and just POPD your way out when you’re done.

  2. Call FTP scripts
    This sample prompts for the username and password, but they can of course be hardcoded if you’re feeling lucky.

    set FTPADDRESS=ftp.myserver.com set SITEBACKUPFILE=FileToTransfer.zip set /p FTPUSERNAME=Enter FTP User Name: set /p FTPPASSWORD=Enter FTP Password: CLS > script.ftp USER >>script.ftp ECHO %FTPUSERNAME% >>script.ftp ECHO %FTPPASSWORD% >>script.ftp ECHO binary >>script.ftp ECHO prompt n :: Use put instead of get to upload the file >>script.ftp ECHO get %SITEBACKUPFILE% >>script.ftp ECHO bye FTP -v -s:script.ftp %FTPADDRESS% TYPE NUL >script.ftp DEL script.ftp
  3. Read from the registry
    You can make creative use of the FOR command to read from and parse a registry value (see my previous post for more info).

    FOR /F “tokens=2* delims= ” %%A IN (‘REG QUERY “HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL” /v SQL2005’) DO SET SQLINSTANCE=%%B
  4. Run SQL Commands
    You can call OSQL (or SQLCMD on servers with SQL 2005 installed) to execute SQL commands:

    osql -E -d master -Q “BACKUP DATABASE [%DATABASENAME%] TO DISK = N’D:\DataBase\Backups\%DATABASENAME%_backup’ WITH INIT , NOUNLOAD , NAME = N’%DATABASENAME% backup’, NOSKIP , STATS = 10, NOFORMAT”
  5. Check if a file or folder exists
    I used this to do a quick and dirty check to see if a Windows Hotfix had been installed in my IE7 Standalone scripts:

    IF EXIST %SystemRoot%\$NtUninstallKB915865$\ GOTO KB_INSTALLED ECHO Installing Hotfix (KB915865) to allow tab support START /D “%~dp0/Installation/Update/” xmllitesetup.exe
  6. Pause execution for a number of seconds
    There are different ways to do this from within a batch file, all with their tradeoffs. I use a ping to an invalid IP address with a timeout. The best way to do this is to find an invalid IP address and then pint it, but 1.1.1.1 is a pretty safe bet:

    ECHO Waiting 15 seconds PING 1.1.1.1 -n 1 -w 15000 > NUL
  7. Use defaults for optional parameters
    It’s not really easy to check for a missing parameter. You have to use something like “IF dummy==%1dummy”, which will only be true if %1 is empty. So, for example, here we’re allowing a user to supply an application path via the third parameter, and defaulting it if it’s missing. By the way, beware the IF syntax. The line spacing makes a difference, so this is one that I just copy and paste to avoid figuring it out every time.

    IF dummy==dummy%3 ( SET APPLICATIONPATH=”C:\Program Files\MyApp\” ) ELSE ( SET APPLICATIONPATH = %3 )
  8. Process each file matching a pattern in a directory
    I previously posted a script which iterates all files named *.bak in a directory and restores them on the local instance of SQL Server. Here’s an excerpt:

    PUSHD %BACKUPDIRECTORY% FOR %%A in (*.bak) do CALL :Subroutine %%A POPD GOTO:EOF :Subroutine set DBNAME=%~n1 ::RUN SOME OSQL COMMANDS TO RESTORE THE BACKUP GOTO:EOF
  9. Use batch parameter expansion to avoid parsing file or directory info
    Batch file parameters are read as %1, %2, etc. DOS Command Extensions – available on Windows 2000 and up – add a lot of automatic parsing and expansion that really simplifies reading filenames passed in as parameters. I originally put this at the top of the list, but I moved it because I figured the insane syntax would drive people off. I wrote a simple batch script that shows some examples. I think that makes it a little more readable. Stick with me, I think this is one of the best features in DOS batch and is worth learning.

    First, here’s the batch file which just echos the processed parameters:

    @echo off echo %%~1 = %~1 echo %%~f1 = %~f1 echo %%~d1 = %~d1 echo %%~p1 = %~p1 echo %%~n1 = %~n1 echo %%~x1 = %~x1 echo %%~s1 = %~s1 echo %%~a1 = %~a1 echo %%~t1 = %~t1 echo %%~z1 = %~z1 echo %%~$PATHATH:1 = %~$PATHATH:1 echo %%~dp1 = %~dp1 echo %%~nx1 = %~nx1 echo %%~dp$PATH:1 = %~dp$PATH:1 echo %%~ftza1 = %~ftza1

    Now we’ll call it, passing in “C:\Windows\Notepad.exe” as a parameter:

    C:\Temp>batchparams.bat c:\windows\notepad.exe %~1 = c:\windows\notepad.exe %~f1 = c:\WINDOWS\NOTEPAD.EXE %~d1 = c: %~p1 = \WINDOWS\ %~n1 = NOTEPAD %~x1 = .EXE %~s1 = c:\WINDOWS\NOTEPAD.EXE %~a1 = –a—— %~t1 = 08/25/2005 01:50 AM %~z1 = 17920 %~$PATHATH:1 = %~dp1 = c:\WINDOWS\ %~nx1 = NOTEPAD.EXE %~dp$PATH:1 = c:\WINDOWS\ %~ftza1 = –a—— 08/25/2005 01:50 AM 17920 c:\WINDOWS\NOTEPAD.EXE

    As I said, the syntax is completely crazy, but it’s easy to look them up – just type HELP CALL at a DOS prompt; it gives you this:

    %~1 – expands %1 removing any surrounding quotes (“)
    %~f1 – expands %1 to a fully qualified path name
    %~d1 – expands %1 to a drive letter only
    %~p1 – expands %1 to a path only
    %~n1 – expands %1 to a file name only
    %~x1 – expands %1 to a file extension only
    %~s1 – expanded path contains short names only
    %~a1 – expands %1 to file attributes
    %~t1 – expands %1 to date/time of file
    %~z1 – expands %1 to size of file
    %~$PATH:1 – searches the directories listed in the PATH environment variable and expands %1 to the fully qualified name of the first one found. If the environment variable name is not defined or the file is not found by the search, then this modifier expands to the empty string
    The modifiers can be combined to get compound results:

    %~dp1 – expands %1 to a drive letter and path only
    %~nx1 – expands %1 to a file name and extension only
    %~dp$PATH:1 – searches the directories listed in the PATH environment variable for %1 and expands to the drive letter and path of the first one found.
    %~ftza1 – expands %1 to a DIR like output line

    In the above examples %1 and PATH can be replaced by other valid values. The %~ syntax is terminated by a valid argument number. The %~ modifiers may not be used with %*

  10. Learn from the masters
    By far, my favorite resource for DOS Batch trickery is the Batch Files section of Rob van der Woude’s Scripting Pages. He’s got some good PowerShell resources, too.