一輩子受用的Regular Expressions -- 兼談另類的電腦學習態度

文章推薦指數: 80 %
投票人數:10人

Regular Expression (簡稱regexp 或RE) 是什麼? 有人直譯為「常規表示式」; 筆者偏好意譯, 姑且叫它「字串樣版」。

它的功能是協助我們搜尋字串, 甚至對檔案內的特定字 ... 初學建議 命令列 目錄與檔案 目錄與捷徑 基本工具 文件閱讀 I/O導向 用戶與權限 網路指令 多工環境 Shell RegularExpressions XWindow 組合的力量 light dark 我的部落格: 人權 玩具 快速跳到: 社群活動 本層目錄 上層目錄 此頁@朝陽 此頁@資管 English 一輩子受用的RegularExpressions--兼談另類的電腦學習態度 這份講義已過時;建議改參考 新的講義 Regexp是什麼? RegularExpression(簡稱regexp或RE)是什麼? 有人直譯為「常規表示式」;筆者偏好意譯,姑且叫它「字串樣版」。

它的功能是協助我們搜尋字串,甚至對檔案內的特定字串做全面性的代換。

一般簡單的文字檔案編輯器(例如MSWindows下的記事本,DOS下的edit 或是Linux下的nano)不支援regularexpression(以下簡稱regexp), 所以我們在搜尋或代換字串時,這些編輯器往往無法抓住我們的意思, 不是太寬鬆(例如想找port,卻連important也找出來)就是太嚴格 (例如想找color,卻遺漏了colour)。

雖然大部分編輯器提供忽略大小寫等選項,但所能解決的問題還是相當有限, 像上述兩個簡單的例子都沒有辦法解決。

如果改用regexp, 則甚至可以處理諸如"一份html檔裡面所有的hyperlinks" 這類的複雜搜尋條件。

具體地說,我們在想要搜尋的字串當中夾雜一些特殊符號, 指示電腦以較嚴格或較寬鬆的條件去搜尋,這樣的字串就叫做regexp。

例如要找modem或Modem,我們可以用[Mm]odem這個regexp來表示。

這裡的方括弧就是regexp語法中的特殊符號之一, 代表裡面任何一個字元放在這一個位置都可以 (但總歸要是一個字元)。

Regexp可以應用在什麼領域? 任何用得上電腦的領域。

只要 你想處理的資料是某種文字格式(.txt,.html,.csv,...), 或者很容易與某種文字格式互轉而不失真(例如.sxw);抱歉, 封閉的.doc格式並不適用; 你想做的事情是有規律,機械化,重複繁瑣的動作 聽起來跟「需要寫程式解決的問題」很類似嗎?沒錯,有很多時候, 沒有學過regexp的人,真的會著手寫一支程式來解決他的問題;但是會 regexp的人,則會下一個稍長,含有regexp的指令來解決同一個問題, 連文字檔案編輯器都不用打開,就已經完成相同的任務。

且讓我舉幾個例子: 數個班級的同學混合進行專題分組,老師傳來的成績依組別區分; 但系辦公室要將成績改成依班級區分,以便傳給教務處。

經常上某個固定的網頁查看某類商品當天的促銷狀況,想自動化。

將一份文件當中的美式日期(月/日/年)改成澳洲日期(日/月年) 將數千個照片檔按照檔名分別放到不同的目錄底下。

寫一封信給過去幾個月內曾經與我通過三封以上e-mail的朋友。

...(請出題目給我,讓我們把您的問題與我的解答擺在這裡與大家分享) Regexp可以拿來做這麼多神奇的事,學起來卻遠比寫程式簡單許多! 它不是一種程式語言,也不是一套應用軟體,而是一套約莫兩打特殊符號的規則 (其實只要學一打就很夠用了)。

從廿年前的vi編輯器,到現在的 java語言,在許多不同應用領域的軟體上 都可以使用上這套規則。

只要有國中英文程度,與幾小時的學習投資, 就可以學會regexp,這並不是資訊科系的專利。

另一方面, 它的知識價值卻比任何視窗軟體(微軟視窗或LinuxX視窗)更長久, 因為它不會退流行,而且你可以拿它來與其他知識發揮 組合相乘的效果。

筆者特別呼籲 擅長分析語句文法的英文老師, 與喜愛分析組合勝過死記的數理老師,花一點點工夫認識regexp。

Regexp絕對不比英文的複合句,數學的因式分解,物理的運動學更困難。

請您給我幾個小時,讓我證明給您看,這個,而不是如何拉MSWord的選單, 才具有長遠的學習投資價值,才應該是資訊教育的主體。

練習前的準備 在*BSD或各種版本的GNU/Linux底下學習regexp 比較方便。

在這些系統裡面,許多支援regexp的工具都是內建的, 不必另外安裝;而且它們使用的資料檔多為開放透明的文字檔, 比較方便舉例。

當然regexp在MSWindows底下也可以使用 (如果花那麼多精神學習一套工具,卻只能在一兩個作業平臺上使用, 未免太不划算!)例如你的Windows系統內若裝有 perl, vi,或 cygwin,就可以用regexp。

不過 windows下到處都是封閉的.doc檔, 無法發揮regexp的長處。

