Shell元字元(正規表示式)及其用法詳解

2020-07-16 10:04:34
在正規表示式中,我們把用於匹配的特殊符號又稱作元字元。在 Shell 中,元字元又分為基礎元字元擴充套件元字元

基礎元字元

我們先來看看到底有哪些基礎元字元,如表 1 所示。

表 1 基礎元字元
元字元 作 用
* 前一個字元匹配 0 次或任意多次
. 匹配除換行符外的任意一個字元
^ 匹配行首。例如,^hello 會匹配以 hello 開頭的行
$ 匹配行尾。例如,hello& 會匹配以 hello 結尾的行
[] 匹配屮柄號屮指定的任意一個字元,而且只匹配一個字元。例如.[aoeiu]匹配任意一個元音字母, [0-9] 匹配任意一位數位,[a-z][0-9] 匹配由小寫字母和一位數位構成的兩位字元
[^] 匹配除中括號中的字元以外的任意一個字元。例如,[^0-9] 匹配任意一位非數位字元,[^a-z] 匹配任意一位非小寫字母
跳脫符,用於取消特殊符號的含義
{n} 表示其前面的字元恰好出現 n 次。例如,[0-9]{4} 匹配4位元數位,[1][3-8][0-9]{9} 匹配手機號碼
(n,} 表示其前面的字元出現不少於 n 次。例如,[0-9]{2,} 匹配兩位及以上的數位
{n,m} 表示其前面的字元至少出現 n 次,最多出現 m 次。例如,[a-z]{6,8} 匹配 6?8 位的小寫字母

下面舉例來說明這些基礎元字元的作用。我們已經學習過的 grep 命令支援正規表示式,所以下面的練習都需要利用 grep 命令來演示。在使用 grep 命令開始練習之前,建議大家在 ~/.bashrc 檔案中建立這個別名,如下:

