Unicode 問答集
問:什么是Unicode?
答:Unicode給每個(gè)字符提供了一個(gè)唯一的數(shù)字,不論是什么平臺(tái),不論是什么程序,不論什么語言。Unicode標(biāo)準(zhǔn)已經(jīng)被這些工業(yè)界的領(lǐng)導(dǎo)們所采用,例如:Apple, HP, IBM, JustSystem, Microsoft, Oracle, SAP, Sun, Sybase, Unisys和其它許多公司。最新的標(biāo)準(zhǔn)都需要Unicode,例如XML, Java, ECMAScript (JavaScript), LDAP, CORBA 3.0, WML等等,并且,Unicode是實(shí)現(xiàn)ISO/IEC 10646的正規(guī)方式。許多操作系統(tǒng),所有最新的瀏覽器和許多其他產(chǎn)品都支持它。Unicode標(biāo)準(zhǔn)的出現(xiàn)和支持它工具的存在,是近來全球軟件技術(shù)最重要的發(fā)展趨勢(shì)。
問:為什么使用Unicode?
答:基本上,計(jì)算機(jī)只是處理數(shù)字。它們指定一個(gè)數(shù)字,來儲(chǔ)存字母或其他字符。在創(chuàng)造Unicode之前,有數(shù)百種指定這些數(shù)字的編碼系統(tǒng)。沒有一個(gè)編碼可以包含足夠的字符:例如,單單歐州共同體就需要好幾種不同的編碼來包括所有的語言。即使是單一種語言,例如英語,也沒有哪一個(gè)編碼可以適用于所有的字母,標(biāo)點(diǎn)符號(hào),和常用的技術(shù)符號(hào)。這些編碼系統(tǒng)也會(huì)互相沖突。也就是說,兩種編碼可能使用相同的數(shù)字代表兩個(gè)不同的字符,或使用不同的數(shù)字代表相同的字符。任何一臺(tái)特定的計(jì)算機(jī)(特別是服務(wù)器)都需要支持許多不同的編碼,但是,不論什么時(shí)候數(shù)據(jù)通過不同的編碼或平臺(tái)之間,那些數(shù)據(jù)總會(huì)有損壞的危險(xiǎn)。
問:舉個(gè)例子吧。
答:比如,簡(jiǎn)體中文(GB)、繁體中文(BIG5)、日文中,“趙”都是一個(gè)字,但是編碼不同。在不同的編碼下,BIG5的趙是0xBBAF,而0xBBAF在GB里面就被顯示為“化”,這就是亂碼。而Unicode采用統(tǒng)一的編碼,“趙”只有一個(gè),不必管他在哪種文字里。
問:Unicode的優(yōu)點(diǎn)是什么?
答:舉一個(gè)最明顯的例子就是Windows 2000/XP以及微軟Office2000及其后的產(chǎn)品。因?yàn)檫@些軟件都是Unicode內(nèi)核,因此,無論何種文字,都可以在上面正常顯示,而且是同屏顯示。以前,簡(jiǎn)體中文的Word文件拿到英文版打開就會(huì)是亂碼,簡(jiǎn)體中文的程序在Windows英文版上運(yùn)行會(huì)出現(xiàn)亂碼,而現(xiàn)在一切都解決了。
問:中國京劇戲考為什么使用Unicode?
答:因?yàn)橛行﹦”局械纳ё?,只在擴(kuò)展字庫或繁體字庫中才有,有的甚至沒有。而Unicode不僅包含了所有常用字和大部分生僻字,而且因?yàn)槠淇蓴U(kuò)展,在現(xiàn)在沒有的情況下,將來也是可以擴(kuò)充的。例如最新的Unicode 4.0標(biāo)準(zhǔn),較3.0增加了很多生僻字。目前有70207個(gè)漢字。再有一點(diǎn)就是Unicode在將來會(huì)取代現(xiàn)有的GBK及BIG5。
問:我如何能夠看到不是亂碼的劇本?
答:如果您閱讀PDF的格式,只需要有Adobe Reader即可。如果您是在網(wǎng)站上直接閱讀劇本,有時(shí)可能會(huì)出現(xiàn)亂碼,請(qǐng)查看菜單(或右鍵單擊劇本)中,選擇編碼,然后點(diǎn)Unicode (UTF-8) 即可。注意,有些字在早期的 Unicode 定義中還沒有,所以建議您閱讀PDF格式的劇本。詳情請(qǐng)見這里。
Unicode是一個(gè)16位的字符集,它可以移植到所有主要的計(jì)算機(jī)平臺(tái)并且覆蓋幾乎整個(gè)世界。它也是單一地區(qū)的;它不包括代碼頁或者其它讓軟件很難讀寫和測(cè)試的復(fù)雜的東西?,F(xiàn)在還沒有一個(gè)合理的多平臺(tái)的字符集可以和它競(jìng)爭(zhēng)。由于以上原因,Trolltech公司從Qt 2.0開始選擇Unicode作為它天然的字符集。
Unicode協(xié)會(huì)提供了大量的文檔,包括
標(biāo)準(zhǔn)當(dāng)前的版本是3.0.1。
在Qt中,和大多數(shù)使用Qt的應(yīng)用程序中,幾乎所有的或全部的用戶可見的字符串都被使用Unicode方式存儲(chǔ)。Qt提供了:
為了獲得Unicode的益處,我們建議使用QString來存儲(chǔ)所有用戶可見的字符串并且使用QTextStream來處理所有文本文件輸入輸出。在你寫的任何一個(gè)自定制的窗口部件中使用QKeyEvent::text()來處理鍵盤輸入;它對(duì)于西歐或者北美的速度較慢的打字員來說沒有什么不同的,但是對(duì)于那些速度較快或者使用特殊輸入法的人們來說使用text()是有好處的。
在Qt中所有可能是用戶可見字符串的函數(shù)參數(shù),QLabel::setText()和很多其它函數(shù),使用const QString &來作為類型。QString對(duì)于像下面這樣的const char *工作的
myLabel->setText( "Hello, Dolly!" );
提供了隱式調(diào)用。還有一個(gè)函數(shù)QObject::tr()也提供翻譯支持,像這樣:
myLabel->setText( tr("Hello, Dolly!") );
tr()(有時(shí)被簡(jiǎn)化)從const char *映射到Unicode字符串,并且使用QTranslator對(duì)象來進(jìn)行這個(gè)映射。
程序需要和其它程序進(jìn)行通訊或者使用傳統(tǒng)文件格式進(jìn)行讀寫文件,Qt提供了大量的內(nèi)置的QTextCodec類,這些類知道如何在Unicode和傳統(tǒng)編碼之間進(jìn)行翻譯。
默認(rèn)地,和const char *的互相轉(zhuǎn)換使用基于本地的編碼解碼器。無論如何,程序都能夠很容易地找到其它地區(qū)的編碼解碼器,并且可以對(duì)于任何一個(gè)打開的文件或者網(wǎng)絡(luò)連接使用一個(gè)特殊的編碼解碼器。安裝那些內(nèi)置的編碼解碼器不支持新的編碼解碼器也是很容易的。(寫這篇文檔的時(shí)候,越南語/VISCII就是一個(gè)這樣的例子。)
盡管US-ASCII和ISO-8859-1是非常普通的,這里也提供了可以和它們互相映射的特別快的函數(shù)。舉例來說,打開一個(gè)應(yīng)用程序的圖標(biāo)也許會(huì)這樣做:
QFile f( QString::fromLatin1("appicon.png") );
關(guān)于輸出,Qt對(duì)于從Unicode到任何一個(gè)系統(tǒng)和字體提供的編碼的轉(zhuǎn)換作出了最大的努力?;诓僮飨到y(tǒng)、本地和字體的可用性和Qt對(duì)所使用的字符的支持,這種轉(zhuǎn)換也許是好的,也許是壞的。我們將在即將推出的版本中繼續(xù)改進(jìn),以最普通的地區(qū)編碼作為重點(diǎn)。
Copyright ? 2002 Trolltech | Trademarks | 譯者:Cavendish |
Qt 3.0.5版 |
by Markus Kuhn
中國LINUX論壇翻譯小組 xLoneStar[譯] 2000年2月這篇文章說明了在 POSIX 系統(tǒng) (Linux,Unix) 上使用 Unicode/UTF-8 所需要的信息. 在將來不遠(yuǎn)的幾年里, Unicode 已經(jīng)很接近于取代 ASCII 與 Latin-1 編碼的位置了. 它不僅允許你處理處理事實(shí)上存在于地球上的任何語言文字, 而且提供了一個(gè)全面的數(shù)學(xué)與技術(shù)符號(hào)集, 因此可以簡(jiǎn)化科學(xué)信息交換.
UTF-8 編碼提供了一種簡(jiǎn)便而向后兼容的方法, 使得那種完全圍繞 ASCII 設(shè)計(jì)的操作系統(tǒng), 比如 Unix, 也可以使用 Unicode. UTF-8 就是 Unix, Linux 已經(jīng)類似的系統(tǒng)使用 Unicode 的方式. 現(xiàn)在是你了解它的時(shí)候了.
國際標(biāo)準(zhǔn) ISO 10646 定義了 通用字符集 (Universal Character Set, UCS). UCS 是所有其他字符集標(biāo)準(zhǔn)的一個(gè)超集. 它保證與其他字符集是雙向兼容的. 就是說, 如果你將任何文本字符串翻譯到 UCS格式, 然后再翻譯回原編碼, 你不會(huì)丟失任何信息.
UCS 包含了用于表達(dá)所有已知語言的字符. 不僅包括拉丁語,希臘語, 斯拉夫語,希伯來語,阿拉伯語,亞美尼亞語和喬治亞語的描述, 還包括中文, 日文和韓文這樣的象形文字, 以及 平假名, 片假名, 孟加拉語, 旁遮普語果魯穆奇字符(Gurmukhi), 泰米爾語, 印.埃納德語(Kannada), Malayalam, 泰國語, 老撾語, 漢語拼音(Bopomofo), Hangul, Devangari, Gujarati, Oriya, Telugu 以及其他數(shù)也數(shù)不清的語. 對(duì)于還沒有加入的語言, 由于正在研究怎樣在計(jì)算機(jī)中最好地編碼它們, 因而最終它們都將被加入. 這些語言包括 Tibetian, 高棉語, Runic(古代北歐文字), 埃塞俄比亞語, 其他象形文字, 以及各種各樣的印-歐語系的語言, 還包括挑選出來的藝術(shù)語言比如 Tengwar, Cirth 和 克林貢語(Klingon). UCS 還包括大量的圖形的, 印刷用的, 數(shù)學(xué)用的和科學(xué)用的符號(hào), 包括所有由 TeX, Postscript, MS-DOS,MS-Windows, Macintosh, OCR 字體, 以及許多其他字處理和出版系統(tǒng)提供的字符.
ISO 10646 定義了一個(gè) 31 位的字符集. 然而, 在這巨大的編碼空間中, 迄今為止只分配了前 65534 個(gè)碼位 (0x0000 到 0xFFFD). 這個(gè) UCS 的 16位子集稱為 基本多語言面 (Basic Multilingual Plane, BMP). 將被編碼在 16 位 BMP 以外的字符都屬于非常特殊的字符(比如象形文字), 且只有專家在歷史和科學(xué)領(lǐng)域里才會(huì)用到它們. 按當(dāng)前的計(jì)劃, 將來也許再也不會(huì)有字符被分配到從 0x000000 到 0x10FFFF 這個(gè)覆蓋了超過 100 萬個(gè)潛在的未來字符的 21 位的編碼空間以外去了. ISO 10646-1 標(biāo)準(zhǔn)第一次發(fā)表于 1993 年, 定義了字符集與 BMP 中內(nèi)容的架構(gòu). 定義 BMP 以外的字符編碼的第二部分 ISO 10646-2 正在準(zhǔn)備中, 但也許要過好幾年才能完成. 新的字符仍源源不斷地加入到 BMP 中, 但已經(jīng)存在的字符是穩(wěn)定的且不會(huì)再改變了.
UCS 不僅給每個(gè)字符分配一個(gè)代碼, 而且賦予了一個(gè)正式的名字. 表示一個(gè) UCS 或 Unicode 值的十六進(jìn)制數(shù), 通常在前面加上 "U+", 就象 U+0041 代表字符"拉丁大寫字母A". UCS 字符 U+0000 到 U+007F 與 US-ASCII(ISO 646) 是一致的, U+0000 到 U+00FF 與 ISO 8859-1(Latin-1) 也是一致的. 從 U+E000 到 U+F8FF, 已經(jīng) BMP 以外的大范圍的編碼是為私用保留的.
UCS里有些編碼點(diǎn)分配給了 組合字符.它們類似于打字機(jī)上的無間隔重音鍵. 單個(gè)的組合字符不是一個(gè)完整的字符. 它是一個(gè)類似于重音符或其他指示標(biāo)記, 加在前一個(gè)字符后面. 因而, 重音符可以加在任何字符后面. 那些最重要的被加重的字符, 就象普通語言的正字法(orthographies of common languages)里用到的那種, 在 UCS 里都有自己的位置, 以確保同老的字符集的向后兼容性. 既有自己的編碼位置, 又可以表示為一個(gè)普通字符跟隨一個(gè)組合字符的被加重字符, 被稱為 預(yù)作字符(precomposed characters). UCS 里的預(yù)作字符是為了同沒有預(yù)作字符的舊編碼, 比如 ISO 8859, 保持向后兼容性而設(shè)的. 組合字符機(jī)制允許在任何字符后加上重音符或其他指示標(biāo)記, 這在科學(xué)符號(hào)中特別有用, 比如數(shù)學(xué)方程式和國際音標(biāo)字母, 可能會(huì)需要在一個(gè)基本字符后組合上一個(gè)或多個(gè)指示標(biāo)記.
組合字符跟隨著被修飾的字符. 比如, 德語中的元音變音字符 ("拉丁大寫字母A 加上分音符"), 既可以表示為 UCS 碼 U+00C4 的預(yù)作字符, 也可以表示成一個(gè)普通 "拉丁大寫字母A" 跟著一個(gè)"組合分音符":U+0041 U+0308 這樣的組合. 當(dāng)需要堆疊多個(gè)重音符, 或在一個(gè)基本字符的上面和下面都要加上組合標(biāo)記時(shí), 可以使用多個(gè)組合字符. 比如在泰國文中, 一個(gè)基本字符最多可加上兩個(gè)組合字符.
不是所有的系統(tǒng)都需要支持象組合字符這樣的 UCS 里所有的先進(jìn)機(jī)制. 因此 ISO 10646 指定了下列三種實(shí)現(xiàn)級(jí)別:
歷史上, 有兩個(gè)獨(dú)立的, 創(chuàng)立單一字符集的嘗試. 一個(gè)是國際標(biāo)準(zhǔn)化組織(ISO)的 ISO 10646 項(xiàng)目, 另一個(gè)是由(一開始大多是美國的)多語言軟件制造商組成的協(xié)會(huì)組織的 Unicode 項(xiàng)目. 幸運(yùn)的是, 1991年前后, 兩個(gè)項(xiàng)目的參與者都認(rèn)識(shí)到, 世界不需要兩個(gè)不同的單一字符集. 它們合并雙方的工作成果, 并為創(chuàng)立一個(gè)單一編碼表而協(xié)同工作. 兩個(gè)項(xiàng)目仍都存在并獨(dú)立地公布各自的標(biāo)準(zhǔn), 但 Unicode 協(xié)會(huì)和 ISO/IEC JTC1/SC2 都同意保持 Unicode 和 ISO 10646 標(biāo)準(zhǔn)的碼表兼容, 并緊密地共同調(diào)整任何未來的擴(kuò)展.
Unicode 協(xié)會(huì)公布的 Unicode 標(biāo)準(zhǔn) 嚴(yán)密地包含了 ISO 10646-1 實(shí)現(xiàn)級(jí)別3的基本多語言面. 在兩個(gè)標(biāo)準(zhǔn)里所有的字符都在相同的位置并且有相同的名字.
Unicode 標(biāo)準(zhǔn)額外定義了許多與字符有關(guān)的語義符號(hào)學(xué), 一般而言是對(duì)于實(shí)現(xiàn)高質(zhì)量的印刷出版系統(tǒng)的更好的參考. Unicode 詳細(xì)說明了繪制某些語言(比如阿拉伯語)表達(dá)形式的算法, 處理雙向文字(比如拉丁與希伯來文混合文字)的算法和 排序與字符串比較 所需的算法, 以及其他許多東西.
另一方面, ISO 10646 標(biāo)準(zhǔn), 就象廣為人知的 ISO 8859 標(biāo)準(zhǔn)一樣, 只不過是一個(gè)簡(jiǎn)單的字符集表. 它指定了一些與標(biāo)準(zhǔn)有關(guān)的術(shù)語, 定義了一些編碼的別名, 并包括了規(guī)范說明, 指定了怎樣使用 UCS 連接其他 ISO 標(biāo)準(zhǔn)的實(shí)現(xiàn), 比如 ISO 6429 和 ISO 2022. 還有一些與 ISO 緊密相關(guān)的, 比如 ISO 14651 是關(guān)于 UCS 字符串排序的.
考慮到 Unicode 標(biāo)準(zhǔn)有一個(gè)易記的名字, 且在任何好的書店里的 Addison-Wesley 里有, 只花費(fèi) ISO 版本的一小部分, 且包括更多的輔助信息, 因而它成為使用廣泛得多的參考也就不足為奇了. 然而, 一般認(rèn)為, 用于打印 ISO 10646-1 標(biāo)準(zhǔn)的字體在某些方面的質(zhì)量要高于用于打印 Unicode 2.0的. 專業(yè)字體設(shè)計(jì)者總是被建議說要兩個(gè)標(biāo)準(zhǔn)都實(shí)現(xiàn), 但一些提供的樣例字形有顯著的區(qū)別. ISO 10646-1 標(biāo)準(zhǔn)同樣使用四種不同的風(fēng)格變體來顯示表意文字如中文, 日文和韓文 (CJK), 而 Unicode 2.0 的表里只有中文的變體. 這導(dǎo)致了普遍的認(rèn)為 Unicode 對(duì)日本用戶來說是不可接收的傳說, 盡管是錯(cuò)誤的.
首先 UCS 和 Unicode 只是分配整數(shù)給字符的編碼表. 現(xiàn)在存在好幾種將一串字符表示為一串字節(jié)的方法. 最顯而易見的兩種方法是將 Unicode 文本存儲(chǔ)為 2 個(gè) 或 4 個(gè)字節(jié)序列的串. 這兩種方法的正式名稱分別為 UCS-2 和 UCS-4. 除非另外指定, 否則大多數(shù)的字節(jié)都是這樣的(Bigendian convention). 將一個(gè) ASCII 或 Latin-1 的文件轉(zhuǎn)換成 UCS-2 只需簡(jiǎn)單地在每個(gè) ASCII 字節(jié)前插入 0x00. 如果要轉(zhuǎn)換成 UCS-4, 則必須在每個(gè) ASCII 字節(jié)前插入三個(gè) 0x00.
在 Unix 下使用 UCS-2 (或 UCS-4) 會(huì)導(dǎo)致非常嚴(yán)重的問題. 用這些編碼的字符串會(huì)包含一些特殊的字符, 比如 '\0' 或 '/', 它們?cè)?文件名和其他 C 庫函數(shù)參數(shù)里都有特別的含義. 另外, 大多數(shù)使用 ASCII 文件的 UNIX 下的工具, 如果不進(jìn)行重大修改是無法讀取 16 位的字符的. 基于這些原因, 在文件名, 文本文件, 環(huán)境變量等地方, UCS-2 不適合作為 Unicode 的外部編碼.
在 ISO 10646-1 Annex R 和 RFC 2279 里定義的 UTF-8 編碼沒有這些問題. 它是在 Unix 風(fēng)格的操作系統(tǒng)下使用 Unicode 的明顯的方法.
UTF-8 有一下特性:
下列字節(jié)串用來表示一個(gè)字符. 用到哪個(gè)串取決于該字符在 Unicode 中的序號(hào).
U-00000000 - U-0000007F: | 0xxxxxxx |
U-00000080 - U-000007FF: | 110xxxxx 10xxxxxx |
U-00000800 - U-0000FFFF: | 1110xxxx 10xxxxxx 10xxxxxx |
U-00010000 - U-001FFFFF: | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
U-00200000 - U-03FFFFFF: | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
U-04000000 - U-7FFFFFFF: | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
xxx 的位置由字符編碼數(shù)的二進(jìn)制表示的位填入. 越靠右的 x 具有越少的特殊意義. 只用最短的那個(gè)足夠表達(dá)一個(gè)字符編碼數(shù)的多字節(jié)串. 注意在多字節(jié)串中, 第一個(gè)字節(jié)的開頭"1"的數(shù)目就是整個(gè)串中字節(jié)的數(shù)目.
例如: Unicode 字符 U+00A9 = 1010 1001 (版權(quán)符號(hào)) 在 UTF-8 里的編碼為:
11000010 10101001 = 0xC2 0xA9
而字符 U+2260 = 0010 0010 0110 0000 (不等于) 編碼為:
11100010 10001001 10100000 = 0xE2 0x89 0xA0
這種編碼的官方名字拼寫為 UTF-8, 其中 UTF 代表 UCS Transformation Format. 請(qǐng)勿在任何文檔中用其他名字 (比如 utf8 或 UTF_8) 來表示 UTF-8, 當(dāng)然除非你指的是一個(gè)變量名而不是這種編碼本身.
在大約 1993 年之后開發(fā)的大多數(shù)現(xiàn)代編程語言都有一個(gè)特別的數(shù)據(jù)類型, 叫做 Unicode/ISO 10646-1 字符. 在 Ada95 中叫 Wide_Character, 在 Java 中叫 char.
ISO C 也詳細(xì)說明了處理多字節(jié)編碼和寬字符 (wide characters) 的機(jī)制, 1994 年 9 月 Amendment 1 to ISO C 發(fā)表時(shí)又加入了更多. 這些機(jī)制主要是為各類東亞編碼而設(shè)計(jì)的, 它們比處理 UCS 所需的要健壯得多. UTF-8 是 ISO C 標(biāo)準(zhǔn)調(diào)用多字節(jié)字符串的編碼的一個(gè)例子, wchar_t 類型可以用來存放 Unicode 字符.
在 UTF-8 之前, 不同地區(qū)的 Linux 用戶使用各種各樣的 ASCII 擴(kuò)展. 最普遍的歐洲編碼是 ISO 8859-1 和 ISO 8859-2, 希臘編碼 ISO 8859-7, 俄國編碼 KOI-8, 日本編碼 EUC 和 Shift-JIS, 等等. 這使得 文件的交換非常困難, 且應(yīng)用軟件必須特別關(guān)心這些編碼的不同之處.
最終, Unicode 將取代所有這些編碼, 主要通過 UTF-8 的形式. UTF-8 將應(yīng)用在
在 UTF-8 模式下, 終端模擬器, 比如 xterm 或 Linux console driver, 將每次按鍵轉(zhuǎn)換成相應(yīng)的 UTF-8 串, 然后發(fā)送到前臺(tái)進(jìn)程的 stdin 里. 類似的, 任何進(jìn)程在 stdout 上的輸出都將發(fā)送到終端模擬器, 在那里用一個(gè) UTF-8 解碼器進(jìn)行處理, 之后再用一種 16 位的字體顯示出來.
只有在功能完善的多語言字處理器包里才可能有完全的 Unicode 功能支持. 而廣泛用在 Linux 里用于取代 ASCII 和其他 8 位字符集的方案則要簡(jiǎn)單得多. 第一步, Linux 終端模擬器和命令行工具將只是轉(zhuǎn)變到 UTF-8. 這意味著只用到 級(jí)別1 的 ISO 10646-1 實(shí)現(xiàn) (沒有組合字符), 且只支持那些不需要更多處理的語言象 拉丁, 希臘, 斯拉夫 和許多科學(xué)用符號(hào). 在這個(gè)級(jí)別上, UCS 支持與 ISO 8859 支持類似, 唯一顯著的區(qū)別是現(xiàn)在我們有幾千種字符可以用了, 其中的字符可以用多字節(jié)串來表示.
總有一天 Linux 會(huì)當(dāng)然地支持組合字符, 但即便如此, 對(duì)于組合字符串, 預(yù)作字符(如何可用的話)仍將是首選的. 更正式地, 在 Linux 下用 Unicode 對(duì)文本編碼的首選的方法應(yīng)該是定義在 Unicode Technical Report #15 里的 Normalization Form C.
在今后的一個(gè)階段, 人們可以考慮增加在日文和中文里用到的雙字節(jié)字符的支持 (他們相對(duì)比較簡(jiǎn)單), 組合字符支持, 甚至也許對(duì)從右至左書寫的語言如希伯來文 (他們可不是那么簡(jiǎn)單的) 的支持. 但對(duì)這些高級(jí)功能的支持不應(yīng)該阻礙簡(jiǎn)單的平板 UTF-8 在 拉丁, 希臘, 斯拉夫和科學(xué)用符號(hào)方面的快速應(yīng)用, 以取代大量的歐洲 8 位編碼, 并提供一個(gè)象樣的科學(xué)用符號(hào)集.
有兩種途徑可以支持 UTF-8, 我稱之為軟轉(zhuǎn)換與硬轉(zhuǎn)換. 軟轉(zhuǎn)換時(shí), 各處的數(shù)據(jù)均保存為 UTF-8 形式, 因而需要修改的軟件很少. 在硬轉(zhuǎn)換時(shí), 程序?qū)⒆x入的 UTF-8 數(shù)據(jù)轉(zhuǎn)換成寬字符數(shù)組, 以在應(yīng)用程序內(nèi)部處理. 在輸出時(shí), 再把字符串轉(zhuǎn)換回 UTF-8 形式.
大多數(shù)應(yīng)用程序只用軟轉(zhuǎn)換就可以工作得很好了. 這使得將 UTF-8 引入 Unix 成為切實(shí)可行的. 例如, 象 cat 和 echo 這樣的程序根本不需要修改. 他們?nèi)匀豢梢詫?duì)輸入輸出的是 ISO 8859-2 還是 UTF-8 一無所知, 因?yàn)樗鼈冎皇前徇\(yùn)字節(jié)流而沒有處理它們. 它們只能識(shí)別 ASCII 字符和象 '\n' 這樣的控制碼, 而這在 UTF-8 下也沒有任何改變. 因此, 這些應(yīng)用程序的 UTF-8 編碼與解碼將完全在終端模擬器里完成.
而那些通過數(shù)字節(jié)數(shù)來獲知字符數(shù)量的程序則需要一些小修改. 在 UTF-8 模式下, 它們必須不數(shù)入 0x80 到 0xBF 范圍內(nèi)的字節(jié), 因?yàn)檫@些只是跟隨字節(jié), 它們本身并不是字符. 例如, ls 程序就必須要修改, 因?yàn)樗ㄟ^數(shù)文件名中字符數(shù)來排放給用戶的目錄表格布局. 類似地, 所有的假定其輸出為定寬字體, 并因此而格式化它們的程序, 必須學(xué)會(huì)怎樣數(shù) UTF-8 文本中的字符數(shù). 編輯器的功能, 如刪除單個(gè)字符, 必須要作輕微的修改, 以刪除可能屬于該字符的所有字節(jié). 受影響有編輯器 (vi,emacs, 等等)以及使用 ncurses 庫的程序.
Linux 核心使用軟轉(zhuǎn)換也可以工作得很好, 只需要非常微小的修改以支持 UTF-8. 大多數(shù)處理字符串的核心功能 (例如: 文件名, 環(huán)境變量, 等等) 都不受影響. 下列地方也許必須修改:
從 GNU glibc 2.1 開始, wchar_t 類型已經(jīng)正式定為只存放獨(dú)立于當(dāng)前 locale 的, 32位的 ISO 10646 值. glibc 2.2 開始將完全支持 ISO C 中的多字節(jié)轉(zhuǎn)換函數(shù) (wprintf(),mbstowcs(),等等), 這些函數(shù)可以用于在 wchar_t 和包括 UTF-8 在內(nèi)的任何依賴于 locale 的多字節(jié)編碼間進(jìn)行轉(zhuǎn)換.
例如, 你可以寫
wprintf(L"Sch鰊e Gre!\n");
然后, 你的軟件將按照你的用戶在環(huán)境變量 LC_CTYPE (例如, en_US.UTF-8 或 de_DE.ISO_8859-1) 中選擇的 locale 所指定的編碼來打印這段文字. 你的編譯器必須運(yùn)行在與該 C 源文件所用編碼相應(yīng)的 locale 中, 在目標(biāo)文件中以上的寬字符串將改為 wchar_t 字符串存儲(chǔ). 在輸出時(shí), 運(yùn)行時(shí)庫將把 wchar_t 字符串轉(zhuǎn)換回與程序執(zhí)行時(shí)的 locale 相應(yīng)的編碼.
注意, 類似這樣的操作:
char c = L"a";
只允許從 U+0000 到 U+007F (7 位 ASCII) 范圍里的字符. 對(duì)于非 ASCII 字符, 不能直接從 wchar_t 到 char 轉(zhuǎn)換.
現(xiàn)在, 象 readline() 這樣的函數(shù)在 UTF-8 locale 下也能工作了.
如果你的應(yīng)用程序既支持 8 位字符集 (ISO 8859-*,KOI-8,等等), 也支持 UTF-8, 那么它必須通過某種方法以得知是否應(yīng)使用 UTF-8 模式. 幸運(yùn)的是, 在未來的幾年里, 人們將只使用 UTF-8, 因此你可以將它作為默認(rèn), 但即使如此, 你還是得既支持傳統(tǒng) 8 位字符集, 也支持 UTF-8.
當(dāng)前的應(yīng)用程序使用許許多多的不同的命令行開關(guān)來激活它們各自的 UTF-8 模式, 例如:
記住每一個(gè)應(yīng)用程序的命令行選項(xiàng)或其他配置方法是非常單調(diào)乏味的, 因此急需某種標(biāo)準(zhǔn)方法.
如果你在你的應(yīng)用程序里使用硬轉(zhuǎn)換, 并使用某種特定的 C 庫函數(shù)來處理外部字符編碼和內(nèi)部使用的 wchar_t 編碼的轉(zhuǎn)換工作, 那么 C 庫會(huì)幫你處理模式切換的問題. 你只需將環(huán)境變量 LC_CTYPE 設(shè)為正確的 locale, 例如, 如果你使用 UTF-8, 那就是en.UTF-8, 而如果是 Latin-1, 并需要英語的轉(zhuǎn)換, 則設(shè)為 en.ISO_8859-1.
然而, 大多數(shù)現(xiàn)存軟件的維護(hù)者選擇用軟轉(zhuǎn)換來代替, 而不使用 libc 的寬字符函數(shù), 不僅因?yàn)樗鼈冞€未得到廣泛應(yīng)用, 還因?yàn)檫@會(huì)使得軟件進(jìn)行大規(guī)模修改. 在這種情況下, 你的應(yīng)用程序必須自己來獲知何時(shí)使用 UTF-8 模式. 一種方式是做以下工作:
按照環(huán)境變量 LC_ALL, LC_CTYPE, LANG 的順序, 尋找第一個(gè)有值的變量. 如果該值包含 UTF-8 子串 (也許是小寫或沒有"-") 則默認(rèn)為 UTF-8 模式 (仍然可以用命令行開關(guān)來重設(shè)), 因?yàn)檫@個(gè)值可靠又恰當(dāng)?shù)刂甘玖?C 庫應(yīng)該使用一種 UTF-8 locale.
提供一個(gè)命令行選項(xiàng) (或者如果是 X 客戶程序則用 X resource 的值) 將仍然是有用的, 可以用來重設(shè)由 LC_CTYPE 等環(huán)境變量指定的默認(rèn)值.
在 XFree86 里帶的 xterm 版本最近已經(jīng)由 Thomas E. Dickey 加入了支持 UTF-8 的擴(kuò)展. 使用方法是, 獲取 xterm patch #119 (1999-10-16) 或更新版本, 用 "./configure --enable-wide-chars ; make" 來編譯, 然后用命令行選項(xiàng) -u8 來調(diào)用 xterm, 使它將輸入輸出轉(zhuǎn)換為 UTF-8. 在 UTF-8 模式里使用一個(gè) *-ISO10646-1 字體. 當(dāng)你在 ISO 8859-1 模式里時(shí)也可以使用 *-ISO10646-1 字體, 因?yàn)?ISO 10646-1 字體與 ISO 8859-1 字體是完全向后兼容的.
新的支持 UTF-8 的 xterm 版本, 以及一些 ISO 10646-1 字體, 將被收錄入 XFree86 4.0 版里.
Xterm 當(dāng)前只支持級(jí)別1的 ISO 10646-1, 就是說, 不提供組合字符的支持. 當(dāng)前, 組合字符將被當(dāng)作空格字符對(duì)待. xterm 將來的修訂版很有可能加入某些簡(jiǎn)單的組合字符支持, 就是僅僅將那個(gè)有一個(gè)或多個(gè)組合字符的基字符加粗 (logical OR-ing). 對(duì)于在基線以下的和在小字符上方的重音符來說, 這樣處理的結(jié)果還是可以接受的. 對(duì)于象泰國文字體那樣使用特別設(shè)計(jì)的加粗字符的文字, 這樣處理也能工作的很好. 然而, 對(duì)于某些字體里, 在較高的字符上方組合上的重音符, 特別是對(duì)于 "fixed" 字體族, 產(chǎn)生的結(jié)果就不完全令人滿意了. 因此, 在可用的地方, 應(yīng)該繼續(xù)優(yōu)先使用預(yù)作字符.
Xterm 當(dāng)前只支持那種所有字形都等寬的 cell-spaced 的字體. 將來的修訂版很有可能為 CJK 語言加入半寬與全寬字符支持, 類似于 kterm 提供的那種. 如果選擇的普通字體是 X×Y 象素大小, 且寬字符模式是打開的, 那么 xterm 會(huì)試圖裝入另外的一個(gè) 2X×Y 象素大小的字體 (同樣的 XLFD, 只是 AVERAGE_WIDTH 屬性的值翻倍). 它會(huì)用這個(gè)字體來顯示所有在 Unicode Technical Report #11 里被分配了East Asian Wide (W) 或 East Asian FullWidth (F) 寬度屬性的 Unicode 字符. 下面這個(gè) C 函數(shù)用來測(cè)試一個(gè) Unicode 字符是否是寬字符并需要用覆蓋兩個(gè)字符單元的字形來顯示:
/* This function tests, whether the ISO 10646/Unicode character code * ucs belongs into the East Asian Wide (W) or East Asian FullWidth * (F) category as defined in Unicode Technical Report #11. In this * case, the terminal emulator should represent the character using a * a glyph from a double-wide font that covers two normal (Latin) * character cells. */ int iswide(int ucs) { if (ucs < 0x1100) return 0; return (ucs >= 0x1100 && ucs <= 0x115f) || /* Hangul Jamo */ (ucs >= 0x2e80 && ucs <= 0xa4cf && (ucs & ~0x0011) != 0x300a && ucs != 0x303f) || /* CJK ... Yi */ (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ (ucs >= 0xff00 && ucs <= 0xff5f) || /* Fullwidth Forms */ (ucs >= 0xffe0 && ucs <= 0xffe6); }
某些 C 庫也提供了函數(shù)
#include <wchar.h> int wcwidth(wchar_t wc); int wcswidth(const wchar_t *pwcs, size_t n);
用來測(cè)定該寬字符 wc 或由 pwcs 指向的字符串中的 n 個(gè)寬字符碼 (或者少于 n 個(gè)寬字符碼, 如果在 n 個(gè)寬字符碼之前遇到一個(gè)空寬字符的話) 所要求的列位置的數(shù)量. 這些函數(shù)定義在 Open Group 的 Single UNIX Specification 里. 一個(gè)拉丁/希臘/斯拉夫/等等的字符要求一個(gè)列位置, 一個(gè) CJK 象形文字要求兩個(gè), 而一個(gè)組合字符要求零個(gè).
此刻還沒有給 xterm 增加從右到左功能的計(jì)劃. 希伯來與阿拉伯用戶因此不得不靠應(yīng)用程序在將希伯來文與阿拉伯文字符串送到終端前按左方向翻轉(zhuǎn)它們, 換句話說, 雙向處理必須在應(yīng)用程序里完成, 而不是在 xterm 里. 至少, 希伯來與阿拉伯文在預(yù)作字形的可用性的形式上, 以及提示表格上的支持, 比 ISO 8859 要有所改進(jìn). 現(xiàn)在還遠(yuǎn)沒有決定 xterm 是否支持雙向文字以及該怎樣工作. ISO 6429 = ECMA-48 和 Unicode bidi algorithm 都提供了可供選擇的開始點(diǎn). 也可以參考 ECMA Technical
Report TR/53. Xterm 也不處理阿拉伯文, Hangul 或 印度文本的格式化算法, 而且現(xiàn)在還不太清楚在 VT100 模擬器里處理是否可行和值得, 或者應(yīng)該留給應(yīng)用軟件去處理. 如果你打算在你的應(yīng)用程序里支持雙向文字輸出, 看一下 FriBidi, Dov Grobgeld 的 Unicode 雙向算法的自由實(shí)現(xiàn).
在過去的幾個(gè)月里出現(xiàn)了相當(dāng)多的 X11 的 Unicode 字體, 并且還在快速增多.
-misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso10646-1
(舊的 xterm 的 fixed 缺省字體的一個(gè)擴(kuò)展, 包括超過 3000 個(gè)字符) 已經(jīng)是 XFree86 3.9 snapshot 的一部分了.
Unicode X11 字體名字以 -ISO10646-1 結(jié)尾. 這個(gè) X 邏輯字體描述器 (X Logical Font Descriptor, XLFD) 的 CHARSET_REGISTRY 和 CHARSET_ENCODING 域里的值已經(jīng)為所有 Unicode 和 ISO 10646-1 的 16 位字體而正式地注冊(cè)了. 每個(gè) *-ISO10646-1 字體都包含了整個(gè) Unicode 字符集里的某幾個(gè)子集, 而用戶必須弄清楚他們選擇的字體覆蓋哪幾個(gè)他們需要的字符子集.
*-ISO10646-1 字體通常也指定一個(gè) DEFAULT_CHAR 值, 指向一個(gè)非 Unicode 字形, 用來表示所有在該字體里不可用的字符 (通常是一個(gè)虛線框, 一個(gè) H 的大小, 位于 0x1F 或 0xFFFE). 這使得用戶至少能知道這兒有一個(gè)不支持的字符. xterm 用的小的定寬字體比如 6x13 等, 將永遠(yuǎn)無法覆蓋所有的 Unicode, 因?yàn)樵S多文字比如日本漢字只能用比歐洲用戶廣泛使用的大的象素尺寸才能表示. 歐洲使用的典型的 Unicode 字體將只包含大約 1000 到 3000 個(gè)字符的子集.
X 協(xié)議無法讓一個(gè)應(yīng)用程序方便地找出一個(gè) cell-spaced 字體提供哪些字形, 它沒有為字體提供這樣的量度. 因此 Mark Leisher 和 Erik van de Poel (Netscape) 指定了一個(gè)新的 _XFREE86_GLYPH_RANGES BDF 屬性, 告訴應(yīng)用程序該 BDF 字體實(shí)現(xiàn)了哪個(gè) Unicode 子集. Mark Leisher 提供了一些樣例代碼以產(chǎn)生并掃描這個(gè)屬性, 而 Xmbdfed 3.9 以及更高版本將自動(dòng)將其加入到由它產(chǎn)生的每個(gè) BDF 文件里.
VT100 終端模擬器接受 ISO 2022 (=ECMA-35) ESC 序列, 用于在不同的字符集間切換.
UTF-8 在 ISO 2022 的意義里是一個(gè) "其他編碼系統(tǒng)" (參考 ECMA 35 的 15.4 節(jié)). UTF-8 是在 ISO 2022 SS2/SS3/G0/G1/G2/G3 世界之外的, 因此如果你從 ISO 2022 切換到 UTF-8, 所有的 SS2/SS3/G0/G1/G2/G3 狀態(tài)都變得沒有意義了, 直到你離開 UTF-8 并切換回 ISO 2022. UTF-8 是一個(gè)沒有國家的編碼, 也就是一個(gè)自我終結(jié)的短字節(jié)序列完全決定了它代表什么字符, 獨(dú)立于任何國家的切換. G0 與 G1 在 ISO 10646 里與在 ISO 8859-1 里相同, 而 G2/G3 在 ISO 10646 里不存在, 因?yàn)槿魏巫址加泄潭ǖ奈恢? 因而不會(huì)發(fā)聲切換. 在 UTF-8 模式下, 你的終端不會(huì)因?yàn)槟闩既坏匮b入一個(gè)二進(jìn)制文件而切換入一種奇怪圖形字符模式. 這使得一個(gè)終端在 UTF-8 模式下比在 ISO 2022 模式下要健壯得多, 而且因此可以有辦法將終端鎖在 UTF-8 模式里, 而不會(huì)偶然地回到 ISO 2022 世界里.
ISO 2022 標(biāo)準(zhǔn)指定了一系列的 ESC % 序列, 以離開 ISO 2022 世界 (指定其他的編碼系統(tǒng), DOCS), 用于 UTF-8 的許多這樣的序列已經(jīng)注冊(cè)進(jìn)了 ISO 2375 International Register of Coded Character Sets:
當(dāng)一個(gè)終端模擬器在 UTF-8 模式時(shí), 任何 ISO 2022 逃脫碼序列例如用于切換 G2/G3 等的都被忽略. 一個(gè)在 UTF-8 模式下的終端模擬器唯一會(huì)執(zhí)行的 ISO 2022 序列是 ESC %@ 以從 UTF-8 返回 ISO 2022 方案.
UTF-8 仍然允許你使用象 CSI 這樣的 C1 控制字符, 盡管 UTF-8 也使用 0x80-0x9F 范圍里的字節(jié). 重要的是必須理解在 UTF-8 模式下的終端模擬器必須在執(zhí)行任何控制字符前對(duì)收到的字節(jié)流運(yùn)用 UTF-8 解碼器. C1 字符與其他任何大于 U+007F 的字符一樣需先經(jīng)過 UTF-8 解碼.
參考 Adobe 的 Unicode and Glyph Names 指南.
參考 Juliusz Chroboczek 的 客戶機(jī)間 Unicode 文本的交換 草案, 對(duì) ICCCM 的一個(gè)擴(kuò)充的建議, 用一個(gè)新的可用于屬性類型(property type)和選中(selection)目標(biāo)的原子 UTF8_STRING 來處理 UTF-8 的選中.
你確實(shí)應(yīng)該訂閱的是 unicode@unicode.org 郵件列表, 這是發(fā)現(xiàn)標(biāo)準(zhǔn)的作者和其他許多領(lǐng)袖的話語的最好辦法. 訂閱方法是, 用 "subscribe" 作為標(biāo)題, "subscribe YOUR@EMAIL.ADDRESS unicode" 作為正文, 發(fā)一條消息到 unicode-request@unicode.org.
也有一個(gè)專注與改進(jìn)通常用于 GNU/Linux 系統(tǒng)上應(yīng)用程序的 UTF-8 支持的郵件列表 linux-utf8@nl.linux.org. 訂閱方法是, 以 "subscribe linux-utf8" 為內(nèi)容, 發(fā)送消息到 majordomo@nl.linux.org. 你也可以瀏覽 linux-utf8 archive
其他相關(guān)的還有 XFree86 組的 "字體" 與 "i18n" 列表, 但你必須成為一名正式的開發(fā)者才能訂閱.
我不斷地將新的材料加入這份文檔, 因此請(qǐng)定期來查看. 歡迎所有關(guān)于改進(jìn)的建議, 以及自由軟件社區(qū)里關(guān)于改善 UTF-8 支持的廣告. UTF-8 用在 Linux 里是新近的事, 因此我們?cè)趯淼膸讉€(gè)月里可以見到大量的進(jìn)展.
特別感謝 Ulrich Drepper 和 Bruno Haible 的有價(jià)值的注解
Markus Kuhn <<Markus.Kuhn@cl.cam.ac.uk>
創(chuàng)建于 1999-06-04 -- 最近更新于 2000-01-15 -- http://www.cl.cam.ac.uk/~mgk25/unicode.html
這些頁包含的信息其目的是提供一個(gè)關(guān)于正則表達(dá)式的通用介紹。
盡管試圖讓每個(gè)主題的內(nèi)容都比較獨(dú)立,但這些主題所包含的大部分信息都依賴于對(duì)前面所介紹的特性或概念的理解。因此,建議您順序地仔細(xì)閱讀這些主題,以便最全面地了解這些材料。
“正則表達(dá)式簡(jiǎn)介”包括下述各個(gè)主題:
如果原來沒有使用過正則表達(dá)式,那么可能對(duì)這個(gè)術(shù)語和概念會(huì)不太熟悉。不過,它們并不是您想象的那么新奇。
請(qǐng)回想一下在硬盤上是如何查找文件的。您肯定會(huì)使用 ? 和 * 字符來幫助查找您正尋找的文件。? 字符匹配文件名中的單個(gè)字符,而 * 則匹配一個(gè)或多個(gè)字符。一個(gè)如 'data?.dat' 的模式可以找到下述文件:
data1.dat
data2.dat
datax.dat
dataN.dat
如果使用 * 字符代替 ? 字符,則將擴(kuò)大找到的文件數(shù)量。'data*.dat' 可以匹配下述所有文件名:
data.dat
data1.dat
data2.dat
data12.dat
datax.dat
dataXYZ.dat
盡管這種搜索文件的方法肯定很有用,但也十分有限。? 和 * 通配符的有限能力可以使你對(duì)正則表達(dá)式能做什么有一個(gè)概念,不過正則表達(dá)式的功能更強(qiáng)大,也更靈活。
正則表達(dá)式的“祖先”可以一直上溯至對(duì)人類神經(jīng)系統(tǒng)如何工作的早期研究。Warren McCulloch 和 Walter Pitts 這兩位神經(jīng)生理學(xué)家研究出一種數(shù)學(xué)方式來描述這些神經(jīng)網(wǎng)絡(luò)。
1956 年, 一位叫 Stephen Kleene 的美國數(shù)學(xué)家在 McCulloch 和 Pitts 早期工作的基礎(chǔ)上,發(fā)表了一篇標(biāo)題為“神經(jīng)網(wǎng)事件的表示法”的論文,引入了正則表達(dá)式的概念。正則表達(dá)式就是用來描述他稱為“正則集的代數(shù)”的表達(dá)式,因此采用“正則表達(dá)式”這個(gè)術(shù)語。
隨后,發(fā)現(xiàn)可以將這一工作應(yīng)用于使用Ken Thompson 的計(jì)算搜索算法的一些早期研究,Ken Thompson是Unix 的主要發(fā)明人。正則表達(dá)式的第一個(gè)實(shí)用應(yīng)用程序就是 Unix 中的qed 編輯器。
如他們所說,剩下的就是眾所周知的歷史了。從那時(shí)起直至現(xiàn)在正則表達(dá)式都是基于文本的編輯器和搜索工具中的一個(gè)重要部分。
在典型的搜索和替換操作中,必須提供要查找的確切文字。這種技術(shù)對(duì)于靜態(tài)文本中的簡(jiǎn)單搜索和替換任務(wù)可能足夠了,但是由于它缺乏靈活性,因此在搜索動(dòng)態(tài)文本時(shí)就有困難了,甚至是不可能的。
使用正則表達(dá)式,就可以:
例如,如果需要搜索整個(gè) web 站點(diǎn)來刪除某些過時(shí)的材料并替換某些HTML 格式化標(biāo)記,則可以使用正則表達(dá)式對(duì)每個(gè)文件進(jìn)行測(cè)試,看在該文件中是否存在所要查找的材料或 HTML 格式化標(biāo)記。用這個(gè)方法,就可以將受影響的文件范圍縮小到包含要?jiǎng)h除或更改的材料的那些文件。然后可以使用正則表達(dá)式來刪除過時(shí)的材料,最后,可以再次使用正則表達(dá)式來查找并替換那些需要替換的標(biāo)記。
另一個(gè)說明正則表達(dá)式非常有用的示例是一種其字符串處理能力還不為人所知的語言。VBScript 是 Visual Basic 的一個(gè)子集,具有豐富的字符串處理功能。與 C 類似的 Jscript 則沒有這一能力。正則表達(dá)式給 JScript 的字符串處理能力帶來了明顯改善。不過,可能還是在 VBScript 中使用正則表達(dá)式的效率更高,它允許在單個(gè)表達(dá)式中執(zhí)行多個(gè)字符串操作。
一個(gè)正則表達(dá)式就是由普通字符(例如字符 a 到 z)以及特殊字符(稱為元字符)組成的文字模式。該模式描述在查找文字主體時(shí)待匹配的一個(gè)或多個(gè)字符串。正則表達(dá)式作為一個(gè)模板,將某個(gè)字符模式與所搜索的字符串進(jìn)行匹配。
這里有一些可能會(huì)遇到的正則表達(dá)式示例:
JScript | VBScript | 匹配 |
---|---|---|
/^\[ \t]*$/ | "^\[ \t]*$" | 匹配一個(gè)空白行。 |
/\d{2}-\d{5}/ | "\d{2}-\d{5}" | 驗(yàn)證一個(gè)ID 號(hào)碼是否由一個(gè)2位數(shù)字,一個(gè)連字符以及一個(gè)5位數(shù)字組成。 |
/<(.*)>.*<\/\1>/ | "<(.*)>.*<\/\1>" | 匹配一個(gè) HTML 標(biāo)記。 |
下表是元字符及其在正則表達(dá)式上下文中的行為的一個(gè)完整列表:
字符 | 描述 |
---|---|
\ | 將下一個(gè)字符標(biāo)記為一個(gè)特殊字符、或一個(gè)原義字符、或一個(gè) 后向引用、或一個(gè)八進(jìn)制轉(zhuǎn)義符。例如,'n' 匹配字符 "n"。'\n' 匹配一個(gè)換行符。序列 '\\' 匹配 "\" 而 "\(" 則匹配 "("。 |
^ | 匹配輸入字符串的開始位置。如果設(shè)置了 RegExp 對(duì)象的 Multiline 屬性,^ 也匹配 '\n' 或 '\r' 之后的位置。 |
$ | 匹配輸入字符串的結(jié)束位置。如果設(shè)置了RegExp 對(duì)象的 Multiline 屬性,$ 也匹配 '\n' 或 '\r' 之前的位置。 |
* | 匹配前面的子表達(dá)式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。 * 等價(jià)于{0,}。 |
+ | 匹配前面的子表達(dá)式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等價(jià)于 {1,}。 |
? | 匹配前面的子表達(dá)式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 中的"do" 。? 等價(jià)于 {0,1}。 |
{n} | n 是一個(gè)非負(fù)整數(shù)。匹配確定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的兩個(gè) o。 |
{n,} | n 是一個(gè)非負(fù)整數(shù)。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等價(jià)于 'o+'。'o{0,}' 則等價(jià)于 'o*'。 |
{n,m} | m 和 n 均為非負(fù)整數(shù),其中n <= m。最少匹配 n 次且最多匹配 m 次。劉, "o{1,3}" 將匹配 "fooooood" 中的前三個(gè) o。'o{0,1}' 等價(jià)于 'o?'。請(qǐng)注意在逗號(hào)和兩個(gè)數(shù)之間不能有空格。 |
? | 當(dāng)該字符緊跟在任何一個(gè)其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面時(shí),匹配模式是非貪婪的。非貪婪模式盡可能少的匹配所搜索的字符串,而默認(rèn)的貪婪模式則盡可能多的匹配所搜索的字符串。例如,對(duì)于字符串 "oooo",'o+?' 將匹配單個(gè) "o",而 'o+' 將匹配所有 'o'。 |
. | 匹配除 "\n" 之外的任何單個(gè)字符。要匹配包括 '\n' 在內(nèi)的任何字符,請(qǐng)使用象 '[.\n]' 的模式。 |
(pattern) | 匹配pattern 并獲取這一匹配。所獲取的匹配可以從產(chǎn)生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中則使用 $0…$9 屬性。要匹配圓括號(hào)字符,請(qǐng)使用 '\(' 或 '\)'。 |
(?:pattern) | 匹配 pattern 但不獲取匹配結(jié)果,也就是說這是一個(gè)非獲取匹配,不進(jìn)行存儲(chǔ)供以后使用。這在使用 "或" 字符 (|) 來組合一個(gè)模式的各個(gè)部分是很有用。例如, 'industr(?:y|ies) 就是一個(gè)比 'industry|industries' 更簡(jiǎn)略的表達(dá)式。 |
(?=pattern) | 正向預(yù)查,在任何匹配 pattern 的字符串開始處匹配查找字符串。這是一個(gè)非獲取匹配,也就是說,該匹配不需要獲取供以后使用。例如, 'Windows (?=95|98|NT|2000)' 能匹配 "Windows 2000" 中的 "Windows" ,但不能匹配 "Windows 3.1" 中的 "Windows"。預(yù)查不消耗字符,也就是說,在一個(gè)匹配發(fā)生后,在最后一次匹配之后立即開始下一次匹配的搜索,而不是從包含預(yù)查的字符之后開始。 |
(?!pattern) | 負(fù)向預(yù)查,在任何不匹配Negative lookahead matches the search string at any point where a string not matching pattern 的字符串開始處匹配查找字符串。這是一個(gè)非獲取匹配,也就是說,該匹配不需要獲取供以后使用。例如'Windows (?!95|98|NT|2000)' 能匹配 "Windows 3.1" 中的 "Windows",但不能匹配 "Windows 2000" 中的 "Windows"。預(yù)查不消耗字符,也就是說,在一個(gè)匹配發(fā)生后,在最后一次匹配之后立即開始下一次匹配的搜索,而不是從包含預(yù)查的字符之后開始 |
x|y | 匹配 x 或 y。例如,'z|food' 能匹配 "z" 或 "food"。'(z|f)ood' 則匹配 "zood" 或 "food"。 |
[xyz] | 字符集合。匹配所包含的任意一個(gè)字符。例如, '[abc]' 可以匹配 "plain" 中的 'a'。 |
[^xyz] | 負(fù)值字符集合。匹配未包含的任意字符。例如, '[^abc]' 可以匹配 "plain" 中的'p'。 |
[a-z] | 字符范圍。匹配指定范圍內(nèi)的任意字符。例如,'[a-z]' 可以匹配 'a' 到 'z' 范圍內(nèi)的任意小寫字母字符。 |
[^a-z] | 負(fù)值字符范圍。匹配任何不在指定范圍內(nèi)的任意字符。例如,'[^a-z]' 可以匹配任何不在 'a' 到 'z' 范圍內(nèi)的任意字符。 |
\b | 匹配一個(gè)單詞邊界,也就是指單詞和空格間的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。 |
\B | 匹配非單詞邊界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。 |
\cx | 匹配由x指明的控制字符。例如, \cM 匹配一個(gè) Control-M 或回車符。 x 的值必須為 A-Z 或 a-z 之一。否則,將 c 視為一個(gè)原義的 'c' 字符。 |
\d | 匹配一個(gè)數(shù)字字符。等價(jià)于 [0-9]。 |
\D | 匹配一個(gè)非數(shù)字字符。等價(jià)于 [^0-9]。 |
\f | 匹配一個(gè)換頁符。等價(jià)于 \x0c 和 \cL。 |
\n | 匹配一個(gè)換行符。等價(jià)于 \x0a 和 \cJ。 |
\r | 匹配一個(gè)回車符。等價(jià)于 \x0d 和 \cM。 |
\s | 匹配任何空白字符,包括空格、制表符、換頁符等等。等價(jià)于 [ \f\n\r\t\v]。 |
\S | 匹配任何非空白字符。等價(jià)于 [^ \f\n\r\t\v]。 |
\t | 匹配一個(gè)制表符。等價(jià)于 \x09 和 \cI。 |
\v | 匹配一個(gè)垂直制表符。等價(jià)于 \x0b 和 \cK。 |
\w | 匹配包括下劃線的任何單詞字符。等價(jià)于'[A-Za-z0-9_]'。 |
\W | 匹配任何非單詞字符。等價(jià)于 '[^A-Za-z0-9_]'。 |
\xn | 匹配 n,其中 n 為十六進(jìn)制轉(zhuǎn)義值。十六進(jìn)制轉(zhuǎn)義值必須為確定的兩個(gè)數(shù)字長(zhǎng)。例如, '\x41' 匹配 "A"。'\x041' 則等價(jià)于 '\x04' & "1"。正則表達(dá)式中可以使用 ASCII 編碼。. |
\num | 匹配 num,其中 num 是一個(gè)正整數(shù)。對(duì)所獲取的匹配的引用。例如,'(.)\1' 匹配兩個(gè)連續(xù)的相同字符。 |
\n | 標(biāo)識(shí)一個(gè)八進(jìn)制轉(zhuǎn)義值或一個(gè)后向引用。如果 \n 之前至少 n 個(gè)獲取的子表達(dá)式,則 n 為后向引用。否則,如果 n 為八進(jìn)制數(shù)字 (0-7),則 n 為一個(gè)八進(jìn)制轉(zhuǎn)義值。 |
\nm | 標(biāo)識(shí)一個(gè)八進(jìn)制轉(zhuǎn)義值或一個(gè)后向引用。如果 \nm 之前至少有is preceded by at least nm 個(gè)獲取得子表達(dá)式,則 nm 為后向引用。如果 \nm 之前至少有 n 個(gè)獲取,則 n 為一個(gè)后跟文字 m 的后向引用。如果前面的條件都不滿足,若 n 和 m 均為八進(jìn)制數(shù)字 (0-7),則 \nm 將匹配八進(jìn)制轉(zhuǎn)義值 nm。 |
\nml | 如果 n 為八進(jìn)制數(shù)字 (0-3),且 m 和 l 均為八進(jìn)制數(shù)字 (0-7),則匹配八進(jìn)制轉(zhuǎn)義值 nml。 |
\un | 匹配 n,其中 n 是一個(gè)用四個(gè)十六進(jìn)制數(shù)字表示的 Unicode 字符。例如, \u00A9 匹配版權(quán)符號(hào) (?)。 |
構(gòu)造正則表達(dá)式的方法和創(chuàng)建數(shù)學(xué)表達(dá)式的方法一樣。也就是用多種元字符與操作符將小的表達(dá)式結(jié)合在一起來創(chuàng)建更大的表達(dá)式。
可以通過在一對(duì)分隔符之間放入表達(dá)式模式的各種組件來構(gòu)造一個(gè)正則表達(dá)式。對(duì) JScript 而言,分隔符為一對(duì)正斜杠 (/) 字符。例如:
/expression/
對(duì) VBScript 而言,則采用一對(duì)引號(hào) ("") 來確定正則表達(dá)式的邊界。例如:
"expression"
在上面所示的兩個(gè)示例中,正則表達(dá)式模式 (expression) 均存儲(chǔ)在RegExp 對(duì)象的Pattern 屬性中。
正則表達(dá)式的組件可以是單個(gè)的字符、字符集合、字符范圍、字符間的選擇或者所有這些組件的任意組合。
在構(gòu)造正則表達(dá)式之后,就可以象數(shù)學(xué)表達(dá)式一樣來求值,也就是說,可以從左至右并按照一個(gè)優(yōu)先權(quán)順序來求值。
下表從最高優(yōu)先級(jí)到最低優(yōu)先級(jí)列出各種正則表達(dá)式操作符的優(yōu)先權(quán)順序:
操作符 | 描述 |
---|---|
\ | 轉(zhuǎn)義符 |
(), (?:), (?=), [] | 圓括號(hào)和方括號(hào) |
*, +, ?, {n}, {n,}, {n,m} | 限定符 |
^, $, \anymetacharacter | 位置和順序 |
| | “或”操作 |
普通字符由所有那些未顯式指定為元字符的打印和非打印字符組成。這包括所有的大寫和小寫字母字符,所有數(shù)字,所有標(biāo)點(diǎn)符號(hào)以及一些符號(hào)。
最簡(jiǎn)單的正則表達(dá)式是一個(gè)單獨(dú)的普通字符,可以匹配所搜索字符串中的該字符本身。例如,單字符模式 'A' 可以匹配所搜索字符串中任何位置出現(xiàn)的字母 'A'。這里有一些單字符正則表達(dá)式模式的示例:
/a/
/7/
/M/
等價(jià)的 VBScript 單字符正則表達(dá)式為:
"a"
"7"
"M"
可以將多個(gè)單字符組合在一起得到一個(gè)較大的表達(dá)式。例如,下面的 JScript 正則表達(dá)式不是別的,就是通過組合單字符表達(dá)式 'a'、'7'以及 'M' 所創(chuàng)建出來的一個(gè)表達(dá)式。
/a7M/
等價(jià)的 VBScript 表達(dá)式為:
"a7M"
請(qǐng)注意這里沒有連接操作符。所需要做的就是將一個(gè)字符放在了另一個(gè)字符后面。
有不少元字符在試圖對(duì)其進(jìn)行匹配時(shí)需要進(jìn)行特殊的處理。要匹配這些特殊字符,必須首先將這些字符轉(zhuǎn)義,也就是在前面使用一個(gè)反斜杠 (\)。下表給出了這些特殊字符及其含義:
特殊字符 | 說明 |
---|---|
$ | 匹配輸入字符串的結(jié)尾位置。如果設(shè)置了 RegExp 對(duì)象的 Multiline 屬性,則 $ 也匹配 '\n' 或 '\r'。要匹配 $ 字符本身,請(qǐng)使用 \$。 |
( ) | 標(biāo)記一個(gè)子表達(dá)式的開始和結(jié)束位置。子表達(dá)式可以獲取供以后使用。要匹配這些字符,請(qǐng)使用 \( 和 \)。 |
* | 匹配前面的子表達(dá)式零次或多次。要匹配 * 字符,請(qǐng)使用 \*。 |
+ | 匹配前面的子表達(dá)式一次或多次。要匹配 + 字符,請(qǐng)使用 \+。 |
. | 匹配除換行符 \n之外的任何單字符。要匹配 .,請(qǐng)使用 \。 |
[ | 標(biāo)記一個(gè)中括號(hào)表達(dá)式的開始。要匹配 [,請(qǐng)使用 \[。 |
? | 匹配前面的子表達(dá)式零次或一次,或指明一個(gè)非貪婪限定符。要匹配 ? 字符,請(qǐng)使用 \?。 |
\ | 將下一個(gè)字符標(biāo)記為或特殊字符、或原義字符、或后向引用、或八進(jìn)制轉(zhuǎn)義符。例如, 'n' 匹配字符 'n'。'\n' 匹配換行符。序列 '\\' 匹配 "\",而 '\(' 則匹配 "("。 |
^ | 匹配輸入字符串的開始位置,除非在方括號(hào)表達(dá)式中使用,此時(shí)它表示不接受該字符集合。要匹配 ^ 字符本身,請(qǐng)使用 \^。 |
{ | 標(biāo)記限定符表達(dá)式的開始。要匹配 {,請(qǐng)使用 \{。 |
| | 指明兩項(xiàng)之間的一個(gè)選擇。要匹配 |,請(qǐng)使用 \|。 |
有不少很有用的非打印字符,偶爾必須使用。下表顯示了用來表示這些非打印字符的轉(zhuǎn)義序列:
字符 | 含義 |
---|---|
\cx | 匹配由x指明的控制字符。例如, \cM 匹配一個(gè) Control-M 或回車符。 x 的值必須為 A-Z 或 a-z 之一。否則,將 c 視為一個(gè)原義的 'c' 字符。 |
\f | 匹配一個(gè)換頁符。等價(jià)于 \x0c 和 \cL。 |
\n | 匹配一個(gè)換行符。等價(jià)于 \x0a 和 \cJ。 |
\r | 匹配一個(gè)回車符。等價(jià)于 \x0d 和 \cM。 |
\s | 匹配任何空白字符,包括空格、制表符、換頁符等等。等價(jià)于 [ \f\n\r\t\v]。 |
\S | 匹配任何非空白字符。等價(jià)于 [^ \f\n\r\t\v]。 |
\t | 匹配一個(gè)制表符。等價(jià)于 \x09 和 \cI。 |
\v | 匹配一個(gè)垂直制表符。等價(jià)于 \x0b 和 \cK。 |
句點(diǎn) (.) 匹配一個(gè)字符串中任何單個(gè)的打印或非打印字符,除了換行符 (\n) 之外。下面的 JScript 正則表達(dá)式可以匹配 'aac'、'abc'、'acc'、'adc'如此等等,同樣也可以匹配 'a1c'、'a2c'、a-c'以及 a#c':
/a.c/
等價(jià)的 VBScript 正則表達(dá)式為:
"a.c"
如果試圖匹配一個(gè)包含文件名的字符串,其中句點(diǎn) (.) 是輸入字符串的一部分,則可以在正則表達(dá)式中的句點(diǎn)前面加上一個(gè)反斜杠 (\) 字符來實(shí)現(xiàn)這一要求。舉例來說,下面的 JScript 正則表達(dá)式就能匹配 'filename.ext':
/filename\.ext/
對(duì) VBScript 而言,等價(jià)的表達(dá)式如下所示:
"filename\.ext"
這些表達(dá)式仍然是相當(dāng)有限的。它們只允許匹配任何單字符。很多情況下,對(duì)從列表中匹配特殊字符十分有用。例如,如果輸入文字中包含用數(shù)字表示為Chapter 1, Chapter 2諸如此類的章節(jié)標(biāo)題,你可能需要找到這些章節(jié)標(biāo)題。
可以在一個(gè)方括號(hào) ([ 和 ]) 中放入一個(gè)或多個(gè)單字符,來創(chuàng)建一個(gè)待匹配的列表。如果字符被放入括號(hào)中括起來,則該列表稱為括號(hào)表達(dá)式。括號(hào)內(nèi)和其他任何地方一樣,普通字符代表其本身,也就是說,它們匹配輸入文字中出現(xiàn)的一處自己。大多數(shù)特殊字符在位于括號(hào)表達(dá)式中時(shí)都將失去其含義。這里有一些例外:
括號(hào)表達(dá)式中所包含的字符只匹配該括號(hào)表達(dá)式在正則表達(dá)式中所處位置的一個(gè)單字符。下面的 JScript 正則表達(dá)式可以匹配 'Chapter 1'、'Chapter 2'、'Chapter 3'、'Chapter 4' 以及 'Chapter 5':
/Chapter [12345]/
在 VBScript 中要匹配同樣的章節(jié)標(biāo)題,請(qǐng)使用下面的表達(dá)式:
"Chapter [12345]"
請(qǐng)注意單詞 'Chapter' 及后面的空格與括號(hào)內(nèi)的字符的位置關(guān)系是固定的。因此,括號(hào)表達(dá)式只用來指定滿足緊跟在單詞 'Chapter' 和一個(gè)空格之后的單字符位置的字符集合。這里是第九個(gè)字符位置。
如果希望使用范圍而不是字符本身來表示待匹配的字符,則可以使用連字符將該范圍的開始和結(jié)束字符分開。每個(gè)字符的字符值將決定其在一個(gè)范圍內(nèi)的相對(duì)順序。下面的 JScript 正則表達(dá)式包含了一個(gè)等價(jià)于上面所示的括號(hào)列表的范圍表達(dá)式。
/Chapter [1-5]/
VBScipt 中相同功能的表達(dá)式如下所示:
"Chapter [1-5]"
如果以這種方式指定范圍,則開始和結(jié)束值都包括在該范圍內(nèi)。有一點(diǎn)特別需要注意的是,在 Unicode 排序中起始值一定要在結(jié)束值之前。
如果想在括號(hào)表達(dá)式中包括連字符,則必須使用下述方法之一:
[\-]
[-a-z]
[a-z-]
[!--]
[!-~]
同樣,通過在列表開始處放置一個(gè)插入符(^),就可以查找所有不在列表或范圍中的字符。如果該插入符出現(xiàn)在列表的其他位置,則匹配其本身,沒有任何特殊含義。下面的 JScript 正則表達(dá)式匹配章節(jié)號(hào)大于 5 的章節(jié)標(biāo)題:
/Chapter [^12345]/
對(duì) VBScript 則使用:
"Chapter [^12345]"
在上面所示的示例中,表達(dá)式將匹配第九個(gè)位置處除1, 2, 3, 4, or 5 之外的任何數(shù)字字符。因此, 'Chapter 7' 為一個(gè)匹配,同樣 'Chapter 9' 也是如此。
上面的表達(dá)式可以使用連字符 (-) 表示。對(duì) JScript 為:
/Chapter [^1-5]/
或者,對(duì) VBScript 為:
"Chapter [^1-5]"
括號(hào)表達(dá)式的典型用法是指定對(duì)任何大寫或小寫字母字符或任何數(shù)字的匹配。下面的 JScript 表達(dá)式給出了這一匹配:
/[A-Za-z0-9]/
等價(jià)的 VBScript 表達(dá)式為:
"[A-Za-z0-9]"
有時(shí)候不知道要匹配多少字符。為了能適應(yīng)這種不確定性,正則表達(dá)式支持限定符的概念。這些限定符可以指定正則表達(dá)式的一個(gè)給定組件必須要出現(xiàn)多少次才能滿足匹配。
下表給出了各種限定符及其含義的說明:
字符 | 描述 |
---|---|
* | 匹配前面的子表達(dá)式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。 * 等價(jià)于{0,}。 |
+ | 匹配前面的子表達(dá)式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等價(jià)于 {1,}。 |
? | 匹配前面的子表達(dá)式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 中的"do" 。? 等價(jià)于 {0,1}。 |
{n} | n 是一個(gè)非負(fù)整數(shù)。匹配確定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的兩個(gè) o。 |
{n,} | n 是一個(gè)非負(fù)整數(shù)。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等價(jià)于 'o+'。'o{0,}' 則等價(jià)于 'o*'。 |
{n,m} | m 和 n 均為非負(fù)整數(shù),其中n <= m。最少匹配 n 次且最多匹配 m 次。劉, "o{1,3}" 將匹配 "fooooood" 中的前三個(gè) o。'o{0,1}' 等價(jià)于 'o?'。請(qǐng)注意在逗號(hào)和兩個(gè)數(shù)之間不能有空格。 |
對(duì)一個(gè)很大的輸入文檔而言,章節(jié)數(shù)很輕易就超過九章,因此需要有一種方法來處理兩位數(shù)或者三位數(shù)的章節(jié)號(hào)。限定符就提供了這個(gè)功能。下面的JScript 正則表達(dá)式可以匹配具有任何位數(shù)的章節(jié)標(biāo)題:
/Chapter [1-9][0-9]*/
下面的 VBScript 正則表達(dá)式執(zhí)行同樣的匹配:
"Chapter [1-9][0-9]*"
請(qǐng)注意限定符出現(xiàn)在范圍表達(dá)式之后。因此,它將應(yīng)用于所包含的整個(gè)范圍表達(dá)式,在本例中,只指定了從 0 到 9 的數(shù)字。
這里沒有使用 '+' 限定符,因?yàn)榈诙换蚝罄m(xù)位置上并不一定需要一個(gè)數(shù)字。同樣也沒有使用 '?' 字符,因?yàn)檫@將把章節(jié)數(shù)限制為只有兩位數(shù)字。在 'Chapter' 和空格字符之后至少要匹配一個(gè)數(shù)字。
如果已知章節(jié)數(shù)限制只有99 章,則可以使用下面的 JScript 表達(dá)式來指定至少有一位數(shù)字,但不超過兩個(gè)數(shù)字。
/Chapter [0-9]{1,2}/
對(duì) VBScript 可以使用下述正則表達(dá)式:
"Chapter [0-9]{1,2}"
上述表達(dá)式的缺點(diǎn)是如果有一個(gè)章節(jié)號(hào)大于 99,它仍只會(huì)匹配前兩位數(shù)字。另一個(gè)缺點(diǎn)是某些人可以創(chuàng)建一個(gè) Chapter 0,而且仍能匹配。一個(gè)更好的用來匹配兩位數(shù)的 JScript 表達(dá)式如下:
/Chapter [1-9][0-9]?/
或者
/Chapter [1-9][0-9]{0,1}/
對(duì) VBScript 而言,下述表達(dá)式與上面等價(jià):
"Chapter [1-9][0-9]?"
或者
"Chapter [1-9][0-9]{0,1}"
'*
'、 '+'
和 '?'
限定符都稱之為貪婪的,也就是說,他們盡可能多地匹配文字。有時(shí)這根本就不是所希望發(fā)生的情況。有時(shí)則正好希望最小匹配。
例如,你可能要搜索一個(gè) HTML 文檔來查找一處包含在 H1 標(biāo)記中的章節(jié)標(biāo)題。在文檔中該文字可能具有如下形式:
<H1>Chapter 1 – Introduction to Regular Expressions</H1>
下面的表達(dá)式匹配從開始的小于號(hào) (<) 到 H1 標(biāo)記結(jié)束處的大于號(hào)之間的所有內(nèi)容。
/<.*>/
VBScript 的正則表達(dá)式為:
"<.*>"
如果所要匹配的就是開始的 H1 標(biāo)記,則下述非貪婪地表達(dá)式就只匹配 <H1>。
/<.*?>/
或者
"<.*?>"
通過在 '*'、 '+' 或 '?' 限定符后放置 '?',該表達(dá)式就從貪婪匹配轉(zhuǎn)為了非貪婪或最小匹配。
到現(xiàn)在為止,所看到的示例都只考慮查找任何地方出現(xiàn)的章節(jié)標(biāo)題。出現(xiàn)的任何一個(gè)字符串 'Chapter' 后跟一個(gè)空格和一個(gè)數(shù)字可能是一個(gè)真正的章節(jié)標(biāo)題,也可能是對(duì)其他章節(jié)的交叉引用。由于真正的章節(jié)標(biāo)題總是出現(xiàn)在一行的開始,因此需要設(shè)計(jì)一個(gè)方法只查找標(biāo)題而不查找交叉引用。
定位符提供了這個(gè)功能。定位符可以將一個(gè)正則表達(dá)式固定在一行的開始或結(jié)束。也可以創(chuàng)建只在單詞內(nèi)或只在單詞的開始或結(jié)尾處出現(xiàn)的正則表達(dá)式。下表包含了正則表達(dá)式及其含義的列表:
字符 | 描述 |
---|---|
^ | 匹配輸入字符串的開始位置。如果設(shè)置了 RegExp 對(duì)象的 Multiline 屬性,^ 也匹配 '\n' 或 '\r' 之后的位置。 |
$ | 匹配輸入字符串的結(jié)束位置。如果設(shè)置了RegExp 對(duì)象的 Multiline 屬性,$ 也匹配 '\n' 或 '\r' 之前的位置。 |
\b | 匹配一個(gè)單詞邊界,也就是指單詞和空格間的位置。 |
\B | 匹配非單詞邊界。 |
不能對(duì)定位符使用限定符。因?yàn)樵谝粋€(gè)換行符或者單詞邊界的前面或后面不會(huì)有連續(xù)多個(gè)位置,因此諸如 '^*' 的表達(dá)式是不允許的。
要匹配一行文字開始位置的文字,請(qǐng)?jiān)谡齽t表達(dá)式的開始處使用 '^' 字符。不要把 '^' 的這個(gè)語法與其在括號(hào)表達(dá)式中的語法弄混。它們的語法根本不同。
要匹配一行文字結(jié)束位置的文字,請(qǐng)?jiān)谡齽t表達(dá)式的結(jié)束處使用 '$' 字符。
要在查找章節(jié)標(biāo)題時(shí)使用定位符,下面的 JScript 正則表達(dá)式將匹配位于一行的開始處最多有兩個(gè)數(shù)字的章節(jié)標(biāo)題:
/^Chapter [1-9][0-9]{0,1}/
VBScript 中相同功能的正則表達(dá)式如下:
"^Chapter [1-9][0-9]{0,1}"
一個(gè)真正的章節(jié)標(biāo)題不僅出現(xiàn)在一行的開始,而且這一行中也僅有這一個(gè)內(nèi)容,因此,它必然也位于一行的結(jié)束。下面的表達(dá)式確保所指定的匹配只匹配章節(jié)而不會(huì)匹配交叉引用。它是通過創(chuàng)建一個(gè)只匹配一行文字的開始和結(jié)束位置的正則表達(dá)式來實(shí)現(xiàn)的。
/^Chapter [1-9][0-9]{0,1}$/
對(duì) VBScript 則使用:
"^Chapter [1-9][0-9]{0,1}$"
匹配單詞邊界有少許不同,但卻給正則表達(dá)式增加了一個(gè)非常重要的功能。單詞邊界就是單詞和空格之間的位置。非單詞邊界就是其他任何位置。下面的 JScript 表達(dá)式將匹配單詞 'Chapter' 的前三個(gè)字符,因?yàn)樗鼈兂霈F(xiàn)在單詞邊界后:
/\bCha/
對(duì) VBScript 為:
"\bCha"
這里 '\b' 操作符的位置很關(guān)鍵。如果它位于要匹配的字符串的開始,則將查找位于單詞開頭處的匹配;如果它位于改字符串的末尾,則查找位于單詞結(jié)束處的匹配。例如,下面的表達(dá)式將匹配單詞 'Chapter' 中的 'ter',因?yàn)樗霈F(xiàn)在單詞邊界之前:
/ter\b/
以及
"ter\b"
下面的表達(dá)式將匹配 'apt',因?yàn)樗挥?'Chapter' 中間,但不會(huì)匹配 'aptitude' 中的'apt':
/\Bapt/
以及
"\Bapt"
這是因?yàn)樵趩卧~ 'Chapter' 中 'apt' 出現(xiàn)在非單詞邊界位置,而在單詞 'aptitude' 中位于單詞邊界位置。非單詞邊界操作符的位置不重要,因?yàn)槠ヅ渑c一個(gè)單詞的開頭或結(jié)尾無關(guān)。
選擇允許使用 '|' 字符來在兩個(gè)或多個(gè)候選項(xiàng)中進(jìn)行選擇。通過擴(kuò)展章節(jié)標(biāo)題的正則表達(dá)式,可以將其擴(kuò)充為不僅僅適用于章節(jié)標(biāo)題的表達(dá)式。不過,這可沒有想象的那么直接。在使用選擇時(shí),將匹配'|' 字符每邊最可能的表達(dá)式。你可能認(rèn)為下面的 JScript 和 VBScript 表達(dá)式將匹配位于一行的開始和結(jié)束位置且后跟一個(gè)或兩個(gè)數(shù)字的 'Chapter' 或 'Section':
/^Chapter|Section [1-9][0-9]{0,1}$/
"^Chapter|Section [1-9][0-9]{0,1}$"
不幸的是,真正的情況是上面所示的正則表達(dá)式要么匹配位于一行開始處的單詞 'Chapter',要么匹配一行結(jié)束處的后跟任何數(shù)字的 'Section'。如果輸入字符串為 'Chapter 22',上面的表達(dá)式將只匹配單詞 'Chapter'。如果輸入字符串為 'Section 22',則該表達(dá)式將匹配 'Section 22'。但這種結(jié)果不是我們此處的目的,因此必須有一種辦法來使正則表達(dá)式對(duì)于所要做的更易于響應(yīng),而且確實(shí)也有這種方法。
可以使用圓括號(hào)來限制選擇的范圍,也就是說明確該選擇只適用于這兩個(gè)單詞 'Chapter' 和 'Section'。不過,圓括號(hào)同樣也是難處理的,因?yàn)樗鼈円灿脕韯?chuàng)建子表達(dá)式,有些內(nèi)容將在后面關(guān)于子表達(dá)式的部分介紹。通過采用上面所示的正則表達(dá)式并在適當(dāng)位置添加圓括號(hào),就可以使該正則表達(dá)式既可以匹配 'Chapter 1',也可以匹配 'Section 3'。
下面的正則表達(dá)式使用圓括號(hào)將 'Chapter' 和 'Section' 組成一組,所以該表達(dá)式才能正確工作。對(duì) JScript 為:
/^(Chapter|Section) [1-9][0-9]{0,1}$/
對(duì) VBScript 為:
"^(Chapter|Section) [1-9][0-9]{0,1}$"
這些表達(dá)式工作正確,只是產(chǎn)生了一個(gè)有趣的副產(chǎn)品。在 'Chapter|Section' 兩邊放置圓括號(hào)建立了適當(dāng)?shù)木幗M,但也導(dǎo)致兩個(gè)待匹配單詞之一都被捕獲供今后使用。由于在上面所示的表達(dá)式中只有一組圓括號(hào),因此只能有一個(gè)捕獲的 submatch??梢允褂?VBScript 的Submatches 集合或者JScript 中RegExp 對(duì)象的 $1-$9 屬性來引用這個(gè)子匹配。
有時(shí)捕獲一個(gè)子匹配是所希望的,有時(shí)則是不希望的。在說明所示的示例中,真正想做的就是使用圓括號(hào)對(duì)單詞 'Chapter' 或 'Section' 之間的選擇編組。并不希望在后面再引用該匹配。實(shí)際上,除非真的是需要捕獲子匹配,否則請(qǐng)不要使用。由于不需要花時(shí)間和內(nèi)存來存儲(chǔ)那些子匹配,這種正則表達(dá)式的效率將更高。
可以在正則表達(dá)式模式圓括號(hào)內(nèi)部的前面使用 '?:'來防止存儲(chǔ)該匹配供今后使用。對(duì)上面所示正則表達(dá)式的下述修改提供了免除子匹配存儲(chǔ)的相同功能。對(duì) JScript:
/^(?:Chapter|Section) [1-9][0-9]{0,1}$/
對(duì) VBScript:
"^(?:Chapter|Section) [1-9][0-9]{0,1}$"
除了 '?:' 元字符,還有兩個(gè)非捕獲元字符用于稱之為預(yù)查的匹配。一個(gè)為正向預(yù)查,用 ?= 表示, 在任何開始匹配圓括號(hào)內(nèi)的正則表達(dá)式模式的位置來匹配搜索字符串。一個(gè)為負(fù)向預(yù)查,用 '?!' 表示,在任何開始不匹配該正則表達(dá)式模式的位置來匹配搜索字符串。
例如,假定有一個(gè)包含引用有 Windows 3.1、Windows 95、Windows 98 以及 Windows NT 的文檔。進(jìn)一步假設(shè)需要更新該文檔,方法是查找所有對(duì) Windows 95、Windows 98 以及 Windows NT 的引用,并將這些引用更改為 Windows 2000??梢允褂孟旅娴?JScript 正則表達(dá)式,這是一個(gè)正向預(yù)查,來匹配 Windows 95、Windows 98 以及 Windows NT:
/Windows(?=95 |98 |NT )/
在 VBScript 要進(jìn)行同樣的匹配可以使用下述表達(dá)式:
"Windows(?=95 |98 |NT )"
找到一個(gè)匹配后,緊接匹配到的文字(而不包括預(yù)查中使用的字符)就開始對(duì)下一次匹配的搜索。例如,如果上面所示的表達(dá)式匹配到 'Windows 98',則將從 'Windows' 而不是 '98' 之后繼續(xù)查找。
正則表達(dá)式一個(gè)最重要的特性就是將匹配成功的模式的某部分進(jìn)行存儲(chǔ)供以后使用這一能力。請(qǐng)回想一下,對(duì)一個(gè)正則表達(dá)式模式或部分模式兩邊添加圓括號(hào)將導(dǎo)致這部分表達(dá)式存儲(chǔ)到一個(gè)臨時(shí)緩沖區(qū)中??梢允褂梅遣东@元字符 '?:', '?=', or '?!' 來忽略對(duì)這部分正則表達(dá)式的保存。
所捕獲的每個(gè)子匹配都按照在正則表達(dá)式模式中從左至右所遇到的內(nèi)容存儲(chǔ)。存儲(chǔ)子匹配的緩沖區(qū)編號(hào)從 1 開始,連續(xù)編號(hào)直至最大 99 個(gè)子表達(dá)式。每個(gè)緩沖區(qū)都可以使用 '\n' 訪問,其中 n 為一個(gè)標(biāo)識(shí)特定緩沖區(qū)的一位或兩位十進(jìn)制數(shù)。
后向引用一個(gè)最簡(jiǎn)單,最有用的應(yīng)用是提供了確定文字中連續(xù)出現(xiàn)兩個(gè)相同單詞的位置的能力。請(qǐng)看下面的句子:
Is is the cost of of gasoline going up up?
根據(jù)所寫內(nèi)容,上面的句子明顯存在單詞多次重復(fù)的問題。如果能有一種方法無需查找每個(gè)單詞的重復(fù)現(xiàn)象就能修改該句子就好了。下面的 JScript 正則表達(dá)式使用一個(gè)子表達(dá)式就可以實(shí)現(xiàn)這一功能。
/\b([a-z]+) \1\b/gi
等價(jià)的 VBScript 表達(dá)式為:
"\b([a-z]+) \1\b"
在這個(gè)示例中,子表達(dá)式就是圓括號(hào)之間的每一項(xiàng)。所捕獲的表達(dá)式包括一個(gè)或多個(gè)字母字符,即由'[a-z]+' 所指定的。該正則表達(dá)式的第二部分是對(duì)前面所捕獲的子匹配的引用,也就是由附加表達(dá)式所匹配的第二次出現(xiàn)的單詞。'\1'用來指定第一個(gè)子匹配。單詞邊界元字符確保只檢測(cè)單獨(dú)的單詞。如果不這樣,則諸如 "is issued" 或 "this is" 這樣的短語都會(huì)被該表達(dá)式不正確地識(shí)別。
在 JScript 表達(dá)式中,正則表達(dá)式后面的全局標(biāo)志 ('g') 表示該表達(dá)式將用來在輸入字符串中查找盡可能多的匹配。大小寫敏感性由表達(dá)式結(jié)束處的大小寫敏感性標(biāo)記 ('i') 指定。多行標(biāo)記指定可能出現(xiàn)在換行符的兩端的潛在匹配。對(duì) VBScript 而言,在表達(dá)式中不能設(shè)置各種標(biāo)記,但必須使用 RegExp 對(duì)象的屬性來顯式設(shè)置。
使用上面所示的正則表達(dá)式,下面的 JScript 代碼可以使用子匹配信息,在一個(gè)文字字符串中將連續(xù)出現(xiàn)兩次的相同單詞替換為一個(gè)相同的單詞:
var ss = "Is is the cost of of gasoline going up up?.\n"; var re = /\b([a-z]+) \1\b/gim; //
創(chuàng)建正則表達(dá)式樣式. var rv = ss.replace(re,"$1"); //
用一個(gè)單詞替代兩個(gè)單詞.
最接近的等價(jià) VBScript 代碼如下:
Dim ss, re, rv
ss = "Is is the cost of of gasoline going up up?." & vbNewLine
Set re = New RegExp
re.Pattern = "\b([a-z]+) \1\b"
re.Global = True
re.IgnoreCase = True
re.MultiLine = True
rv = re.Replace(ss,"$1")
請(qǐng)注意在 VBScript 代碼中,全局、大小寫敏感性以及多行標(biāo)記都是使用 RegExp 對(duì)象的適當(dāng)屬性來設(shè)置的。
在replace 方法中使用 $1 來引用所保存的第一個(gè)子匹配。如果有多個(gè)子匹配,則可以用 $2, $3 等繼續(xù)引用。
后向引用的另一個(gè)用途是將一個(gè)通用資源指示符 (URI) 分解為組件部分。假定希望將下述的URI 分解為協(xié)議 (ftp, http, etc),域名地址以及頁面/路徑:
http://msdn.microsoft.com:80/scripting/default.htm
下面的正則表達(dá)式可以提供這個(gè)功能。對(duì) JScript,為:
/(\w+):\/\/([^/:]+)(:\d*)?([^# ]*)/
對(duì) VBScript 為:
"(\w+):\/\/([^/:]+)(:\d*)?([^# ]*)"
第一個(gè)附加子表達(dá)式是用來捕獲該 web 地址的協(xié)議部分。該子表達(dá)式匹配位于一個(gè)冒號(hào)和兩個(gè)正斜杠之前的任何單詞。第二個(gè)附加子表達(dá)式捕獲該地址的域名地址。該子表達(dá)式匹配不包括 '^'、 '/' 或 ':' 字符的任何字符序列。第三個(gè)附加子表達(dá)式捕獲網(wǎng)站端口號(hào)碼,如果指定了該端口號(hào)。該子表達(dá)式匹配后跟一個(gè)冒號(hào)的零或多個(gè)數(shù)字。最后,第四個(gè)附加子表達(dá)式捕獲由該 web 地址指定的路徑以及\或者頁面信息。該子表達(dá)式匹配一個(gè)和多個(gè)除'#' 或空格之外的字符。
將該正則表達(dá)式應(yīng)用于上面所示的 URI 后,子匹配包含下述內(nèi)容:
RegExp.$1 包含 "http"
RegExp.$2 包含 "msdn.microsoft.com"
RegExp.$3 包含 ":80"
RegExp.$4 包含 "/scripting/default.htm"
正則表達(dá)式(regular expression)對(duì)象包含一個(gè)正則表達(dá)式模式(pattern)。它具有用正則表達(dá)式模式去匹配或代替一個(gè)串(string)中特定字符(或字符集合)的屬性(properties)和方法(methods)。 要為一個(gè)單獨(dú)的正則表達(dá)式添加屬性,可以使用正則表達(dá)式構(gòu)造函數(shù)(constructor function),無論何時(shí)被調(diào)用的預(yù)設(shè)置的正則表達(dá)式擁有靜態(tài)的屬性(the predefined RegExp object has static properties that are set whenever any regular expression is used, 我不知道我翻得對(duì)不對(duì),將原文列出,請(qǐng)自行翻譯)。
[注意] 文本格式的參數(shù)不用引號(hào),而在用構(gòu)造函數(shù)時(shí)的參數(shù)需要引號(hào)。如:/ab+c/i new RegExp("ab+c","i")是實(shí)現(xiàn)一樣的功能。在構(gòu)造函數(shù)中,一些特殊字符需要進(jìn)行轉(zhuǎn)意(在特殊字符前加"\")。如:re = new RegExp("\\w+")
正則表達(dá)式中的特殊字符
|
說了這么多了,我們來看一些正則表達(dá)式的實(shí)際應(yīng)用的例子:
E-mail地址驗(yàn)證:
正則表達(dá)式對(duì)象的屬性及方法
function test_email(strEmail) {
var myReg = /^[_a-z0-9]+@([_a-z0-9]+\.)+[a-z0-9]{2,3}$/;
if(myReg.test(strEmail)) return true;
return false;
}
HTML代碼的屏蔽
function mask_HTMLCode(strInput) {
var myReg = /<(\w+)>/;
return strInput.replace(myReg, "<$1>");
}
預(yù)定義的正則表達(dá)式擁有有以下靜態(tài)屬性:input, multiline, lastMatch, lastParen, leftContext, rightContext和$1到$9。其中input和multiline可以預(yù)設(shè)置。其他屬性的值在執(zhí)行過exec或test方法后被根據(jù)不同條件賦以不同的值。許多屬性同時(shí)擁有長(zhǎng)和短(perl風(fēng)格)的兩個(gè)名字,并且,這兩個(gè)名字指向同一個(gè)值。(JavaScript模擬perl的正則表達(dá)式)
正則表達(dá)式對(duì)象的屬性
屬性 含義 $1...$9 如果它(們)存在,是匹配到的子串 $_ 參見input $* 參見multiline $& 參見lastMatch $+ 參見lastParen $` 參見leftContext $’ 參見rightContext constructor 創(chuàng)建一個(gè)對(duì)象的一個(gè)特殊的函數(shù)原型 global 是否在整個(gè)串中匹配(bool型) ignoreCase 匹配時(shí)是否忽略大小寫(bool型) input 被匹配的串 lastIndex 最后一次匹配的索引 lastParen 最后一個(gè)括號(hào)括起來的子串 leftContext 最近一次匹配以左的子串 multiline 是否進(jìn)行多行匹配(bool型) prototype 允許附加屬性給對(duì)象 rightContext 最近一次匹配以右的子串 source 正則表達(dá)式模式 lastIndex 最后一次匹配的索引
正則表達(dá)式對(duì)象的方法
例子
方法 含義 compile 正則表達(dá)式比較 exec 執(zhí)行查找 test 進(jìn)行匹配 toSource 返回特定對(duì)象的定義(literal representing),其值可用來創(chuàng)建一個(gè)新的對(duì)象。重載Object.toSource方法得到的。 toString 返回特定對(duì)象的串。重載Object.toString方法得到的。 valueOf 返回特定對(duì)象的原始值。重載Object.valueOf方法得到
<script language = "JavaScript">
var myReg = /(\w+)\s(\w+)/;
var str = "John Smith";
var newstr = str.replace(myReg, "$2, $1");
document.write(newstr);
</script>
將輸出"Smith, John"
javascript正則表達(dá)式檢驗(yàn)
/*********************************************************************************
* EO_JSLib.js
* javascript正則表達(dá)式檢驗(yàn)
**********************************************************************************/
//校驗(yàn)是否全由數(shù)字組成
function isDigit(s)
{
var patrn=/^[0-9]{1,20}$/;
if (!patrn.exec(s)) return false
return true
}
//校驗(yàn)登錄名:只能輸入5-20個(gè)以字母開頭、可帶數(shù)字、“_”、“.”的字串
function isRegisterUserName(s)
{
var patrn=/^[a-zA-Z]{1}([a-zA-Z0-9]|[._]){4,19}$/;
if (!patrn.exec(s)) return false
return true
}
//校驗(yàn)用戶姓名:只能輸入1-30個(gè)以字母開頭的字串
function isTrueName(s)
{
var patrn=/^[a-zA-Z]{1,30}$/;
if (!patrn.exec(s)) return false
return true
}
//校驗(yàn)密碼:只能輸入6-20個(gè)字母、數(shù)字、下劃線
function isPasswd(s)
{
var patrn=/^(\w){6,20}$/;
if (!patrn.exec(s)) return false
return true
}
//校驗(yàn)普通電話、傳真號(hào)碼:可以“+”開頭,除數(shù)字外,可含有“-”
function isTel(s)
{
//var patrn=/^[+]{0,1}(\d){1,3}[ ]?([-]?(\d){1,12})+$/;
var patrn=/^[+]{0,1}(\d){1,3}[ ]?([-]?((\d)|[ ]){1,12})+$/;
if (!patrn.exec(s)) return false
return true
}
//校驗(yàn)手機(jī)號(hào)碼:必須以數(shù)字開頭,除數(shù)字外,可含有“-”
function isMobil(s)
{
var patrn=/^[+]{0,1}(\d){1,3}[ ]?([-]?((\d)|[ ]){1,12})+$/;
if (!patrn.exec(s)) return false
return true
}
//校驗(yàn)郵政編碼
function isPostalCode(s)
{
//var patrn=/^[a-zA-Z0-9]{3,12}$/;
var patrn=/^[a-zA-Z0-9 ]{3,12}$/;
if (!patrn.exec(s)) return false
return true
}
//校驗(yàn)搜索關(guān)鍵字
function isSearch(s)
{
var patrn=/^[^`~!@#$%^&*()+=|\\\][\]\{\}:;'\,.<>/?]{1}[^`~!@$%^&()+=|\\\][\]\{\}:;'\,.<>?]{0,19}$/;
if (!patrn.exec(s)) return false
return true
}
function isIP(s) //by zergling
{
var patrn=/^[0-9.]{1,20}$/;
if (!patrn.exec(s)) return false
return true
}
JAVA正則表達(dá)式4種常用功能 正則表達(dá)式在字符串處理上有著強(qiáng)大的功能,sun在jdk1.4加入了對(duì)它的支持 下面簡(jiǎn)單的說下它的4種常用功能: 查詢: 以下是代碼片段: String str="abc efg ABC"; String regEx="a|f"; //表示a或f Pattern p=Pattern.compile(regEx); Matcher m=p.matcher(str); boolean rs=m.find(); 如果str中有regEx,那么rs為true,否則為flase。如果想在查找時(shí)忽略大小寫,則可以寫成Pattern p=Pattern.compile(regEx,Pattern.CASE_INSENSITIVE); 提取: 以下是代碼片段: String regEx=".+\(.+)$"; String str="c:\dir1\dir2\name.txt"; Pattern p=Pattern.compile(regEx); Matcher m=p.matcher(str); boolean rs=m.find(); for(int i=1;i<=m.groupCount();i++){ System.out.println(m.group(i)); } 以上的執(zhí)行結(jié)果為name.txt,提取的字符串儲(chǔ)存在m.group(i)中,其中i最大值為m.groupCount(); 分割: 以下是代碼片段: String regEx="::"; Pattern p=Pattern.compile(regEx); String[] r=p.split("xd::abc::cde"); 執(zhí)行后,r就是{"xd","abc","cde"},其實(shí)分割時(shí)還有跟簡(jiǎn)單的方法: String str="xd::abc::cde"; String[] r=str.split("::"); 替換(刪除): 以下是代碼片段: String regEx="a+"; //表示一個(gè)或多個(gè)a Pattern p=Pattern.compile(regEx); Matcher m=p.matcher("aaabbced a ccdeaa"); String s=m.replaceAll("A"); 結(jié)果為"Abbced A ccdeA" 如果寫成空串,既可達(dá)到刪除的功能,比如: String s=m.replaceAll(""); 結(jié)果為"bbced ccde" 附: \D 等於 [^0-9] 非數(shù)字 \s 等於 [ \t\n\x0B\f ] 空白字元 \S 等於 [^ \t\n\x0B\f ] 非空白字元 \w 等於 [a-zA-Z_0-9] 數(shù)字或是英文字 \W 等於 [^a-zA-Z_0-9] 非數(shù)字與英文字 ^ 表示每行的開頭 $ 表示每行的結(jié)尾 |
原著:Steve Mansour
sman@scruznet.com
Revised: June 5, 1999
(copied by jm /at/ jmason.org from http://www.scruz.net/%7esman/regexp.htm, after the original disappeared! )
翻譯:Neo Lee
neo.lee@gmail.com
2004年10月16日
譯者按:原文因?yàn)槟甏眠h(yuǎn),文中很多鏈接早已過期(主要是關(guān)于vi、sed等工具的介紹和手冊(cè)),本譯文中已將此類鏈接刪除,如需檢查這些鏈接可以查看上面鏈接的原文。除此之外基本照原文直譯,括號(hào)中有“譯者按”的部分是譯者補(bǔ)充的說明。如有內(nèi)容方面的問題請(qǐng)直接和Steve Mansor聯(lián)系,當(dāng)然,如果你只寫中文,也可以和我聯(lián)系。
什么是正則表達(dá)式
范例
簡(jiǎn)單
中級(jí)(神奇的咒語)
困難(不可思議的象形文字)
不同工具中的正則表達(dá)式
我們將在如下的章節(jié)中利用一些例子來解釋正則表達(dá)式的用法,絕大部分的例子是基于vi中的文本替換命令和grep文件搜索命令來書寫的,不過它們都是比較典型的例子,其中的概念可以在sed、awk、perl和其他支持正則表達(dá)式的編程語言中使用。你可以看看不同工具中的正則表達(dá)式這一節(jié),其中有一些在別的工具中使用正則表達(dá)式的例子。還有一個(gè)關(guān)于vi中文本替換命令(s)的簡(jiǎn)單說明附在文后供參考。
在最簡(jiǎn)單的情況下,一個(gè)正則表達(dá)式看上去就是一個(gè)普通的查找串。例如,正則表達(dá)式"testing"中沒有包含任何元字符,,它可以匹配"testing"和"123testing"等字符串,但是不能匹配"Testing"。
要想真正的用好正則表達(dá)式,正確的理解元字符是最重要的事情。下表列出了所有的元字符和對(duì)它們的一個(gè)簡(jiǎn)短的描述。
元字符 | 描述 | |
---|---|---|
|
| |
|
匹配任何單個(gè)字符。例如正則表達(dá)式r.t匹配這些字符串:rat、rut、r t,但是不匹配root。 | |
|
匹配行結(jié)束符。例如正則表達(dá)式weasel$ 能夠匹配字符串"He's a weasel"的末尾,但是不能匹配字符串"They are a bunch of weasels."。 | |
|
匹配一行的開始。例如正則表達(dá)式^When in能夠匹配字符串"When in the course of human events"的開始,但是不能匹配"What and When in the"。 | |
|
匹配0或多個(gè)正好在它之前的那個(gè)字符。例如正則表達(dá)式.*意味著能夠匹配任意數(shù)量的任何字符。 | |
|
這是引用府,用來將這里列出的這些元字符當(dāng)作普通的字符來進(jìn)行匹配。例如正則表達(dá)式$被用來匹配美元符號(hào),而不是行尾,類似的,正則表達(dá)式.用來匹配點(diǎn)字符,而不是任何字符的通配符。 | |
[c1-c2] [^c1-c2] |
匹配括號(hào)中的任何一個(gè)字符。例如正則表達(dá)式r[aou]t匹配rat、rot和rut,但是不匹配ret??梢栽诶ㄌ?hào)中使用連字符-來指定字符的區(qū)間,例如正則表達(dá)式[0-9]可以匹配任何數(shù)字字符;還可以制定多個(gè)區(qū)間,例如正則表達(dá)式[A-Za-z]可以匹配任何大小寫字母。另一個(gè)重要的用法是“排除”,要想匹配除了指定區(qū)間之外的字符——也就是所謂的補(bǔ)集——在左邊的括號(hào)和第一個(gè)字符之間使用^字符,例如正則表達(dá)式[^269A-Z] 將匹配除了2、6、9和所有大寫字母之外的任何字符。 | |
|
匹配詞(word)的開始(<)和結(jié)束(>)。例如正則表達(dá)式<the能夠匹配字符串"for the wise"中的"the",但是不能匹配字符串"otherwise"中的"the"。注意:這個(gè)元字符不是所有的軟件都支持的。 | |
|
將 ( 和 ) 之間的表達(dá)式定義為“組”(group),并且將匹配這個(gè)表達(dá)式的字符保存到一個(gè)臨時(shí)區(qū)域(一個(gè)正則表達(dá)式中最多可以保存9個(gè)),它們可以用 1 到9 的符號(hào)來引用。 | |
|
將兩個(gè)匹配條件進(jìn)行邏輯“或”(Or)運(yùn)算。例如正則表達(dá)式(him|her) 匹配"it belongs to him"和"it belongs to her",但是不能匹配"it belongs to them."。注意:這個(gè)元字符不是所有的軟件都支持的。 | |
|
匹配1或多個(gè)正好在它之前的那個(gè)字符。例如正則表達(dá)式9+匹配9、99、999等。注意:這個(gè)元字符不是所有的軟件都支持的。 | |
|
匹配0或1個(gè)正好在它之前的那個(gè)字符。注意:這個(gè)元字符不是所有的軟件都支持的。 | |
{i,j} |
匹配指定數(shù)目的字符,這些字符是在它之前的表達(dá)式定義的。例如正則表達(dá)式A[0-9]{3} 能夠匹配字符"A"后面跟著正好3個(gè)數(shù)字字符的串,例如A123、A348等,但是不匹配A1234。而正則表達(dá)式[0-9]{4,6} 匹配連續(xù)的任意4個(gè)、5個(gè)或者6個(gè)數(shù)字字符。注意:這個(gè)元字符不是所有的軟件都支持的。 |
最簡(jiǎn)單的元字符是點(diǎn),它能夠匹配任何單個(gè)字符(注意不包括新行符)。假定有個(gè)文件test.txt包含以下幾行內(nèi)容:
要想匹配行首的字符要使用抑揚(yáng)字符(^)——又是也被叫做插入符。例如,想找到text.txt中行首"he"打頭的行,你可能會(huì)先用簡(jiǎn)單表達(dá)式he,但是這會(huì)匹配第三行的the,所以要使用正則表達(dá)式^he,它只匹配在行首出現(xiàn)的h。
有時(shí)候指定“除了×××都匹配”會(huì)比較容易達(dá)到目的,當(dāng)抑揚(yáng)字符(^)出現(xiàn)在方括號(hào)中是,它表示“排除”,例如要匹配he ,但是排除前面是t or s的情性(也就是the和she),可以使用:[^st]he。
可以使用方括號(hào)來指定多個(gè)字符區(qū)間。例如正則表達(dá)式[A-Za-z]匹配任何字母,包括大寫和小寫的;正則表達(dá)式[A-Za-z][A-Za-z]* 匹配一個(gè)字母后面接著0或者多個(gè)字母(大寫或者小寫)。當(dāng)然我們也可以用元字符+做到同樣的事情,也就是:[A-Za-z]+ ,和[A-Za-z][A-Za-z]*完全等價(jià)。但是要注意元字符+ 并不是所有支持正則表達(dá)式的程序都支持的。關(guān)于這一點(diǎn)可以參考后面的正則表達(dá)式語法支持情況。
要指定特定數(shù)量的匹配,要使用大括號(hào)(注意必須使用反斜杠來轉(zhuǎn)義)。想匹配所有100和1000的實(shí)例而排除10和10000,可以使用:10{2,3},這個(gè)正則表達(dá)式匹配數(shù)字1后面跟著2或者3個(gè)0的模式。在這個(gè)元字符的使用中一個(gè)有用的變化是忽略第二個(gè)數(shù)字,例如正則表達(dá)式0{3,} 將匹配至少3個(gè)連續(xù)的0。
這里有一些有代表性的、比較簡(jiǎn)單的例子。
vi 命令 | 作用 |
|
|
:%s/ */ /g | 把一個(gè)或者多個(gè)空格替換為一個(gè)空格。 |
:%s/ *$// | 去掉行尾的所有空格。 |
:%s/^/ / | 在每一行頭上加入一個(gè)空格。 |
:%s/^[0-9][0-9]* // | 去掉行首的所有數(shù)字字符。 |
:%s/b[aeio]g/bug/g | 將所有的bag、beg、big和bog改為bug。 |
:%s/t([aou])g/h1t/g | 將所有tag、tog和tug分別改為hat、hot和hug(注意用group的用法和使用1引用前面被匹配的字符)。 |
將所有方法foo(a,b,c)的實(shí)例改為foo(b,a,c)。這里a、b和c可以是任何提供給方法foo()的參數(shù)。也就是說我們要實(shí)現(xiàn)這樣的轉(zhuǎn)換:
之前 | 之后 | |
foo(10,7,2) | foo(7,10,2) | |
foo(x+13,y-2,10) | foo(y-2,x+13,10) | |
foo( bar(8), x+y+z, 5) | foo( x+y+z, bar(8), 5) |
下面這條替換命令能夠?qū)崿F(xiàn)這一魔法:
現(xiàn)在讓我們把它打散來加以分析。寫出這個(gè)表達(dá)式的基本思路是找出foo()和它的括號(hào)中的三個(gè)參數(shù)的位置。第一個(gè)參數(shù)是用這個(gè)表達(dá)式來識(shí)別的::([^,]*),我們可以從里向外來分析它:
[^,] | 除了逗號(hào)之外的任何字符 | |
[^,]* | 0或者多個(gè)非逗號(hào)字符 | |
([^,]*) | 將這些非逗號(hào)字符標(biāo)記為1,這樣可以在之后的替換模式表達(dá)式中引用它 | |
([^,]*), | 我們必須找到0或者多個(gè)非逗號(hào)字符后面跟著一個(gè)逗號(hào),并且非逗號(hào)字符那部分要標(biāo)記出來以備后用。 |
現(xiàn)在正是指出一個(gè)使用正則表達(dá)式常見錯(cuò)誤的最佳時(shí)機(jī)。為什么我們要使用[^,]*這樣的一個(gè)表達(dá)式,而不是更加簡(jiǎn)單直接的寫法,例如:.*,來匹配第一個(gè)參數(shù)呢?設(shè)想我們使用模式.*來匹配字符串"10,7,2",它應(yīng)該匹配"10,"還是"10,7,"?為了解決這個(gè)兩義性(ambiguity),正則表達(dá)式規(guī)定一律按照最長(zhǎng)的串來,在上面的例子中就是"10,7,",顯然這樣就找出了兩個(gè)參數(shù)而不是我們期望的一個(gè)。所以,我們要使用[^,]*來強(qiáng)制取出第一個(gè)逗號(hào)之前的部分。
這個(gè)表達(dá)式我們已經(jīng)分析到了:foo(([^,]*),這一段可以簡(jiǎn)單的翻譯為“當(dāng)你找到foo(就把其后直到第一個(gè)逗號(hào)之前的部分標(biāo)記為1”。然后我們使用同樣的辦法標(biāo)記第二個(gè)參數(shù)為2。對(duì)第三個(gè)參數(shù)的標(biāo)記方法也是一樣,只是我們要搜索所有的字符直到右括號(hào)。我們并沒有必要去搜索第三個(gè)參數(shù),因?yàn)槲覀儾恍枰{(diào)整它的位置,但是這樣的模式能夠保證我們只去替換那些有三個(gè)參數(shù)的foo()方法調(diào)用,在foo()是一個(gè)重載(overoading)方法時(shí)這種明確的模式往往是比較保險(xiǎn)的。然后,在替換部分,我們找到foo()的對(duì)應(yīng)實(shí)例,然后利用標(biāo)記好的部分進(jìn)行替換,是的第一和第二個(gè)參數(shù)交換位置。
這里有幾行我們現(xiàn)在的數(shù)據(jù):
下面就是第一個(gè)替換命令:
下面這個(gè)替換命令則用來去除空格:
Billy tried really hard而你想把"really"、"really really",以及任意數(shù)量連續(xù)出現(xiàn)的"really"字符串換成一個(gè)簡(jiǎn)單的"very"(simple is good!),那么以下命令:
Sally tried really really hard
Timmy tried really really really hard
Johnny tried really really really really hard
:%s/(really )(really )*/very /就會(huì)把上述的文本變成:
Billy tried very hard表達(dá)式(really )*匹配0或多個(gè)連續(xù)的"really "(注意結(jié)尾有個(gè)空格),而(really )(really )* 匹配1個(gè)或多個(gè)連續(xù)的"really "實(shí)例。
Sally tried very hard
Timmy tried very hard
Johnny tried very hard
當(dāng)然,你也可以在Visual C++編輯器中使用RE。選擇Edit->Replace,然后選擇"Regular expression"選擇框,F(xiàn)ind What輸入框?qū)?yīng)上面介紹的vi命令:%s/pat1/pat2/g中的pat1部分,而Replace輸入框?qū)?yīng)pat2部分。但是,為了得到vi的執(zhí)行范圍和g選項(xiàng),你要使用Replace All或者適當(dāng)?shù)氖止ind Next and Replace(譯者按:知道為啥有人罵微軟弱智了吧,雖然VC中可以選中一個(gè)范圍的文本,然后在其中執(zhí)行替換,但是總之不夠vi那么靈活和典雅)。
Sed是Stream EDitor的縮寫,是Unix下常用的基于文件和管道的編輯工具,可以在手冊(cè)中得到關(guān)于sed的詳細(xì)信息。
這里是一些有趣的sed腳本,假定我們正在處理一個(gè)叫做price.txt的文件。注意這些編輯并不會(huì)改變?cè)次募?,sed只是處理源文件的每一行并把結(jié)果顯示在標(biāo)準(zhǔn)輸出中(當(dāng)然很容易使用重定向來定制):
sed腳本 | 描述 | |
|
| |
sed 's/^$/d' price.txt | 刪除所有空行 | |
sed 's/^[ t]*$/d' price.txt | 刪除所有只包含空格或者制表符的行 | |
sed 's/"http://g' price.txt | 刪除所有引號(hào) |
在Aho,Weinberger和Kernighan的書The AWK Programming Language中有很多很好的awk的例子,請(qǐng)不要讓下面這些微不足道的腳本例子限制你對(duì)awk強(qiáng)大能力的理解。我們同樣假定我們針對(duì)price.txt文件進(jìn)行處理,跟sed一樣,awk也只是把結(jié)果顯示在終端上。
awk腳本 | 描述 | |
|
| |
awk '$0 !~ /^$/' price.txt | 刪除所有空行 | |
awk 'NF > 0' price.txt | awk中一個(gè)更好的刪除所有行的辦法 | |
awk '$2 ~ /^[JT]/ {print $3}' price.txt | 打印所有第二個(gè)字段是'J'或者'T'打頭的行中的第三個(gè)字段 | |
awk '$2 !~ /[Mm]isc/ {print $3 + $4}' price.txt | 針對(duì)所有第二個(gè)字段不包含'Misc'或者'misc'的行,打印第3和第4列的和(假定為數(shù)字) | |
awk '$3 !~ /^[0-9]+.[0-9]*$/ {print $0}' price.txt | 打印所有第三個(gè)字段不是數(shù)字的行,這里數(shù)字是指d.d或者d這樣的形式,其中d是0到9的任何數(shù)字 | |
awk '$2 ~ /John|Fred/ {print $0}' price.txt | 如果第二個(gè)字段包含'John'或者'Fred'則打印整行 |
下面的例子中我們假定在文件phone.txt中包含以下的文本,——其格式是姓加一個(gè)逗號(hào),然后是名,然后是一個(gè)制表符,然后是電話號(hào)碼:
Francis, John 5-3871
Wong, Fred 4-4123
Jones, Thomas 1-4122
Salazar, Richard 5-2522
grep命令 | 描述 | |
|
| |
grep 't5-...1' phone.txt | 把所有電話號(hào)碼以5開頭以1結(jié)束的行打印出來,注意制表符是用t表示的 | |
grep '^S[^ ]* R' phone.txt | 打印所有姓以S打頭和名以R打頭的行 | |
grep '^[JW]' phone.txt | 打印所有姓開頭是J或者W的行 | |
grep ', ....t' phone.txt | 打印所有姓是4個(gè)字符的行,注意制表符是用t表示的 | |
grep -v '^[JW]' phone.txt | 打印所有不以J或者W開頭的行 | |
grep '^[M-Z]' phone.txt | 打印所有姓的開頭是M到Z之間任一字符的行 | |
grep '^[M-Z].*[12]' phone.txt | 打印所有姓的開頭是M到Z之間任一字符,并且點(diǎn)號(hào)號(hào)碼結(jié)尾是1或者2的行 |
egrep command | Description | |
|
| |
egrep '(John|Fred)' phone.txt | 打印所有包含名字John或者Fred的行 | |
egrep 'John|22$|^W' phone.txt | 打印所有包含John 或者以22結(jié)束或者以W的行 | |
egrep 'net(work)?s' report.txt | 從report.txt中找到所有包含networks或者nets的行 |
命令或環(huán)境 | . | [ ] | ^ | $ | ( ) | { } | ? | + | | | ( ) |
vi | X | X | X | X | X | |||||
Visual C++ | X | X | X | X | X | |||||
awk | X | X | X | X | X | X | X | X | ||
sed | X | X | X | X | X | X | ||||
Tcl | X | X | X | X | X | X | X | X | X | |
ex | X | X | X | X | X | X | ||||
grep | X | X | X | X | X | X | ||||
egrep | X | X | X | X | X | X | X | X | X | |
fgrep | X | X | X | X | X | |||||
perl | X | X | X | X | X | X | X | X | X |
s 表示其后是一個(gè)替換命令。
pat1 這是要查找的一個(gè)正則表達(dá)式,這篇文章中有一大堆例子。
g 可選標(biāo)志,帶這個(gè)標(biāo)志表示替換將針對(duì)行中每個(gè)匹配的串進(jìn)行,否則則只替換行中第一個(gè)匹配串。