所以筆者假設你有一個Linux帳號可以使用,並且不害怕學一點 命令列操作。

使用本講義的老師們,如果不方便準備安裝有 Linux的主機並開設帳號,可以考慮發免安裝,直接光碟開機即可使用的 knoppix光碟給學生。

以下用 Mandrake9.1為例;如果是其他版本,命令下法完全一樣, 只有檔案與目錄的位置可能要調整一下。

例如前幾個實驗裡面用最多的howto 文件,在Mandrake底下,放在/usr/share/doc/HOWTO/HTML/en裡面。

sunsite 或ibiblio 找到最近的Linux-HOWTOs-2005*.tar.gz檔案,解壓縮至任意目錄。

我們需要用到less 來閱覽文字檔案,也可能需要用到 lynx 文字瀏覽器將我網頁上的資料抓回你的帳號實驗。

有時候我們下很長的指令, 如果你會用上箭頭把過去的命令叫出來改,還會用[TAB] 鍵讓電腦替你完成檔案名稱快打,會方便很多。

例如要進入上述howto 文件的目錄,可以這樣打:cd /usTABshTABdo TABHOTABHTTABen 這些都是readline使用者界面 所提供的功能。

支援regexp的工具很多,但其中以perl的支援最為豐富,所以本文拿 perl作例子。

別緊張!你不必學perl。

我準備了三種句型: perl-ne'printif/.../')這次條件變得比較嚴格,搜尋到的列數是否少了許多? 不過英文有個討厭的地方:名詞有分單複數。

像這樣找,複數的ports 都被我們排除在外了。

沒關係,請改用/\bports?\b/i。

這裡的問號相當於{0,1}也就是「前面的東西可以有, 也可以沒有」的意思。

請注意找找看是否多出一些複數, 搜尋到的列數是否又增加了呢?至此,我們找到了「不嵌在其他字當中, 不管大小寫,單數或複數的"port"」。

後來在使用PPP撥接上網的時候,又遇到撥接失敗, 但卻沒有錯誤訊息的問題。

有許多程式,並不把錯誤訊息印在螢幕上, 而是印到檔案裡面,這個動作叫做log。

於是我到 PPP-HOWTO裡面去搜尋"log"。

還是一樣,又找到一大堆analogue, Catalogue等等不相關的字。

再改用/\blogs?\b/i搜尋, 就好很多。

但是英文的動詞變化不只加s,還有現在分詞與過去分詞。

所以改搜尋:/\blog(|s|ged|ging)\b/i這樣是否正確多了呢? 也請在less裡面搜尋\ 反白看起來更清楚。

這裡的小括弧與| 符號代表從多個字串中選一個:我們要找的是log,logs,logged, 或logging.請注意這比[...]功能更強,因為方括弧是: 從多個字元中選一個.換句話說,所有[...]能做的事, 其實都可以用(...|...|...)來做;另一方面,用小括弧與| 來取代方括弧,當然會比較囉嗦一些.例如[aeiou]的效果和(a|e|i|o|u) 一樣。

不要前後文,只印出真正有興趣的部分 如果讀者真的操作上面的例子,會發覺上節中perl-ne'printif /REGEXP/;'這樣的句型會把含有REGEXP 的一整列全部給列印出來.如果我們只要REGEXP本身呢? 那就要用(...)和$1,$2,...了。

使用小括弧和「錢號數字」的組合, 可以圈選出我們有興趣的子字串,並告訴perl 我們只想印那幾對小括弧內的子字串。

上面的perl句型,會將找到的字串連同同一列上的前後文 一起印出。

但這樣並不方便, 因為有時候我們希望只印出真正有興趣的部分就好。

此時可以在regexp 外面包一對小括弧,然後稱之為$1將它印出來。

例如我們想分析 last-a指令的輸出。

它印出過去一段時間內, 每個登入主機的人次。

如果想抓取「星期幾」欄位,可以這樣下: last-a|perl-ne'print"$1\n"if /\b([A-Z][a-z][a-z])\b/'就像吃西式自助餐一樣, 只取我們真正有興趣的東西,不要取而不用,感覺更清爽。

請注意:如果檔案內的一列上有兩個以上符合regexp描述的字串, 那麼這第二種句型只會印出每列上第一個符合條件的字串, 後面其他符合條件的字串都不會印出來。

例如我們要抓的如果是月份, 長相與星期幾一模一樣,該怎麼辦呢?搜尋時只好改成搜尋 「大小小空大小小」(分別指"大寫","小寫","空格"),並且在第二串 「大小小」外面加上一對小括弧就可以了。

請根據提示自己試試看。

又,如果想同時印出月份及每次連線有多久,例如: ckhung:0SunOct2620:51-21:54(01:03) 應該如何寫呢?與上一例(抓月份)相同, 雖然我們對「星期幾」沒有興趣,但是它的長相與我們有興趣的字串相同, 我們不得不從它開始比對起:perl-ne'print"Month:$1Duration: $2\n"if/\b[A-Z][a-z][a-z] ([A-Z][a-z][a-z]).*\(([0-9][0-9]:[0-9][0-9])\)/'這裡的 *相當於{0,無窮大},也就是說前面那個東西 ("任意一個字元")可以重複出現0次,1次,2次,...任意次。

