1. 程式人生 > >Linux正則表示式基礎

Linux正則表示式基礎

轉載自實驗樓

實驗介紹

雖然我們這一節的標題是正則表示式,但實際這一節實驗只是介紹grepsedawk這三個命令,而正則表示式作為這三個命令的一種使用方式(命令輸出中可以包含正則表示式)。正則表示式本身的內容很多,要把它說明清楚需要單獨一門課程來實現,不過我們這一節中涉及到的相關內容通常也能夠滿足很多情況下的需求了。

想要更深入地學習使用正則表示式,在這裡 正則表示式基礎

一、正則表示式

什麼是正則表示式呢?

正則表示式,又稱正規表示式、正規表示法、正規表示式、規則表示式、常規表示法(英語:Regular Expression,在程式碼中常簡寫為 regex、regexp 或 RE),電腦科學的一個概念。正則表示式使用單個字串來描述、匹配一系列符合某個句法規則的字串。在很多文字編輯器裡,正則表示式通常被用來檢索、替換那些符合某個模式的文字。

許多程式設計語言都支援利用正則表示式進行字串操作。例如,在 Perl 中就內建了一個功能強大的正則表示式引擎。正則表示式這個概念最初是由 UNIX 中的工具軟體(例如sedgrep)普及開的。正則表示式通常縮寫成“regex”,單數有 regexp、regex,複數有 regexps、regexes、regexen。

簡單的說形式和功能上正則表示式和我們前面講的萬用字元很像,不過它們之間又有很大差別,特別在於一些特殊的匹配字元的含義上,希望初學者注意不要將兩者弄混淆。

1. 舉例

假設我們有這樣一個文字檔案,包含"shiyanlou",和"shilouyan"這兩個字串,同樣一個表示式:

shi*

如果這作為一個正則表示式,它將只能匹配 shi,而如果不是作為正則表示式*作為一個萬用字元,則將同時匹配這兩個字串。這是為什麼呢?因為在正則表示式中*表示匹配前面的子表示式(這裡就是它前面一個字元)零次或多次,比如它可以匹配"sh","shii","shish","shiishi"等等,而作為萬用字元表示匹配萬用字元後面任意多個任意字元,所以它可以匹配"shiyanlou",和"shilouyan"兩個字元。

體驗完了,下面就來開始正式學習正則表示式吧。

2.基本語法:

一個正則表示式通常被稱為一個模式(pattern),為用來描述或者匹配一系列符合某個句法規則的字串。

選擇

|

豎直分隔符表示選擇,例如"boy|girl"可以匹配"boy"或者"girl"

數量限定

數量限定除了我們舉例用的*,還有+加號,?問號,.點號,如果在一個模式中不加數量限定符則表示出現一次且僅出現一次:

  • +表示前面的字元必須出現至少一次(1次或多次),例如,"goo+gle",可以匹配"gooogle","goooogle"等;
  • ?表示前面的字元最多出現一次(0次或1次),例如,"colou?r",可以匹配"color"或者"colour";
  • *星號代表前面的字元可以不出現,也可以出現一次或者多次(0次、或1次、或多次),例如,“0*42”可以匹配42、042、0042、00042等。

範圍和優先順序

()圓括號可以用來定義模式字串的範圍和優先順序,這可以簡單的理解為是否將括號內的模式串作為一個整體。例如,"gr(a|e)y"等價於"gray|grey",(這裡體現了優先順序,豎直分隔符用於選擇a或者e而不是gra和ey),"(grand)?father"匹配father和grandfather(這裡體驗了範圍,?將圓括號內容作為一個整體匹配)。

語法(部分)

正則表示式有多種不同的風格,下面列舉一些常用的作為 PCRE 子集的適用於perlpython程式語言及grepegrep的正則表示式匹配規則:(由於markdown表格解析的問題,下面的豎直分隔符用全形字元代替,實際使用時請換回半形字元)