[[email protected] ~】# vi /root/.bashrc
alias grep='grep --color=auto'

這樣,grep 命令所匹配的字元都會使用顏色提示,更加容易理解正規表示式所具體匹配的字串。

練習檔案建立

既然正規表示式是用來在檔案中匹配字串的,那麼我們必須建立一個測試用的檔案,才可以進行後續的實驗。檔案如下:

[[email protected] ~]#vi test_rule.txt
Mr. Li Ming said:
he was the most honest man in LampBrother.
123despise him.

But since Mr. shen Chao came,
he never saaaid those words.
5555nice!

because,actuaaaally,
Mr. Shen Chao is the most honest man Later,Mr. Li ming soid his hot body.

這篇文件中加入了一些數位和故意寫錯的英文單詞,是為了後續的實驗。

"*"前一個字元匹配0次或任意多次

注意,"*"和萬用字元中的"*"含義不同,它代表前一個字元重複 0 次或任意多次。比如,"a*"並不是匹配"a"後面的任意字元,而是可以匹配所有內容,包括空白行。我們試試:

[[email protected] ~]# grep "a*" test_rule.txt
Mr. Li Ming said:
he was the most honest man in LampBrother.
123despise him.
But since Mr. shen Chao came, he never saaaid those words.
5555nice!
because,actuaaaally,
Mr. Shen Chao is the most honest man
Later,Mr. Li ming soid his hot body.

為什麼會這樣呢? "a*"代表匹配 0 個 a 或無數個 a,如果是匹配 0 個 a,也就是每個字元都會匹配,所以會匹配所有內容,包括空白行。所以"a*"這樣的正規表示式是沒有任何意義的。

如果這樣寫正規表示式"aa*",則代表這行字串一定要有一個 a,但是後面有沒有 a 都可以。也就是說,會匹配至少包含一個 a 的行。

[[email protected] ~]# grep "aa*" test_rule.txt Mr. Li Ming said:
he was the most honest man in LampBrother. But since Mr. shen Chao came, he never saaaid those words. because,actuaaaally,
Mr. Shen Chao is the most honest man Later,Mr. Li ming soid his hot body

如果正規表示式是"aaa*",則會匹配最少包含兩個連續 a 的字串。例如:

[[email protected] ~]# grep "aaa*" test_rule.txt
he never saaaid those words.
because,actuaaaally,

如果正規表示式是"aaaaa*",則會匹配最少包含 4 個連續 a 的字串。例如:

[[email protected] ~]# grep "aaaaa*" test_rule.txt
because,actuaaaally,

當然,如果再多寫一個 a,如"aaaaaa*",就不能從這篇文件中匹配任何內容了,因為這篇文件中 a 最多的單詞"actuaaaally"只有 4 個連續的 a,而"aaaaaa*"會匹配最少 5 個連續的 a。

"."匹配除換行符外的任意一個字元

正規表示式"."只能匹配一個字元,這個字元可以是任意字元。舉個例子:

[[email protected] ~]# grep "s..d" test_rule.txt
Mr. Li Ming said:
Later,Mr. Li ming soid his hot body.
# "s..d"會匹配在s和d這兩個字母之間一定有兩個字元的單詞

如果我想匹配在 s 和 d 字母之間有任意字元的單詞, 那麼該怎麼寫呢?"s*d"這個正規表示式肯定是不行的,因為它會匹配包含 d 字元的行,s*可以匹配任何字元。正確的寫法應該是"s.*d"。例如:

[[email protected] ~]# grep "s.*d" test_rule.txt
Mr. Li Ming said:
he never saaaid those words.
Later,Mr. Li ming soid his hot body.
#最後一句話比較有意思,匹配的是"soid his hot bod"

那麼,是否只寫"."就會匹配所有的內容呢?當然是這樣的,我們執行一下吧。

[[email protected] ~]# grep ".*" test_rule.txt
Mr. Li Ming said:
he was the most honest man in LampBrother.
123despise him.
But since Mr. shen Chao came,
he never saaaid those words.
5555nice!
because,actuaaaally,
Mr. Shen Chao is the most honest man
Later,Mr. Li ming soid his hot body


"^"匹配行首,"$"匹配行尾

"^"代表匹配行首,比如"^M"會匹配以大寫"M"開頭的行。

[[email protected] ~]# grep "^M" test_rule.txt
Mr. Li Ming said:
Mr. Shen Chao is the most honest man

"$"代表匹配行尾,比如"n$"會匹配以小寫"n"結尾的行。

[[email protected] ~]# grep "n$" test_rule.txt
Mr. Shen Chao is the most honest man

注意,如果文件是在 Windows 中寫入的,那麼"n$"是不能正確執行的,因為在 Windows 中換行符是"^M$",而在 Linux 中換行符是"$"。因為換行符不同,所以不能正確判斷行結尾字串。

那怎麼解決呢?也很簡單,執行命令"dos2unix 檔名"把文件格式轉換為 Linux 格式即可。如果沒有這個命令,則只需安裝 dos2unix 這個 RPM 包即可。

而"^$"則會匹配空白行。

[[email protected] ~]# grep -n "^$" test_rule.txt
4:
8:
11:

如果不加"-n"選項,空白行是沒有任何顯示的;加入了"-n"能看到空白行的行號。

"[]"匹配中括號中指定的任意一個字元,且只匹配一個字元

"[]"會匹配中括號中指定的任意一個字元,注意只能匹配一個字元。比如 [ao] 要麼匹配 a 字元,要麼匹配一個 o 字元。

[[email protected] ~]# grep "s[ao]id" test_rule.txt
Mr. Li Ming said:
Later,Mr. Li ming soid his hot body.
而"[0-9]"會匹配任意一個數位,例如:
[[email protected] ~]# grep "[0-9]" test_rule.txt 123despise him.
5555nice!
#列出包含有數位的行

而"[A-Z]"則會匹配任意一個大寫字母,例如:

[[email protected] ~]# grep "[A-Z|" test_rule.txt
Mr. Li Ming said:
he was the most honest man in LampBrother.
But since Mr. shen Chao came,
Mr. Shen Chao is the most honest man
Later,Mr. Li ming soid his hot body.
#列出包含大寫字母的行

如果正規表示式是"^[a-z]",則代表匹配以小寫字母開頭的行,例如:

[[email protected] ~]# grep "^[a-z]" test_rule.txt
he was the most honest man in LampBrother.
he never saaaid those words.
because,actuaaaally,


"[^]"匹配除中括號的字元以外的任意一個字元

這裡需要注意,如果"^"在 [] 外,則代表的是行首;如果在 [] 內,則代表的是取反。比如"^[a-z]"會匹配以小寫字母開頭的行,而"^[A-z]"會匹配不以小寫字母開頭的行。

[[email protected] ~]# grep "^[^a-z]" test_rule.txt
Mr. Li Ming said:
123despise him.
But since Mr. shen Chao came,
5555nice!
Mr. Shen Chao is the most honest man Later,Mr. Li ming soid his hot body.

而"^[^a-zA-Z]"會匹配不以字母開頭的行。

[[email protected] ~]# grep "^[^a-zA-Z]" test_rule.txt 123despise him.
5555nice!


""跳脫符

跳脫符會取消特殊符號的含義。如果想要匹配使用"."結尾的行,那麼正規表示式是".$"是不行的,因為"."在正規表示式中有特殊含義,代表任意一個字元。所以需要在前面加入跳脫符,如".$"。

[[email protected] ~]# grep ".$" test_rule.txt
he was the most honest man in LampBrother.
123despise him.
he never saaaid those words.
Later,Mr. Li ming soid his hot body.


"{n}"表示其前面的字元恰好出現 n 次

"{n}"中的 n 代表數位,這個正規表示式會匹配前一個字元恰好出現 n 次的字串,比如"zo{3}m"只能匹配"zooom"這個字串。例如,"a{3}"就會匹配 a 字母連續出現 3 次的字串。

[[email protected] ~]# grep "a{3}" test_rule.txt
he never saaaid those words,
because,actuaaaally,

上面的兩行都包含三個連續的 a,所以都會匹配。但是,如果想要只顯示三個連續的 a,則可以這樣來寫:

[[email protected] ~]# grep "[su]a{3}[il]" test_rule.txt
he never saaaid those words.
#只匹配3個連續的a
[[email protected] ~]# grep "[su]a{4}[il]" test_rule.txt because,actuaaaally,
# 只匹配4個連續的a

如果正規表示式是"[0-9]{3}",則會匹配包含三個連續數位的字串。

[[email protected] ~]# grep "[0-9]{3}" test_rule.txt
123despise him.
5555nice!

雖然"5555"有四個連續的數位,但是包含三個連續的數位,所以也是可以列出的。但是這樣不能體現出來"[0-9]{3}"只能匹配三個連續的數位,而不能匹配四個連續的數位。那麼正規表示式就應該這樣來寫: ^[0-9]{3}[a-z]。

[[email protected] -]# grep "^[0-9]{3}[a-z]" test_mle.txt
123despise him.
#只匹配以連續三個數位開頭的行
[[email protected] ~]# grep "^[0-9]{4}[a-z]"test_mle.txt
5555nice!
#只匹配以連續四個數位開頭的行

這樣就只能匹配包含 3 個連續的數位的行,而包含 4 個連續數位的行就不能匹配了。

"{n,}"表示其前面的字元出現不少於 n 次

"{n,}"會匹配前面的字元出現最少n次的字串。比如"zo{3,}m"這個正規表示式就會匹配在字母 z 和 m 之間最少有三個 o 的字串。那麼"^[(0-9]{3,}[a-z]"這個正規表示式就能匹配最少連續 3 個數位開頭的字串。

[[email protected] ~]# grep "^[0-9]{3,}[a-z]" test_rule.txt
123despise him.
5555nice!
#匹配最少以連續三個數位開頭的行

而"[su]a{3,}[il]"會匹配在字母 s 或 u 和 i 或 l 之間最少出現 3 個連續的 a 的字串。

[[email protected] ~]# grep "[su]a{3,}[il]" test_mle.txt
he never saaaid those words, because,actuaaaaly,
#匹配在字母 s 或 u 和 i 或 l 之間最少出現 3 個連續的 a 的字串


"{n,m}"表示其前面的字元至少出現n次,最多出現m次

"{n,m}"會匹配前一^字元最少出現 n 次、最多出現 m 次的字串,比如"{1,3}"能夠匹配字串"zom"、"zoom"和"zooom"。

還是用我們的例子檔案做實驗:

[[email protected] ~]# grep "sa{1,3}i" test_rule.txt
Mr. Li Ming said:
he never saaaid those words.
#匹配在字母s和字母i之間最少有一個a、最多有3個a的字串
[[email protected] ~]# grep "sa{2,3}i" test_rule.txt
he never saaaid those words.
#匹配在字母s和字母i之間最少有兩個a、最多有3個a的字串

擴充套件正規表示式

熟悉正規表示式的人應該很疑惑,在正規表示式中應該還可以支援一些元字元,比如"+"、"?"、"|"、"()"。

其實 Linux 是支援這些元字元的,只是 grep 命令預設不支援而已。如果要想支援這些元字元,則必須使用 egrep 或 grep -E 命令,所以我們又把這些元字元稱作擴充套件元字元

如果査詢 grep 命令的幫助,對 egrep 的說明就是和 grep -E 一樣的命令,所以我們可以把這兩個命令當作別名來對待。通過表 2 來看看 Shell 中支援的擴充套件元字元。

表 2 擴充套件元字元
擴充套件元字元 作 用
+ 前一個字元匹配 1 次或任意多次。
如“go+gle”會匹配“gogle” “google”或“gooogle”。當然,如果“o”有更多個,則也能匹配
? 前一個字元匹配 0 次或 1 次。
如 “colou?r” 可以匹配 “colour” 或 “color”
| 匹配兩個或多個分支選擇。
如“was|his”既會匹配包含“was”的行,也會匹配包含“his”的行
() 匹配其整體為一個字元,即模式單元。可以理解為由多個單個字元組成的大字元。
如“(dog)+”會匹配“dog” “dogdog” “dogdogdog”等,因為被()包含的字元會被當成一個整體。但 “hello(world|earth)” 會匹配 “hello world” 及 “hello earth”