又, 因為小括弧在regexp裡面有特殊意義, 所以當我們真正對「小括弧字元」有興趣的時候,前面必須加上倒斜線, 取消它的特殊意義。

最後注意到print 後面的字串其實隨我們高興愛怎麼印就怎麼寫,不見得只能寫 $1(當然如果用到$2$3等等, 後面就必須真的有那麼多對小括弧才有意義)。

再例如我們想將constants.txt檔案裡面, 每列上出現的(第一個)數字抓出來。

先下一個簡單的regexp試試看: perl-ne'print"$1\n"if/([0-9]+)/'constants.txt 這裡的+號相當於{1,無窮大},也就是說前面那個東西 ("一個數字")可以重複出現1次,2次,3次,...任意次。

諸如{3,5}? *+等等符號,功能都在重複前一串regexp,也稱為 quantifiers。

回到我們的例子,這裡在抓 「連續出現的數字」。

但是這樣只能抓到整數部分,而且沒有正負號。

以下我們省略完整的命令,只寫出regexp,逐步提高它的複雜度, 請讀者自行用上箭頭叫出前一個命令來修改, 並注意每次抓到的數字是否更加完整。

因為[0-9]太常用到了,在perl裡面有一個簡寫: /(\d+)/這裡的\d就是digit"一個數字"的意思。

再考慮正負號(但也可能沒有):/([+-]?\d+)/ 再考慮小數部分(但也可能沒有): /([+-]?\d+(\.\d+)?)/ 注意第二個問號指的是前面小括弧內一整串("小數部分")可有可無。

最後考慮指數部分(但也可能沒有): /([+-]?\d+(\.\d+)?([Ee][+-]?\d+)?)/ 從這個例子,我們也學到一個態度:初學時,別想要一步登天, 先從簡單的狀況處理起,逐步修改到正確;每修改一點, 就立即實驗一下。

另外, 錯誤訊息 是學習過程當中的至寶;如果習慣性地忽略錯誤訊息, 學習的效果會非常差。

提到perl為常用regexp提供的簡寫,還有\w表示 「一個文數字」(wordcharacter),等同於[0-9a-zA-Z]以及 \s表示「一個空白類字元」,等同於[ \t\n]。

這些簡寫只在perl的regexp裡面能用;其他支援regexp 的語言並沒有這樣的符號。

當然這並沒有增加perl的regexp功能, 只是讓使用者可以寫出比較簡潔的regexp而已。

再換一個例子請讀者自行練習:從文件裡面抓出ip。

也就是想抓 「看起來像是hostname」的字串,例如163.17.9.5, penguin.im.cyut.edu.tw,mail.so-net.net,...等等。

不妨就將本頁的原始碼存檔,拿regexp.php當做資料檔來做實驗。

請參考以下提示,逐步將你的regexp複雜化: 先抓出連續出現的文數字或減號。

後面再加上「一個句點,及一串連續出現的文數字或減號」。

因為 .在regexp裡面有特殊意義(「任何一個字元」), 所以這裡的「一個句點」前面必須加上倒斜線,將它的特殊意義取消: \. 其實第二步所加的東西可以重複出現2次,3次,...(大概不會超過 9次吧)。

參考答案在網頁原始碼某處(沒有所謂的「正確答案」啦)。

最後看一個稍微複雜而完整,很有成就感的例子: 網頁伺服器會將過去來訪本網站的每一人次摘要記錄在一個 access_log檔裡面。

假設我想統計每個小時(0點到1點,1點到2點,...23點到0點) 的來訪人次,產生像這樣的圖。

需要撰寫複雜的C/C++/Java程式嗎?先用regexp與一些簡單的命令: (每一步下完,請用less看一下新產生的資料檔,了解每一句話的意義, 並且看看是否與這裡的結果相同?)) perl-ne'print"$1\n"if/:(\d\d):/'ws_1.txt sortws_2.txt uniq-cws_3.txt perl-ne'print"$2$1\n"if/\s*(\d+)\s+(\d+)/' ws_4.txt 上面的sort指令,顧名思義就是在排序;uniq-c 則將相鄰的重複列刪除,並且計算重複的次數。

最後進入 gnuplot下兩個指令 setstyledatalines plot"ws_4TAB" 就完成了!注意到在gnuplot裡面的TAB 鍵一樣也有檔案名稱快打的功能。

如果你不怕長句的話, 上面四句其實可以改用pipe串在一起,完全不需要產生中間的臨時檔。

滑鼠那麼好用,為什麼許多真正的高手卻更喜歡用鍵盤?比手畫腳那麼方便, 為什麼我們非英文科系的一般民眾也要要學英文?非資訊科系不能學命令列嗎? 什麼樣的資訊教育才可以組合活用, 才有長遠的投資價值?筆者對此有一些看法, 完全不同於目前強調拉選單,比炫麗的主流資訊教育思想。

(要求所有科目的老師用微軟公司的powerpoint做教材, 就是資訊資訊融入教學?呼!哈!)讀者在看完這個例子之後, 也許能夠產生一些共鳴。