PCRE(Perl Compatible Regular Expressions中文含義:perl語言相容正則表示式)是一個用 C 語言編寫的正則表示式函式庫,由菲利普.海澤(Philip Hazel)編寫。PCRE是一個輕量級的函式庫,比Boost 之類的正則表示式庫小得多。PCRE 十分易用,同時功能也很強大,效能超過了 POSIX 正則表示式庫和一些經典的正則表示式庫。

字元 描述
\ 將下一個字元標記為一個特殊字元、或一個原義字元。例如,“n”匹配字元“n”。“\n”匹配一個換行符。序列“\\”匹配“\”而“\(”則匹配“(”。
^ 匹配輸入字串的開始位置。
$ 匹配輸入字串的結束位置。
{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?”。請注意在逗號和兩個數之間不能有空格。
* 匹配前面的子表示式零次或多次。例如,zo*能匹配“z”、“zo”以及“zoo”。*等價於{0,}。
+ 匹配前面的子表示式一次或多次。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等價於{1,}。
? 匹配前面的子表示式零次或一次。例如,“do(es)?”可以匹配“do”或“does”中的“do”。?等價於{0,1}。
? 當該字元緊跟在任何一個其他限制符(*,+,?,{n},{n,},{n,m})後面時,匹配模式是非貪婪的。非貪婪模式儘可能少的匹配所搜尋的字串,而預設的貪婪模式則儘可能多的匹配所搜尋的字串。例如,對於字串“oooo”,“o+?”將匹配單個“o”,而“o+”將匹配所有“o”。
. 匹配除“\n”之外的任何單個字元。要匹配包括“\n”在內的任何字元,請使用像“(.|\n)”的模式。
(pattern) 匹配pattern並獲取這一匹配的子字串。該子字串用於向後引用。要匹配圓括號字元,請使用“\(”或“\)”。
x|y 匹配x或y。例如,“z|food”能匹配“z”或“food”。“(z|f)ood”則匹配“zood”或“food”。
[xyz] 字元集合(character class)。匹配所包含的任意一個字元。例如,“[abc]”可以匹配“plain”中的“a”。其中特殊字元僅有反斜線\保持特殊含義,用於轉義字元。其它特殊字元如星號、加號、各種括號等均作為普通字元。脫字元^如果出現在首位則表示負值字元集合;如果出現在字串中間就僅作為普通字元。連字元 - 如果出現在字串中間表示字元範圍描述;如果如果出現在首位則僅作為普通字元。
[^xyz] 排除型(negate)字元集合。匹配未列出的任意字元。例如,“[^abc]”可以匹配“plain”中的“plin”。
[a-z] 字元範圍。匹配指定範圍內的任意字元。例如,“[a-z]”可以匹配“a”到“z”範圍內的任意小寫字母字元。
[^a-z] 排除型的字元範圍。匹配任何不在指定範圍內的任意字元。例如,“[^a-z]”可以匹配任何不在“a”到“z”範圍內的任意字元。

優先順序

優先順序為從上到下從左到右,依次降低:

運算子 說明
\ 轉義符
(), (?:), (?=), [] 括號和中括號
*、+、?、{n}、{n,}、{n,m} 限定符
^、$、\任何元字元 定位點和序列
選擇

更多正則表示式的內容可以參考以下連結:

regex的思導圖:

二、grep模式匹配命令

上面空談了那麼多正則表示式的內容也並沒有提及具體該如何使用它,實在枯燥,如果說正則表示式是一門武功話,那它也只能算得上一些口訣招式罷了,要把它真正練起來還得需要一些兵器在手才行,這裡我們要介紹的grep命令以及後面要講的sed,awk這些就該算作是這樣的兵器了。

1.基本操作

grep命令用於列印輸出文字中匹配的模式串,它使用正則表示式作為模式匹配的條件。grep支援三種正則表示式引擎,分別用三個引數指定:

引數 說明
-E POSIX擴充套件正則表示式,ERE
-G POSIX基本正則表示式,BRE
-P Perl正則表示式,PCRE

不過在你沒學過perl語言的大多數情況下你將只會使用到EREBRE,所以我們接下來的內容都不會討論到PCRE中特有的一些正則表示式語法(它們之間大部分內容是存在交集的,所以你不用擔心會遺漏多少重要內容)

在通過grep命令使用正則表示式之前,先介紹一下它的常用引數:

引數 說明
-b 將二進位制檔案作為文字來進行匹配
-c 統計以模式匹配的數目
-i 忽略大小寫
-n 顯示匹配文字所在行的行號
-v 反選,輸出不匹配行的內容
-r 遞迴匹配查詢
-A n n為正整數,表示after的意思,除了列出匹配行之外,還列出後面的n行
-B n n為正整數,表示before的意思,除了列出匹配行之外,還列出前面的n行
--color=auto 將輸出中的匹配項設定為自動顏色顯示

注:在大多數發行版中是預設設定了grep的顏色的,你可以通過引數指定或修改GREP_COLOR環境變數。

2.使用正則表示式

使用基本正則表示式,BRE

  • 位置

查詢/etc/group檔案中以"shiyanlou"為開頭的行

$ grep 'shiyanlou' /etc/group
$ grep '^shiyanlou' /etc/group

  • 數量
# 將匹配以'z'開頭以'o'結尾的所有字串
$ echo 'zero\nzo\nzoo' | grep 'z.*o'
# 將匹配以'z'開頭以'o'結尾,中間包含一個任意字元的字串
$ echo 'zero\nzo\nzoo' | grep 'z.o'
# 將匹配以'z'開頭,以任意多個'o'結尾的字串
$ echo 'zero\nzo\nzoo' | grep 'zo*'

注意:其中\n為換行符

  • 選擇
# grep預設是區分大小寫的,這裡將匹配所有的小寫字母
$ echo '1234\nabcd' | grep '[a-z]'
# 將匹配所有的數字
$ echo '1234\nabcd' | grep '[0-9]'
# 將匹配所有的數字
$ echo '1234\nabcd' | grep '[[:digit:]]'
# 將匹配所有的小寫字母
$ echo '1234\nabcd' | grep '[[:lower:]]'
# 將匹配所有的大寫字母
$ echo '1234\nabcd' | grep '[[:upper:]]'
# 將匹配所有的字母和數字,包括0-9,a-z,A-Z
$ echo '1234\nabcd' | grep '[[:alnum:]]'
# 將匹配所有的字母
$ echo '1234\nabcd' | grep '[[:alpha:]]'

下面包含完整的特殊符號及說明:

特殊符號 說明
[:alnum:] 代表英文大小寫位元組及數字,亦即 0-9, A-Z, a-z
[:alpha:] 代表任何英文大小寫位元組,亦即 A-Z, a-z
[:blank:] 代表空白鍵與 [Tab] 按鍵兩者
[:cntrl:] 代表鍵盤上面的控制按鍵,亦即包括 CR, LF, Tab, Del.. 等等
[:digit:] 代表數字而已,亦即 0-9
[:graph:] 除了空白位元組 (空白鍵與 [Tab] 按鍵) 外的其他所有按鍵
[:lower:] 代表小寫位元組,亦即 a-z
[:print:] 代表任何可以被列印出來的位元組
[:punct:] 代表標點符號 (punctuation symbol),亦即:" ' ? ! ; : # $...
[:upper:] 代表大寫位元組,亦即 A-Z
[:space:] 任何會產生空白的位元組,包括空白鍵, [Tab], CR 等等
[:xdigit:] 代表 16 進位的數字型別,因此包括: 0-9, A-F, a-f 的數字與位元組

注意:之所以要使用特殊符號,是因為上面的[a-z]不是在所有情況下都管用,這還與主機當前的語系有關,即設定在LANG環境變數的值,zh_CN.UTF-8的話[a-z],即為所有小寫字母,其它語系可能是大小寫交替的如,"a A b B...z Z",[a-z]中就可能包含大寫字母。所以在使用[a-z]時請確保當前語系的影響,使用[:lower:]則不會有這個問題。

# 排除字元
$ echo 'geek|good' | grep '[^o]'

注意:^放到中括號內為排除字元,否則表示行首。

使用擴充套件正則表示式,ERE

要通過grep使用擴充套件正則表示式需要加上-E引數,或使用egrep

  • 數量
# 只匹配"zo"
$ echo 'zero\nzo\nzoo' | grep -E 'zo{1}'
# 匹配以"zo"開頭的所有單詞
$ echo 'zero\nzo\nzoo' | grep -E 'zo{1,}'

注意:推薦掌握{n,m}即可,+,?,*,這幾個不太直觀,且容易弄混淆。

  • 選擇
# 匹配"www.shiyanlou.com"和"www.google.com"
$ echo 'www.shiyanlou.com\nwww.baidu.com\nwww.google.com' | grep -E 'www\.(shiyanlou|google)\.com'
# 或者匹配不包含"baidu"的內容
$ echo 'www.shiyanlou.com\nwww.baidu.com\nwww.google.com' | grep -Ev 'www\.baidu\.com'

注意:因為.號有特殊含義,所以需要轉義。

關於正則表示式和grep命令的內容就介紹這麼多,下面會介紹兩個更強大的工具sedawk,但同樣也正是因為這兩個工具的強大,我們的內容無法包含它們的全部,這裡將只對基本內容作介紹。

三、sed 流編輯器

sed工具在 man 手冊裡面的全名為"sed - stream editor for filtering and transforming text ",意即,用於過濾和轉換文字的流編輯器。

在 Linux/UNIX 的世界裡敢稱為編輯器的工具,大都非等閒之輩,比如前面的"vi/vim(編輯器之神)","emacs(神的編輯器)","gedit"這些個編輯器。sed與上述的最大不同之處大於它是一個非互動式的編輯器,下面我們就開始介紹sed這個編輯器。

sed常用引數介紹

sed 命令基本格式:

sed [引數]... [執行命令] [輸入檔案]...
# 形如:
$ sed -i '1s/sad/happy/' test # 表示將test檔案中第一行的"sad"替換為"happy"
引數 說明
-n 安靜模式,只打印受影響的行,預設列印輸入資料的全部內容
-e 用於在指令碼中新增多個執行命令一次執行,在命令列中執行多個命令通常不需要加該引數
-f filename 指定執行filename檔案中的命令
-r 使用擴充套件正則表示式,預設為標準正則表示式
-i 將直接修改輸入檔案內容,而不是列印到標準輸出裝置

sed編輯器的執行命令(這裡”執行“解釋為名詞)

sed執行命令格式:

[n1][,n2]command
[n1][~step]command
# 其中一些命令可以在後面加上作用範圍,形如:
$ sed -i 's/sad/happy/g' test # g表示全域性範圍
$ sed -i 's/sad/happy/4' test # 4表示指定行中的第四個匹配字串

其中n1,n2表示輸入內容的行號,它們之間為,逗號則表示從n1到n2行,如果為波浪號則表示從n1開始以step為步進的所有行;command為執行動作,下面為一些常用動作指令:

命令 說明
s 行內替換
c 整行替換
a 插入到指定行的後面
i 插入到指定行的前面
p 列印指定行,通常與-n引數配合使用
d 刪除指定行

sed操作舉例

我們先找一個用於練習的文字檔案:

$ cp /etc/passwd ~

列印指定行

# 列印2-5行
$ nl passwd | sed -n '2,5p'
# 列印奇數行
$ nl passwd | sed -n '1~2p'

行內替換

# 將輸入文字中"shiyanlou" 全域性替換為"hehe",並只打印替換的那一行,注意這裡不能省略最後的"p"命令
$ sed -n 's/shiyanlou/hehe/gp' passwd

注意: 行內替換可以結合正則表示式使用。

行間替換

$ nl passwd | grep "shiyanlou"
# 刪除第21行
$ sed -n '21c\www.shiyanlou.com' passwd

關於sed命令就介紹這麼多,你如果希望瞭解更多sed的高階用法,你可以參看如下連結:

四、awk文字處理語言

看到上面的標題,你可能會感到驚異,難道我們這裡要學習的是一門“語言”麼,確切的說,我們是要在這裡學習awk文字處理語言,只是我們並不會在這裡學習到比較完整的關於awk的內容,還是因為前面的原因,它太強大了,它的應用無處不在,我們無法在這裡以簡短的文字描述面面俱到,如果你有目標成為一個linux系統管理員,確實想學好awk,你一不用擔心,實驗樓會在之後陸續上線linux系統管理員的學習路徑,裡面會有單獨的關於正則表示式,awk,sed等相關課程,敬請期待吧。下面的內容,我們就作為一個關於awk的入門體驗章節吧,其中會介紹一些awk的常用操作。

1.awk介紹

AWK是一種優良的文字處理工具,Linux及Unix環境中現有的功能最強大的資料處理引擎之一.其名稱得自於它的創始人Alfred Aho(阿爾佛雷德·艾侯)、Peter Jay Weinberger(彼得·溫伯格)和Brian Wilson Kernighan(布萊恩·柯林漢)姓氏的首個字母.AWK程式設計語言,三位建立者已將它正式定義為“樣式掃描和處理語言”。它允許您建立簡短的程式,這些程式讀取輸入檔案、為資料排序、處理資料、對輸入執行計算以及生成報表,還有無數其他的功能。最簡單地說,AWK是一種用於處理文字的程式語言工具。

在大多數linux發行版上面,實際我們使用的是gawk(GNU awk,awk的GNU版本),在我們的環境中ubuntu上,預設提供的是mawk,不過我們通常可以直接使用awk命令(awk語言的直譯器),因為系統已經為我們建立好了awk指向mawk的符號連結。

$ ll /usr/bin/awk

nawk: 在 20 世紀 80 年代中期,對 awk語言進行了更新,並不同程度地使用一種稱為 nawk(new awk) 的增強版本對其進行了替換。許多系統中仍然存在著舊的awk 直譯器,但通常將其安裝為 oawk (old awk) 命令,而 nawk 直譯器則安裝為主要的 awk 命令,也可以使用 nawk 命令。Dr. Kernighan 仍然在對 nawk 進行維護,與 gawk 一樣,它也是開放原始碼的,並且可以免費獲得; gawk: 是 GNU Project 的awk直譯器的開放原始碼實現。儘管早期的 GAWK 發行版是舊的 AWK 的替代程式,但不斷地對其進行了更新,以包含 NAWK 的特性; mawk 也是awk程式語言的一種直譯器,mawk遵循 POSIX 1003.2 (草案 11.3)定義的 AWK 語言,包含了一些沒有在AWK 手冊中提到的特色,同時 mawk 提供一小部分擴充套件,另外據說mawk是實現最快的awk

2.awk的一些基礎概念

awk所有的操作都是基於pattern(模式)—action(動作)對來完成的,如下面的形式:

$ pattern {action}

你可以看到就如同很多程式語言一樣,它將所有的動作操作用一對{}花括號包圍起來。其中pattern通常是是表示用於匹配輸入的文字的“關係式”或“正則表示式”,action則是表示匹配後將執行的動作。在一個完整awk操作中,這兩者可以只有其中一個,如果沒有pattern則預設匹配輸入的全部文字,如果沒有action則預設為列印匹配內容到螢幕。

awk處理文字的方式,是將文字分割成一些“欄位”,然後再對這些欄位進行處理,預設情況下,awk以空格作為一個欄位的分割符,不過這不是固定了,你可以任意指定分隔符,下面將告訴你如何做到這一點。

3.awk命令基本格式

awk [-F fs] [-v var=value] [-f prog-file | 'program text'] [file...]

其中-F引數用於預先指定前面提到的欄位分隔符(還有其他指定欄位的方式) ,-v用於預先為awk程式指定變數,-f引數用於指定awk命令要執行的程式檔案,或者在不加-f引數的情況下直接將程式語句放在這裡,最後為awk需要處理的文字輸入,且可以同時輸入多個文字檔案。現在我們還是直接來具體體驗一下吧。

4.awk操作體驗

先用vim新建一個文字文件

$ vim test

包含如下內容:

I like linux
www.shiyanlou.com
  • 使用awk將文字內容列印到終端
# "quote>" 不用輸入
$ awk '{
> print
> }' test
# 或者寫到一行
$ awk '{print}' test

說明:在這個操作中我是省略了patter,所以awk會預設匹配輸入文字的全部內容,然後在"{}"花括號中執行動作,即print列印所有匹配項,這裡是全部文字內容

  • 將test的第一行的每個欄位單獨顯示為一行
$ awk '{
> if(NR==1){
> print $1 "\n" $2 "\n" $3
> } else {
> print}
> }' test

# 或者
$ awk '{
> if(NR==1){
> OFS="\n"
> print $1, $2, $3
> } else {
> print}
> }' test

