一輩子受用的Regular Expressions -- 兼談另類的電腦學習態度
文章推薦指數: 80 %
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):/'
最後進入
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了!)兩者其實都是斜體字,但用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
請用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方式公開授權大眾自由複製/修改/散佈。
延伸文章資訊
- 1正規表示式Regular Expression - Poy Chang
正規表示式Regular Expression ... 正規表示式通常被稱為一個模式(pattern),為用來描述或者符合一系列符合某個句法規則的字串,透過他我們可以快速搜尋符合指定模式的文字 ...
- 2Regular expression - Wikipedia
A regular expression is a sequence of characters that specifies a search pattern in text. Usually...
- 3規則運算式語言- 快速參考 - Microsoft Docs
expression 會解譯為零寬度判斷提示。 若要避免具名或編號的擷取群組模棱兩可,您可以選擇性地使用明確的判斷提示,如下所示:
- 4re — Regular expression operations — Python 3.10.5 ...
Regular expressions use the backslash character ( '\' ) to indicate special forms or to allow spe...
- 5正規表達式- JavaScript - MDN Web Docs
Regular expressions are used with the RegExp methods test and exec and with the String methods ma...