貪婪:需要的不多,想要的太多 regexp的搜尋引擎有一個特性,叫做greediness-- 能吃多少,就盡量吃多少。

還記得less 裡面搜尋"長度5-7的英文字"那個例子嗎?如果不指定邊界(\b), 為什麼"greatapeproject"當中的前十四個字母會反白? 請再拿本頁的原始碼做實驗,用less閱讀, 並搜尋「一對雙引號之間的字串」:".*"用perl 的第二種句型寫,就是這樣:perl-ne'print"$1\n"if/"(.*)"/' regexp.php|less不妨在less裡面再搜尋一次,令它反白。

注意到了嗎?大部分「一對雙引號之間的字串」都被正確抓出; 但是有些地方卻因為一列上出現數個字串,而令regexp抓過頭了。

例如 "text/css"出現的那一列上面有三個字串,結果less與perl 從第一個引號開始,到最後一個引號為止,把其間所有東西都抓出來了。

這可不是因為less會抓出所有符合條件字串的關係--果真如此,'rel=' 和'href='就不應該反白才對。

這是因為regexp非常貪婪, 會在滿足搜尋條件的前提下,盡情地吃到無法再吃為止。

但其實我們需要的並不多,看到第二個雙引號,老早就應該停下來了。

有時候,貪婪還真是會誤事。

難怪LinusTorvalds要說:「無論如何, 貪念永遠不是件好事」。

要解決這個問題,可以用[^...]"除了... 之外的任何一個字元"。

只要將"任意字元重複出現任意次"改成 "除了雙引號之外的任意字元重複出現任意次"就可以了:perl-ne 'print"$1\n"if/"([^"]*)"/'regexp.php|less請用錯誤 (貪婪)的方式再試一次,用力比較一下什麼地方不一樣。

在perl裡面,還有一個更優雅的阻止貪婪搜尋的方法。

所謂貪婪搜尋的問題,其實都出在quantifier。

因為quantifier的出現, 才讓regexp搜尋引擎有機會選擇多吃一點或少吃一點。

任何一個 quantifier後面只要加上問號,就可以將這個quantifier轉化成不貪婪的 quantifier,一旦吃足符合regexp的資料,就盡早停下來,不再多吃, 像這樣:"(.*?)"。

也許可以把這裡的問號翻譯成: 「我真的需要這麼多嗎?」 再舉一個例子。

/etc/passwd這個檔案,裡面存放使用者的個人資訊。

請用less閱覽,並搜尋你的使用者代號。

注意連續的兩個冒號, 這中間其實有一個存放"真實姓名,辦公室,辦公室電話,家中電話"的欄位, 不過一開始都是空的。

請下chfn指令修改你的個人資訊。

(提示:沒有人規定不可以填一點笑話進去;當然你要先通過密碼檢查一關, 否則任何人都可以趁你離開電腦五分鐘時亂改你的資訊,就不好玩了。

) 下面這句話會產生"使用者代號=真實姓名"的對照表:perl-ne 'print"$1=$2\n"if/^(.*?):.*:(.*?),/'/etc/passwd 請猜猜看以下搜尋條件分別會得到什麼結果?/:(.*),/及 /:(.*?),/及/:[^:,]*,/(當然, 現在只有一對括弧,下命令的時候,print後面就不應有$2) 一個quantifier後面加了問號(最常見的是+?與 *?)並沒有改變它的意義,改變的只是它的 「貪婪程度」。

全面代換字串 前面談的都是搜尋;以下介紹如何運用regexp 來對檔案內的特定字串做全面性的代換。

例如當朝陽技術學院升格成朝陽科技大學的時候,需要將所有文件裡面的cyit 改成cyut;其他地方原封不動。

如果只有regexp.php一個檔案要改, 可以這麼下:perl-pe's/\bcyit\b/cyut/g'regexp.php> new.php(螢幕上看不到東西?當然!被我們用outputredirection 轉入檔案去嘍。

如果將上句中的>new.php換成 |less就可以看到啦。

)然後用diff比較:diff regexp.phpnew.php|less 它會將兩個非常相似的檔案抓來比較,只印出有差異的地方,以< 開頭的是左檔的內容;以>開頭的則是右檔的內容。

如果沒有問題, 最後再下mvnew.phpregexp.php覆蓋舊檔。

當然我不只一個檔案要改,所以真正下的指令是:perl-i-pe 's/\bcyit\b/cyut/g'`find.-iname'*htm*'` 將本目錄底下所有檔名當中出現htm的檔案一口氣全部抓來修改。

不過這是危險動作,初學者請勿模倣。

有興趣的讀者請自行研讀 shell講義當中的命令結果代換,並查手冊 man1perlrun裡面有關-i的說明。

即使是筆者也習慣用上一段的語法;如果要一次改很多檔案, 也會先用安全的語法實驗,找出最恰當的regexp寫法之後, 再改用這段的危險語法,以免平白磨損硬碟。

此外, 先行備份以防萬一也非常重要。

水能載舟,亦能覆舟; 學習運用原力的絕地武士們可要戒慎(但是不要恐懼)啊! 以下我們就用上段的安全語法來寫例子。