說明:你首先應該注意的是,這裡我使用了awk語言的分支選擇語句if,它的使用和很多高階語言如C/C++語言基本一致,如果你有這些語言的基礎,這裡將很好理解。另一個你需要注意的是NROFS,這兩個是awk內建的變數,NR表示當前讀入的記錄數,你可以簡單的理解為當前處理的行數,OFS表示輸出時的欄位分隔符,預設為" "空格,如上圖所見,我們將欄位分隔符設定為\n換行符,所以第一行原本以空格為欄位分隔的內容就分別輸出到單獨一行了。然後是$N其中N為相應的欄位號,這也是awk的內建變數,它表示引用相應的欄位,因為我們這裡第一行只有三個欄位,所以只引用到了$3。除此之外另一個這裡沒有出現的$0,它表示引用當前記錄(當前行)的全部內容。

  • 將test的第二行的以點為分段的欄位換成以空格為分隔
$ awk -F'.' '{
> if(NR==2){
> print $1 "\t" $2 "\t" $3
> }}' test

# 或者
$ awk '
> BEGIN{
> FS="."
> OFS="\t"  # 如果寫為一行,兩個動作語句之間應該以";"號分開  
> }{
> if(NR==2){
> print $1, $2, $3
> }}' test

說明:這裡的-F引數,前面已經介紹過,它是用來預先指定待處理記錄的欄位分隔符。我們需要注意的是除了指定OFS我們還可以在print 語句中直接列印特殊符號如這裡的\tprint列印的非變數內容都需要用""一對引號包圍起來。上面另一個版本,展示了實現預先指定變數分隔符的另一種方式,即使用BEGIN,就這個表示式指示了,其後的動作將在所有動作之前執行,這裡是FS賦值了新的"."點號代替預設的" "空格

