UNIX和Linux Shell正則表示式語法介紹
非常奇怪,直到今天我仍然能重複週六早上的經典歌曲“Conjunction Junction”。這是好事(看了太多電視)還是壞事(也許是我現在職業的先兆)仍然有待討論。不管怎樣,這首小調在歡快的節奏下傳遞了基本的資訊。
我還沒有為學習 UNIX 構想出與“Conjunction Junction”相似的作品,但是我會在未來的幾個月裡嘗試親手編寫這樣的歌曲。與此同時,趁著快樂回憶所帶來的好心情,我們繼續以 Schoolhouse 搖滾的傳統學習方式攻克命令列。
現在開始上課。吐出嘴裡的口香糖,回到您的座位上,然後拿出一根二號鉛筆。還有您,Spicoli。
您可以將 UNIX 命令列看作是一句話:
- 可執行命令,如 cat 或 ls,是動詞——操作。
- 命令的輸出是名詞——要查閱或使用的資料。
- Shell 操作符,如
|
(管道)或>
(重定向標準輸出),是連詞——用於連線句子。
例如,命令列:ls -A | wc -l
用於計算當前目錄下的條目數(忽略特殊條目 .
和 ..
),它包含兩個句子。第一個句子 ls -A
是動詞結構,列舉當前目錄下的內容,第二個句子 wc -l
是另一個動詞結構,用於計算行數。第一個句子輸出的結果作為第二個句子的輸入,並由連線詞(管道)連線這兩個句子。
在本系列文章以及其他文章中展示的許多您可能已經學習過的命令列句式都具有這種句子結構。
但是,如果缺少了文法上的修飾語,命令列將顯得不專業。當然,基本句子也能完成工作,但是這樣顯得不優美。(在此對高中英語演唱二人組 Rad 女士和 Perlstein 女士表示歉意。)解決更有趣的問題需要用到形容詞。
幾乎所有重要問題都需要從無用資料中過濾出有用資料。雖然屬性的數量和種類會有所不同,但是每種方案都通過某種方式(形式或格式),隱式或顯式地描述了它要查詢並處理的資訊,從而生成另外一種形式的其他資訊。
在命令列中,正則表示式 的作用相當於形容詞——一種描述或限定詞。在應用到輸出時,正則表示式可辨別相關資料和無關資料。
讓我們看一個示例問題。
grep
實用工具逐行過濾輸入並尋找匹配。grep
grep
可以查詢具有固定順序的字元組合,甚至可以通過使用 -i
選項來忽略大小寫。
因此,假定檔案 heroes.txt 包含以下行:
Catwoman |
命令列:
grep -i man heroes.txt |
將生成:
Catwoman |
其中 grep
掃描 heroes.txt 檔案中的每一行並查詢字母 m,後面緊跟 a,然後緊跟 n。除了必須保證相鄰,這些字母可以出現在行的任何位置,甚至可以位於較大的單詞中間。在不考慮大小寫的情況下(-i
選項),Catwoman、Batman、Spider Man、Wonder Woman、Ant Man、Aquaman 和 Martian Manhunter 都包含字串 man
。
grep
實用工具包含其他可優化搜尋的內建選項。例如,-w
選項限制於匹配整個單詞,因此 grep -i -w man
將排除 Catwoman 和 Batman(舉例來說)。
該工具還有一個優秀的功能,可以排除而不是包括所有匹配的搜尋結果。使用 -v
選項來排除 匹配的行。例如:
grep -v -i 'spider' heroes.txt |
將列印除了包含字串 spider
之外的所有行。
Catwoman |
但是,對於以下這些情況,您該如何處理?只希望得到那些開頭為“Bat”的單詞;或者以“bat”、“Bat”、“cat”或“Cat”開頭的單詞?或者希望知道有多少漫畫復仇者的名字以“man”結束。在這些例項中,類似於上述三個示例的簡單字串搜尋將無法滿足要求,因為這些搜尋不區分位置。
正則表示式可以 過濾特定的位置,例如行的開始或結束,以及單詞的開始和結束。正則表示式(通常簡寫為 regex)還可以描述:備選項(您可將其稱為“this”或“that”);固定長度、可變長度或不定長度的重複;範圍(例如,“a-m 之間的任意字母”);還有字元的類別或種類(“可列印字元”或“標點符號”),以及其他技術。
表 1 顯示了一些常用的正則表示式操作符。您可以連線表 1 中顯示的元素(以及其他操作符)並加以組合使用,從而構建(非常)複雜的正則表示式。
表 1. 常用的正則表示式操作符
操作符 | 用途 |
---|---|
. (句號) |
匹配任意單個字元。 |
^ (脫字號) |
匹配出現在行首或字串開始位置的空字串。 |
$ (美元符號) |
匹配出現在行末的空字串。 |
A |
匹配大寫字母 A。 |
a |
匹配小寫字母 a。 |
/d |
匹配任意一位數字。 |
/D |
匹配任意單個非數字字元。 |
/w |
匹配任意單個字母數字字元,同義詞是 [:alnum:] 。 |
[A-E] |
匹配任意大寫的 A、B、C、D 或 E。 |
[^A-E] |
匹配除 A、B、C、D 和 E 之外的任意字元。 |
X? |
匹配出現零次或一次的大寫字母 X。 |
X* |
匹配零個或任意個大寫 X。 |
X+ |
匹配一個或多個字母 X。 |
X{n} |
精確匹配 n 個字母 X。 |
X{n,m} |
匹配最少 n 個並且不超過 m 個字母 X。如果省略 m,表示式將嘗試匹配最少 n 個 X。 |
(abc|def)+ |
匹配一連串的(最少一個) abc 或 def ;abc 和 def 將匹配。 |
以下是一些使用 grep
作為搜尋工具的正則表示式示例。許多其他 UNIX 工具,包括互動式編輯器 vi
和 Emacs、流編輯器 sed
和 awk
,以及所有現代程式語言都支援正則表示式。在您學會正則表示式的語法(也許相當晦澀)之後,就可以將您的專業知識靈活運用到不同的工具、程式語言和作業系統。
要查詢以“Bat”開頭的名稱,請使用:
grep -E '^Bat' |
可以使用 -E
選項來指定正則表示式。^
(脫字號)字元匹配行首或字串的開頭,這是一個出現在每行或每個字串開頭字元之前的假想字元。字母 B
、a
和 t
只具有字面含義並且僅匹配那些特定的字元。因此,命令 grep -E '^Bat'
將生成:
Batman |
由於許多 regex 操作符也為 Shell 所使用(其中一些具有不同的用途,另外一些則有類似的用途),因此一個好的習慣是使用單引號將命令列中的每個 regex 括起來,以保護 regex 操作符免遭 Shell 的誤解。例如,*
(星號)和 $
(美元符號)都是 regex 操作符,並且對於您的 Shell 具有特殊的含義。
要查詢以“man”結尾的名稱,可以使用 regex man$
來匹配序列 m
、a
和 n
,並且後面緊接與 regex 操作符 $
匹配的行(字串)。
基於 ^
和 $
的作用,您可以使用 regex ^$
來查詢空行(相當於在開始之後立即結束的行)。
要查詢以“bat”、“Bat”、“cat”或“Cat”開頭的單詞,可以使用以下兩個技巧。首先是備選項,如果備選項中的任意 模式匹配,都會產生匹配的結果。例如,命令:
grep -E '^(bat|Bat|cat|Cat)' heroes.txt |
可實現這一技巧。regex 操作符 |
(豎線)表示備選項,因此 this|that
匹配字串 this
或字串 that
。因此,^(bat|Bat|cat|Cat)
表示“行首緊跟 bat
、Bat
、cat
或 Cat
之一。”當然,可以使用 grep -i
來簡化該 regex,這樣可以忽略大小寫,從而將命令簡化為:
grep -i -E '^(bat|cat)' heroes.txt |
匹配“bat”、“Bat”、“cat”或“Cat”的另一個方法是使用 [ ]
(方括號)集合 操作符。如果將一組字元放在一個集合中,則可以匹配那些字元中的任意一個。(您可以將集合 看作是字元備選項的簡寫法。)
例如,命令列:
grep -E '^[bcBC]at' heroes.txt |
與以下命令生成的結果相同:
grep -E '^(bat|Bat|cat|Cat)' heroes.txt |
您可以再次使用 -i
將 regex 簡化為 ^[bc]at
。
而且,還可以使用 -
(連字元)操作符在集合中指定包含的字元範圍。例如,使用者名稱通常以字母開頭。假定要在提交給您的伺服器的 Web 表格中驗證這樣的使用者名稱,可以使用類似於 ^[A-Za-z]
的 regex。此 regex 表示“字串的開頭後緊跟任意大寫字母 (A-Z) 或任意小寫字母 (a-z)。”順便說明一下,[A-z]
與 [A-Za-z]
作用相同。
還可以在集合中混合使用範圍和單個字元。regex [A-MXYZ]
將匹配任意大寫的 A-M、X、Y 和 Z。
並且,如果希望反轉集合(即排除集合中的任意字元),可以使用特殊集合 [^ ]
幷包含要排除的範圍或字元。以下是反轉集合的示例。要查詢所有名稱中包含 at 的超級英雄,並排除 Dark Knight 和 Batman,請鍵入:
grep -i -E '[^b]at' heroes.txt |
此命令生成:
Catwoman |
由於某些集合需要經常使用,所以設計出簡化符號以代替大量字元。例如,集合 [A-z0-9_]
十分常用,因此可以簡寫為 /w
。與此類似,操作符 /W
是集合 [^A-z0-9_]
的簡寫。還可以使用符號 [:alnum:]
代替 /w
,使用 [^[:alnum:]]
代替 /W
。
順便說明一下,/w
(以及同義詞 [:alnum:]
)是特定於區域的,而 [A-z0-9_]
即表示字母 A-z、數字 0-9 和下劃線。如果要開發國際化應用程式,請使用區域特定的格式以使程式碼可以在許多區域之間移植。
到目前為止,已經介紹了字面值、位置和兩種備選項操作符。僅使用這些內容,就可以匹配大多數具有可預測 長度的模式。現在回到使用者名稱,通過以下 regex 命令可以確保每個使用者名稱以字母開頭並緊跟恰好七個字母或數字:
[a-z][a-z0-9][a-z0-9][a-z0-9][a-z0-9][a-z0-9][a-z0-9][a-z0-9] |
但是這樣有點笨拙。而且,它只匹配恰好八個字元的使用者名稱。它不會匹配三到八個字元之間的名稱,這通常也是有效的使用者名稱。
正則表示式還可以包括重複修飾符。重複修飾符可以指定數量,如沒有、一個、多個、一個或多個,零或一個、五到十個,以及恰好三個。重複修飾符必須與其他模式組合,修飾符本身沒有含義。
例如,regex:
^[A-z][A-z0-9]{2,7}$ |
可以實現前面描述的使用者名稱過濾功能。使用者名稱 是以字母開頭,後面緊跟至少兩個,但不超過七個字母或數字的字串,並且緊跟字串結尾。
此處的位置定位點非常重要。如果沒有兩個位置操作符,則會錯誤地接受任意長度的使用者名稱。為什麼呢?請考慮 regex:
^[A-z][A-z0-9]{2,7} |
此命令辨別:字串是否以字母開頭並緊跟二到七個字母?但是它未提到終止條件。因此,字串 samuelclemens
滿足條件,但是它的長度顯然超出了有效使用者名稱的範圍。與此類似,省略開始定位點 ^
,或同時省略兩個定位點將分別匹配以類似 munster1313
結束或包含該字串的字串。如果必須匹配特定的長度,請記得在要求的模式的開頭和結尾分別加上分隔符。
以下是其他一些示例:
- 可以使用
{2,}
查詢兩次或多次重複。regex^G[o]{2,}gle
匹配Google
、Gooogle
、Goooogle
等等。 - 重複修飾符
?
、+
和*
分別查詢零次或一次、一次或多次,以及零次或多次重複。(例如,您可以將?
看作是{0,1}
的簡寫法。)regex
boys?
匹配boy
或boys
;regexGoo?gle
匹配Gogle
或Google
。regex
Goo+gle
匹配Google
、Gooogle
、Goooogle
等等。construct
Goo*gle
匹配Gogle
、Google
、Gooogle
等等。 - 可以將重複修飾符應用到單個字元(如上所示),還可以應用到更復雜的組合。使用
(
和)
圓括號(就像數學中的用法)將修飾符應用到子表示式。下面是一個示例:給定文字檔案 test.txt:The rain in Spain falls mainly
on the the plain.
It was the best of of times;
it was the worst of times.命令
grep -i -E '(/b(of|the)/W+){2,}' test.txt
將生成:on the the plain.
It was the best of of times; - regex 操作符
/b
匹配單詞邊界 或(/W/w|/w/W)
。該 regex 表示“一連串完整單詞‘the’或‘of’後面緊跟非文字字元。”您可能會提出疑問,為什麼/W+
是必需的:/b
是位於單詞開頭或結尾的空字串。在單詞之間必須包括這一(或這些)字元,否則該 regex 將無法找到匹配。
查詢文字是常見的問題,但是更常見的問題則是希望在找到文字之後將其提取出來。換句話說,您希望去粗取精。
正則表示式通過捕獲 來提取資訊。如果希望將需要的文字與其他內容分開,請使用圓括號將模式括起來。實際上,您已經使用圓括號收集術語;在預設情況下,圓括號自動進行捕獲。
要檢視捕獲,請切換到 Perl。(grep
實用工具不支援捕獲,因為其目標是列印包含模式的行。)
以下命令:
perl -n -e '/^The/s+(.*)$/ && print "$1/n"' heroes.txt |
將列印:
Tick |
使用命令 perl -e
可以直接從命令列執行 Perl 程式。perl -n
命令針對輸入檔案的每一行執行一次程式。命令的 regex 部分,即位於斜槓之間的文字(/
)表示“匹配字串的開頭,然後字母‘T’、‘h’、‘e’後緊跟一個或多個空格字元 /s+
,然後捕獲直到字串結尾的所有字元。
Perl 捕獲內容被放在以 $1
開頭的特殊 Perl 變數中。Perl 程式的其餘部分列印捕獲的內容。
每個巢狀的括號對,從左開始算起,每個左圓括號加一,放在下一個特殊的數字變數中。例如:
perl -n -e '/^(/w)+-(/w+)$/ && print "$1 $2"' |
將生成:
Spider Man |
捕獲感興趣的文字僅僅是隔靴搔癢。如果能夠準確確定材料,就可以使用其他材料改變其外觀。類似於 vi
和 Emacs 的編輯器將模式匹配與替換組合,從而將查詢和替換文字組合成一步操作。還可以使用模式、替換和 sed
從命令列更改文字。
正則表示式非常強大;可供使用的操作符的數量龐大,種類繁多。它包含如此豐富的資訊和實踐知識,我們在這裡所能列舉的實屬鳳毛麟角。
幸運的是,有以下三種優秀的正則表示式理論來源可供使用:
- 如果在您的系統上有 Perl,可以參閱 Perl Regular Expression man 頁面(鍵入
perldoc perlre
)。它會提供 regex 的精彩介紹,幷包含許多有用的示例。許多程式語言都已採用 Perl 相容的正則表示式 (PCRE),因此您在此 man 頁面讀到的內容已被直接轉換到 PHP、Python、Java? 和 Ruby 程式語言,以及許多其他最新工具。 - Jeffrey Friedl 編著的《正則表示式》(第三版)被認為是 regex 用法方面的聖經。該書細緻、準確、清晰、務實地說明了匹配的工作方式、所有的 regex 操作符、多數優先性(限制
+
和*
匹配字元的數量),以及更多內容。此外,Friedl 的書還包括一些令人驚歎的正則表示式,可以準確地匹配完全限定的電子郵件地址和其他 Request for Comments (RFC) 特定的字串。 - Nathan Good 編著的 Regular Expression Recipes 一書提供了針對許多常見資料處理和過濾問題的有用的解決方案。如果需要提取郵政編碼、電話號碼或引用的字串,請嘗試 Nathan 的解決方案。
在命令列中,可以採用許多方法使用正則表示式。幾乎每個處理文字的命令都支援某種形式的正則表示式。大多數 Shell 命令語法還或多或少地擴充套件正則表示式以匹配檔名(儘管操作符的功能可能有所不同)。
例如,鍵入 ls [a-c]
以查詢名為 a、b 或 c 的檔案。鍵入 ls [a-c]*
以查詢以 a、b 或 c 開頭的所有檔名。此處的 *
在 Shell 中不像 grep
的直譯器那樣修飾 [a-c]
,*
被解釋為 .*
。?
操作符在 Shell 中也可以工作,但是被解釋為 .
,即匹配任意單個字元。
檢視您最喜歡的實用工具或 Shell 的文件以確定哪些 regex 操作符受支援,以及操作符可能具有的獨特性。