美國日期的寫法是「月/日/年」例如"12/25/1999"; 而澳洲日期的寫法是「日/月/年」例如"25/12/1999"。

想將report.txt 檔案中的美式日期全部改成澳洲式日期,可以這樣下:perl-pe 's#\b(\d\d)/(\d\d)/(\d\d\d\d)\b#$2/$1/$3#' 而要改用更不應該在文件裡面放一大堆 (請不要說我對微軟有偏見--它的FrontPage確實毒害了太多網頁設計者, 進而傷害了視障者及其他弱勢族群的瀏覽權益。

請幫忙呼籲親朋好友, 叫大家發揮道德良知,別再用FrontPage了!)兩者其實都是斜體字,但用i 比較注重排版外觀;用em表示重視的是文件結構)以下這句把test.html 檔案裡的全部改成同時把全部改成
perl-pe's###g' test.html遇到時,這裡的$1就是空字串;遇到 時,$1就是"/" 用命令產生一長串命令 把目前目錄下的所有.php檔更名為.html檔:find.-name '*.php'|perl-pe's/^(.*)\.php$/mv$1.php$1.html/' 上面只是產生許多mv指令而已,並沒有真正執行更名的動作。

如果確定沒錯,可以把上面的結果pipe給bash去執行。

更改檔名這個例子示範了一個比代換字串更強大的技巧: 我們不僅可以把資料轉換成另一形式的資料,還可以把資料轉換成程式! 以下列出幾個可以應用的場合,請讀者自己試著寫出細節: PostgreSQL 目前似乎無法批次匯入(import)大量資料,如何將既有的純文字表格輸入 PostgreSQL?可以把純文字表轉換成psql可以處理的SQL指令檔。

eznet電話撥接程式 (真的很容易設定!)可以管理很多ppp帳號。

如何把現有的帳號資料一次輸入eznet? gnuplot可以畫複雜的函數圖形; 但是要畫大量的箭頭或文字就必須一句一句寫.如何讓gnuplot 把一個座標檔內的資料都以箭頭的形式畫出來呢? 目錄A(以及它的子目錄,孫目錄...)底下有許多文字檔;目錄B 有相同的目錄結構與檔案,而檔案的內容也幾乎與目錄A 下相應的檔案一模一樣.如何用diff 命令找出究竟有那幾個檔案不同? 延伸上例,假設讀者身為老師,發覺學生A與學生B的期末作業 (許多*.h,*.c檔,分別放在A與B兩目錄下)用上題的方式檢查之後, 出奇地類似,看起來只是部分變數名稱改變。

除了變數名稱的改變之外還有多少相異呢?這裡要用perl產生的程式, 是內含呼叫perl命令的shellscript。

Perl的regexp總整理 常用的regexp符號可以大致分為三類: 比對「一個字元」的符號: [...]...當中任何一個字元 [^...]除了... 之外的任何一個字元 .任何一個字元 具有「定位」功能,但本身不吃掉任何字元的anchor: ^...以...開頭的字串 ...$以...結尾的字串 \b文數字/非文數字的邊界。

計數用,表達「前面那個東西重複出現多少次」的quantifier: {5}重複5次 {3,7}重複3到7次 ?可有可無(0次或1次) *重複出現任意次,包含0次 +重複出現任意次,至少1次 所謂「前面那個東西」可能是一個字元, 也可能是由小括弧括起來的一長串 另外還有表達「這一串」的(...),及表達 「或」的|。

如先前所說,支援regexp的程式很多。

以上所列的觀念,在大多數支援 POSIX標準的語言/軟體裡面都可以找得到。

但是不同的語言/軟體, 採用的符號可能有一點點細微的差別。

例如表達邊界,在perl裡是 \b但在less或vi裡卻是 \<...>又例如在grep當中, ?+|( 等等符號要加上倒斜線才有特殊意義;但在perl裡面, 這些字元反而是加了倒斜線就失去特殊意義,變成比對到它本身。

讀者不必擔心記不得這麼多不同的規則--筆者也記不太起來:-) 瞭解觀念最重要;至於細節,需要用的時候再查手冊就好了。

我們之所以選擇perl來教regexp,有好幾個原因。

第一,perl 有一個很容易記的規則:凡是標點符號,加上倒斜線, 一定沒有特殊意義 第二,perl替最常用的[...] 定義了簡寫: \d其實就是[0-9], "任何一個數字" \D其實就是[^0-9], "任何一個非數字" \w其實就是[a-zA-Z0-9_], "任何一個文數字" \W其實就是[^a-zA-Z0-9_], "任何一個非文數字" \s其實就是[\t\n], "任何一個空白類字元" \S其實就是[^\t\n], "任何一個非空白類字元" 這裡有關\w與\W的說明並不嚴謹.若要處理英文以外的西方語言, 請參考perlre(1)與perllocale(1)。

第三,perl的彈性很大,小小的變化就可以造出三種不同的句型, 應付常用的搜尋/代換工作:(實際上簡單的變化還多得是; 不過筆者必須忍痛就此打住) perl-ne'printif/.../'含有特定字串的那幾列, 全都印出來。

perl-ne'print"$1\n"if/..(..)../'不要前後文, 只印出特定字串就好。

perl-pe's/.../.../g' 把文中所有特定字串全部代換掉。

其他支援regexp的工具 事實上如果用的是比較簡單的REGEXP來搜尋,那麼 grep'REGEXP'的效果和perl-ne'print if/REGEXP/'(第一種句型)的效果一樣。

所以簡單的搜尋(只用到[].^$*\b)通常都用grep指令。

但是在 grep中,想使用?+(){}|等功能,必須要在前面加上\例如搜尋port 的範例,如果用grep來做,要寫成:grep-i'\bports\?\b' howto-index所以比較複雜的搜尋,用egrep或perl 寫起來反而比較清楚。

同樣地,簡單的字串代換可以用sed來做。

sed 's/REGEXP/SUBST/g'的效果和perl -pe's/REGEXP/SUBST/g'一樣。

高手們最喜歡常用的文字編輯器vi與emacs當然也都支援regexp。

在perl紅起來之前,awk曾經是最受系統管理人員歡迎的萬用瑞士刀。

事實上perl有很多語法都是從awk(還有C,還有shellscript,...) 學來的。

用archie尋找ftparchives時,也可以使用regularexpression。

花的時間比較多,但是或許可以避免遺珠之憾。

幾乎所有scriptinglanguages 都支援regexp,例如perl, python, php, tcl, ruby, guile,... 等等。

編譯語言(例如C,C++,...)的使用者可以取得pcre程式庫。

[7] 但regexp不是只給程式設計師用的。

許多GUI軟體也支援regexp。

永遠的朋友--文字檔 太空梭很棒;但是大多數時候 腳踏車比較實用。

我所有的資料(程式, 文件,...)都盡量用文字檔儲存。

搭配regexp, 在任何艱困的環境底下都能夠工作。

作業與更多範例 覺得內容太多,一時無法吸收嗎?沒有關係,這些知識具有長遠的價值, 不會退流行,所以也不急著立即完全吸收。

筆者花了很多年的時間, 每幾年學一點,才將regexp學好(例如*?與 +?就是筆者開始教regexp很多年之後才學會的)。

另一方面, 在還沒有學完整之前,就已經可以開始應用於日常作業當中了。

重點之一是: 要經常使用,才會熟悉。

重點之二是:不論是否用得出來,至少要經常 認出可以使用regexp的場合。

如果您遇到一些實用的問題, 即使只是查覺到可能可以(但不知如何)用regexp解, 本文的目的也就達成一半了。

筆者懇請您將問題清楚描述,如果確實可以用 regexp解決,我樂意為您服務;如果您已自己解決問題,更歡迎與我們分享。

因為這不僅解決您的問題,同時也可以讓我收錄入範例/作業/考試當中, 讓其他讀者一併獲益! 用man1perlfunc看手冊時,想找printf函數的說明,可以按 /printf搜尋printf,並連續多按幾個n(搜尋下一個)。

但是這樣做太慢--因為用printf做例子的地方太多,要按很多次n 才會找到printf的定義。

怎樣找會比較快一點呢? 有些文字檔後面會多出一些沒有用的空白字元。

請用regexp 將它們刪除掉。

look命令可以查字典(沒有解釋,只能知道如何拼字)例如 lookpr會印出所有以pr開頭的英文單字。

想知道有一個英文單字"特權"pr...ge怎麼拼,應該如何找比較快? 在linux下要修改系統設定,如果不熟悉眼前的視窗環境, 還是可以用下指令的方式來進行。

問題是要下什麼指令才能修改滑鼠或網路的設定呢?可以先下:ls /bin/sbin/usr/bin/usr/sbin/|perl-ne'printif /(cfg|cnf|conf|config)/' 得知系統內有那些看來像是改變設定用的指令, 再從裡面撿出與滑鼠或網路相關的指令來試。

(林坤鋅君提供)顯示第二到第三層各目錄使用空間多寡: du|perl-ne'printif/\.(\/[^\/]+){2,3}$/;' (給朝陽的同學)某個檔案裡面有系上同學的某些資料,每列一筆, 其中包含學號。

(不妨用last指令的輸出做實驗) 請下指令抓出屬於你們班同學的部分(單雙號有別, 不要抓到隔壁班的哦!) Unix底下的系統信箱放在/var/spool/mail/目錄內。

請用less 檢視檔案內容,看看如何下指令抓出最近寄信給我的人。

如果你像我一樣用 mutt或elm或pine等文字模式的軟體收發信件(真的很方便, 速度快很多)那麼讀過的信件一樣也以純文字格式儲存在家目錄底下(例如 ~/mail/mbox)。

如果你會perl語言, 還可以進一步寫一個短短的程式抓出 "過去六個月內至少曾經寄三封信給我的人"。

然後一個指令就可以寄信給這些人。

(也許你的e-mail改變了, 要通知眾親朋好友。

) /usr/X11R6/lib/X11/app-defaults/目錄下有許多設定檔, 裡面包含各種視窗前景背景顏色的設定等等,長得像這樣: Rosegarden*background:#ffe4b5 這裡井字號開頭的三位數或六位數16進位數字代表顏色的rgb成分。

請抓出所有顏色(不要前後文) 在DOS下,文字檔內的換列由^M^J兩個字元構成;Linux 下文字檔的換列只有^J一個字元。

請下命令,將來自DOS 的文字檔每列最後面的^M(在perl裡面可以用八進位表示: \015或用十六進位表示:\xd)刪掉。

當然也可以試著將來自linux的文字檔每列最後面加上^M,變成方便DOS 處理的文字檔。

如果你把手冊的輸出存檔,例如manperl> perl.txt再用editor(例如vi或nano)去看, 會發覺檔案內有很多^H也就是倒退字元。

這是因為過去的印表機都是靠 "倒退再印"的方式來印底線或粗體字。

請下命令刪除這些字元。

(^H 用八進位表示:\010;用十六進位表示:\x8) 其實在Linux下其實可以直接用manperl|col-b解決這個問題, 不需要用regexp。

許多版本的linux以rpm管理套件。

下這個命令:rpm-qa--qf '%{NAME}\n' 可以查詢系統內有那些套件,各別屬於那一類,並印出摘要說明。

不要理會複雜的命令,我們有興趣的是它的結果: mpg321 vnc libxml icewm ..。

請用regexp抓出第一對角括弧號裡面的東西(例如 "Applications/Multimedia")請分別試試看貪婪版,「除...之外」版, 及不貪婪版的效果。

請分析last的輸出,統計週日至週六每天的登入人次 (不分那一週,只要同是週二,就全部算在一起) 並按照登入人次多寡對這七天排序。

提示:把perl處理完的資料pipe給 uniq-c與sort-n接續處理。

gnuplot講義裡面, 有一個處理文字檔案,產生gnuplot命令稿, 畫出亞洲各國人口密度比較圖的例子,也用了大量的regexp。

參考資料 http://irw.ncit.edu.tw/peterju/webslide/re/ 勤益科大朱孝國老師的講稿,有豐富的歷史與背景資料及實例與解答 http://people.ofset.org/~ckhung/b/sa/cygwin.php http://people.ofset.org/~ckhung/a/c041.php http://people.ofset.org/~ckhung/a/c013.php http://www.pcre.org/ http://linuxtoday.com/news_story.php3?ltsn=1998-12-24-003-05-OP "Unixasanelementofliteracy"及 http://www.osviews.com/modules.php?op=modload&name=News&file=article&sid=1084 "DeathtotheWizards!"兩篇有類似的看法. 請參考其他文章: 中研院報告(1): http://phi.sinica.edu.tw/aspac/reports/94/94019/ 中研院報告(2): http://phi.sinica.edu.tw/aspac/reports/96/96004/ (感謝Y.Cheng提供)
http://info.sayya.org/~edt1023/vim/ (以vim為中心) http://lib.stat.cmu.edu/scgn/v52/section1_7_0_1.html (英文) http://www.ciser.cornell.edu/info/regex.html (英文) 雜七雜八還沒整理的舊資料 例如要找出所有「看起來像是C語言當中的keyword或identifier 的字串」,可以用[A-Za-z_]\w*又例如perl語言當中 split""的效果也幾乎可以用split/\s+/ 來達成.這些都只是簡寫,並沒有增加新的功能,但是可以讓我們寫起regexp 來比較簡潔.以下我們會經常使用;但是請注意如果你用的是其他支援regexp 的程式,凡是用到這些簡寫的地方可能都要改寫。

[]^-]字元類別:可以是]或^或-當中任何一個字元。

要找的字元如果正好是有特殊意義的標點符號, 就把它放在它不可能有特殊意義的地方,例如把]放在最前面,^ 不要放在最前面,-放在最開始或最後面。

不過這樣寫不易讀,建議只在 "用丟即棄"的命令列當中使用。

如果出現在程式當中,建議這樣寫: [\]\^\-]日後維護才方便。

{2,4}前一個樣版要出現2到4次.延續上例,例如要在COPYING 檔案內找連續2個,連續3個,或連續4個數字,可以在less內下: /\b[0-9]{2,4}\b嚴格說來,為了不遺漏掉"19yy" 之類的字串,應該這樣寫才完整: /([^0-9]|^)[0-9]{2,4}([^0-9]|$)簡單地說, 我們本想要求「前後不可有數字」,所以在[0-9]{2,4}之前與之後加上 [^0-9];但是[^0]的要求又太多了,它要求 "要有一個不是數字的字元"; 那麼如果我們找到的一串數字正好出現在該列的最前面或最後面, 豈不是比對失敗了嗎?所以我們再加上"前面沒有東西了"及 "後面沒有東西了"這些可能狀況。

想統計一下系統內所安裝的各類套件各有多少個:perl-ne 'print"$1\n"if/^Group\s*:\s*(.*\S)\s*SourceRPM/;' rpm-listing將結果pipe給sort|uniq-c|sort-n| less就可以看到由少而多的各類套件個數.因為regexp 引擎具有「貪婪」(greed)的特性--*與+ 之類符號會盡其所能地吞噬它有能力吞噬的字串--所以這裡需要有一個\S 來阻止.*把"SourceRPM"之前的空白吃進去.如果沒有\S會怎麼樣? 在less當中做類似的搜尋就知道了(但記得\s與\S只能在perl 用)。

列印出myprog.c所包含(#include)進來的所有標頭檔(*.h)檔名: perl-ne'print"$1\n"if/^#\s*include\s/' myprog.c 上節找六位或七位數字的例子,我們可以用新語法重寫,讓perl 只印出(每列上第一個)找到的數字,而不要整列印出來:perl-ne 'print"$2\n"if/(\D|^)(\d{6,7})(\D|$)/' Consultants-HOWTO 印出所有看起來像是URL的字串:perl-ne'print"$1\n"if m#(\w+://([-\w]+\.)*[-\w]+(:\d+)?[^<>\s"]*)#'..。

這裡因為我們要找的字串當中有"/",和perl用來分隔搜尋字串/.../ 的分隔字元衝突,如果按照標準的寫法,每個/都要變成\/還好在perl 內,大部分的標點符號我們都可以拿來當做分隔字元.本例用# 當分隔字元,那麼/就可以當做一般字元來比對而不需要加\了。

把一個程式檔當中的(整數)x,y座標顛倒過來:perl-pe 's/\[(-?\d)+,(-?\d)+\]/[$2,$1]/g'prog.pl 這個假設座標都是整數,並以","分開.(這裡假設我們要處理的是perl, 且程式中的座標存在anonymousarray當中) 把sample.c內的(大部分)/*...*/形式的註解改成#..。

形式的註解:perl-pe's@/\*(.*)\*/@#$1@;'sample.c (也許我們想用perl重寫sample.c這個程式, 所以註解的形式當然要改嘍.) 不過跨列的註解還有層層相疊的註解就不方便用regexp改了。

把test檔案內的文數字以外所有字元都給變成%...這種url-escape 的形式:perl-pale's#([\W])#"%"。

sprintf("%02X",ord($1))#ge'test sirobot 可以用來映射整個網站(或其中一個子目錄),但抓回來的檔案裡面,許多 link都會亂掉,例如本來應該是"abc.html"的link,會變成 "..%2Fabc.html%2Fabc.html"這裡的"%2F"其實就是"/"沒什麼問題; 但是".."與重複出現的檔名就麻煩了。

以下命令可以一口氣修正本目錄下所有htm(l)檔:perl-i.bak-pe 's#%2F#/#g;s#\.\./(.+?)/\1#\1#g'`find.-name'*.*htm*'` 註:有些比較複雜的狀況需要用這個更精確的寫法:perl-i.bak-pe 's#\.\.%2F(.*?\.\w+?)%2F\1#\1#g;s#"\.\.%2F(.*?)"#$1#g;s#%2F#/#g' `find.-name'*.*htm*'`心得:還是wget比較好用... 不論未來套裝程式如何進步,提供功能如何多,都不可能取代善用regexp 的能力,因為regexp不是一個功能或一個工具而已, 它的是一個表達力遠比選單視窗更豐富的人機介面。

學會這個豐富表達方式,再理性地選擇真正有用的工具來配合使用, 讓你的新舊知識發揮相乘的效果,[6] 我想會比盲目地追隨流行更能夠有長遠的收穫.另一方面, 讀者也應該體會到使用.html或.txt等文字檔儲存資料的好處。

如果使用的是像.doc檔這樣封閉的二進位檔案格式, 就沒有許多好用的工具可以使用(regexp只是其中之一而已)。

[5] 筆者認為我們的「全民資訊教育」有必要全面檢討: 如果非得在傳統課程之外,再給我們的中學生增加資訊科技的課程, 那麼真正應該教的不是Office2000,而是regexp。

[8]Regexp之於電腦化文字處理的重要性, 不亞於四則運算之於數學的重要性.基本的regexp並不比四則運算困難, 而它的應用範圍也不比四則運算小.既然一般(還沒有決定要念數學系的) 小學生可以學四則運算,一般(還沒有決定要念資訊相關科系的) 國中生當然也可以學regexp.(不是因為regexp本身那麼難, 需要國中生才能理解, 而是因為大部分應用的場合需要一些基本的電腦與英文知識.) 如果讀者從本文得到一點有用的知識,也希望能夠做點什麼事情回饋社會, 那麼筆者建議把這篇文章印給你的上司/下屬,老師/學生,朋友看。

尤其可以拿給尚在求學的年輕朋友。

一方面是因為他們沒有立即的就業壓力與沉重的歷史包袱, 比較可以接受目前非主流的知識; 另一方面是因為要期待正統的資訊教育擺脫廿世紀末的微軟包袱, 走上重視思考甚於重視操作的正途,恐怕不是,呃,200\d年的事。

本頁最新版網址: https://www.cyut.edu.tw/~ckhung/b/gnu/regexp.php; 您所看到的版本:June29201602:26:12. 作者:朝陽科技大學資訊管理系洪朝貴 寶貝你我的地球,請 減少列印,多用背面,丟棄時做垃圾分類。

本文件以 CreativeCommonsAttribution-ShareAlikeLicense 或以 FreeDocumentLicense方式公開授權大眾自由複製/修改/散佈。



請為這篇文章評分?