注意:首先說明一點,我們在學習和使用awk的時候應該儘可能將其作為一門程式語言來理解,這樣將會使你學習起來更容易,所以初學階段在練習awk時應該儘量按照我那樣的方式分多行按照一般程式語言的換行和縮排來輸入,而不是全部寫到一行(當然這在你熟練了之後是沒有任何問題的)。

6.awk常用的內建變數

變數名 說明
FILENAME 當前輸入檔名,若有多個檔案,則只表示第一個。如果輸入是來自標準輸入,則為空字串
$0 當前記錄的內容
$N N表示欄位號,最大值為NF變數的值
FS 欄位分隔符,由正則表示式表示,預設為" "空格
RS 輸入記錄分隔符,預設為"\n",即一行為一個記錄
NF 當前記錄欄位數
NR 已經讀入的記錄數
FNR 當前輸入檔案的記錄數,請注意它與NR的區別
OFS 輸出欄位分隔符,預設為" "空格
ORS 輸出記錄分隔符,預設為"\n"

關於awk的內容本課程將只會包含這些內容,如果你想了解更多,請期待後續課程,或者參看一下連結內容:

作業

1、練習其他幾個命令動作的使用。

  • 練習1: 結合正則表示式做更多練習。
  • 練習2: 參考下面的連結,掌握 sed 處理文字的基本原理,理解 pattern space 和 hold space 概念。 sed簡明教程 sed單行指令碼快速參考 sed完全手冊

  • 練習3: 基於 pattern space 和 hold space 實現將一個文字倒序輸出和交換奇數行和偶數行。

2、一個線上遊戲,當然我們主要目的是學習,這個遊戲也是有寓教於樂的性質,讓你快速學會vim的基礎操作: