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