UNIX 新手指南,第 3 部分:正則表達式
開始之前
了解本教程中包含的內(nèi)容以及如何最好地利用本教程。
關(guān)于本系列
這個包括四個部分的系列教程從頭開始介紹 Unix®。對于很久沒有使用類 UNIX 操作系統(tǒng)的用戶來說,這個初始的教程是個很好的溫習(xí)。對于具有 Windows® 使用經(jīng)驗的新的 UNIX 用戶來說,它也很有價值,因為其中參考了 Windows,并將兩者進行了比較。第二個教程重點講述 vi 文本編輯器,該編輯器是功能最強大(也最神秘)的可用 UNIX 實用程序之一。本教程向您講授有關(guān)使用正則表達式的 UNIX 命令行過濾器的知識,包括 grep、sed 和 awk。
關(guān)于本教程
要發(fā)揮 UNIX 命令行過濾器(如 grep、sed 和 awk)背后的強大功能,您需要非常熟悉正則表達式。本教程向新用戶講授其中每個實用程序的功能和如何使用正則表達式來操作文本。您將首先使用一個簡單和好玩的 grep 示例,然后繼續(xù)研究 sed 和 awk 的實際示例。
目標
本教程的目標是使 UNIX 和 Linux® 用戶習(xí)慣于使用這三個可用于快速和高效地搜索和更改數(shù)據(jù)的強大命令行工具。本教程開頭將解釋許多 UNIX 實用程序(及編程語言)基本框架中使用的正則表達式。隨后的各個部分將給出與 grep、sed 和 awk 一起使用的正則表達式的示例。
先決條件
對于本教程,您需要對命令行有基本的了解。對于本教程的某些部分,了解如何在 UNIX 中使用 stdin、stdout 和 pipe 來處理輸入和輸出是有所幫助的。
系統(tǒng)要求
在任何運行類 UNIX 操作系統(tǒng)的計算機上擁有您自己的帳戶,這是完成本教程所需的要求。類 UNIX 操作系統(tǒng)包括 IBM AIX® 操作系統(tǒng)、Linux®、Berkeley Software Distribution (BSD)、Mac OS® X(通過終端來訪問命令行),以及其他許多系統(tǒng)。
正則表達式
正則表達式是一個字符串,旨在用于搜索或替換另一個字符串。初看起來,這似乎是一個相當(dāng)基本的功能。大多數(shù)用戶都熟悉幾乎每個圖形文本編輯器或字處理應(yīng)用程序中都有的搜索和替換功能。如果將這個基本的搜索和替換功能比作計算器,則正則表達式可比作全功能的計算機。將正則表達式用于搜索條件的強大功能不應(yīng)被低估。
使用正則表達式的過濾器
一些基于 Unix 的最強大命令行工具使用了正則表達式,包括 grep、sed 和 awk(以及包括 Perl 在內(nèi)的一些編程語言)。在從基礎(chǔ) UNIX 命令行用戶轉(zhuǎn)變?yōu)檎嬲某売脩魰r,學(xué)習(xí)如何使用正則表達式是一個必需步驟。存在一些不同版本的正則表達式語法和多個版本的 grep、sed 和 awk,因此本教程將集中于每種實現(xiàn)中都具有的非常標準的最常見構(gòu)造。不要忘了參考您系統(tǒng)的 man 頁,以獲得有關(guān)語法和命令行選項的細節(jié)。
基礎(chǔ)
在探索使用正則表達式的 UNIX 應(yīng)用程序之前,了解基礎(chǔ)知識是非常重要的。在本部分中,您只需繼續(xù)往下閱讀。稍后您將在 grep 中嘗試一些示例。
基本搜索
正則表達式由一些普通字符和特殊字符組成,其中的特殊字符指示搜索條件。 在大多數(shù)基本情況下,正則表達式中也許根本就沒有使用特殊字符。例如,如果您只是希望使用詞條 golf 作為搜索條件,則可以輸入以下命令:
golf
這就是一個正則表達式!它搜索單詞 golf 的所有實例。正則表達式區(qū)分大小寫,因此這將搜索 golf 的所有實例,但是不會查找 Golf 的實例。
使用方括號
若要同時搜索 golf 和 Golf,您可以使用方括號(它們是正則表達式中的特殊字符),并列出一串要搜索的各個字符。這類似于搜索中的搜索(這就是正則表達式背后的神奇之處)。
[Gg]olf
同樣的概念也適用于任何字符列表——而不只是用于區(qū)分大小寫。例如,您可能希望搜索 golf 和 gelf(您虛構(gòu)的一種新體育運動):
g[oe]lf
句點
現(xiàn)在假設(shè)您有第三種體育運動 gilf,您也希望對其進行檢查。使用您到目前為止已學(xué)到的知識,一種方法是在您的搜索條件中使用 o、e 和 i。但是隨著您的搜索的逐步發(fā)展,您可能希望查找以 g 開頭、以 lf 結(jié)尾并且其間具有一個字符的所有字符串。為此,您可以使用另一個特殊字符,即句點 (.)。
g.lf
這將查找以 g 開頭和以 lf 結(jié)尾并且其間具有一個字符的所有字符串。若要將您的搜索擴展到以 g 開頭和以 f 結(jié)尾并且其間具有兩個字符的所有字符串,您可以使用兩個句點:
g..f
使用 grep 來搜索文件
現(xiàn)在您已經(jīng)對正則表達式背后的概念有了基本的了解,您可以開始使用實際的示例,以便能夠看到它們的實際運用。您將試驗的第一個命令行應(yīng)用程序是 grep。grep 的名稱實際上就來自于正則表達式:g/RE/p。grep 用于在一個或多個文件中搜索特定字符串的實例。缺省情況下,grep 輸出其中出現(xiàn)了您的搜索字符串的每一行(而不是僅輸出搜索字符串)。如果您在多個文件中執(zhí)行搜索,則 grep 還會輸出在其中找到該行的文件名。
使用以下文本創(chuàng)建一個名為 grep.txt 的文件:
I like golf.Golf is played on grass.I created gilf.
grep 的基本語法如下:
grep REGULAREXPRESSION FILENAME(S)
基本搜索
現(xiàn)在,返回到前面的第一個正則表達式示例:單獨的單詞 golf。若要與 grep 一起使用這個表達式,可輸入:
grep golf grep.txt
此命令在 grep.txt 文件中搜索字符串 golf 的所有實例,并輸出包含該字符串的行。您的輸出應(yīng)該類似如下:
I like golf.
使用方括號
下一步,試驗一些上面討論過的特殊字符。您可以使用方括號(方括號表達式)來指示您想要搜索 golf 和 Golf:
grep [gG]olf grep.txt
輸出應(yīng)該類似如下:
I like golf.Golf is played on grass.
句點
若要搜索 golf 和 gilf,您同樣可以使用方括號。取而代之的是,可以嘗試使用一個句點來指示您想要搜索 g 和 lf 之間的任何字符:
$grep g.lf grep.txt
輸出應(yīng)該類似如下:
I like golf.I created gilf.
搜索 golf、Golf 和 gilf
您現(xiàn)在已經(jīng)找到了獲得每種 golf 變體的方法,但是還沒有哪個搜索返回了所有三個實例:golf、Golf 和 gilf。花點時間考慮一下如何搜索所有三個實例。這可以通過多種方法來實現(xiàn)。下面是兩個示例:
grep ..lf grep.txtgrep [gG][oi]lf grep.txt
這兩種方法都返回所有三行:
I like golf.Golf is played on grass.I created gilf.
短橫線
您是否能夠想出更多的方法來完成此任務(wù)呢?到目前為止,您僅學(xué)習(xí)了兩個在正則表達式中使用的特殊字符。這只是開始!有些特殊字符在其他特殊字符之內(nèi)使用。例如,當(dāng)您將一組字符包括在方括號中時,您可以使用短橫線 (-) 來搜索一系列字符。將以下行添加到您的文本文件:
What is g2lf?
使用您到目前為止已學(xué)到的知識,您知道如果使用類似于 g.lf 或 g[oi2]lf 的正則表達式,則這一行將包括在搜索結(jié)果中。使用句點將返回在該位置具有任何字符的結(jié)果;使用 [oi2] 將返回僅在該位置具有 o i 或 2 的結(jié)果。通過使用一個短橫線,您可以實現(xiàn)第三種方法,其中不只包括少數(shù)字符,但并不是包括每個字符:
grep g[a-z]lf
此方法產(chǎn)生以下輸出:
I like golf.I created gilf.
從輸出中可以看到,此方法搜索落在 a 和 z 之間的任何字符(按字母順序)。這排除了在 g 和 lf 之間具有數(shù)字或符號的字符串,這些字符串不是真正的單詞,可能不屬于您所需的搜索條件。
方括號中的短橫線
通過在方括號中包括附加集合,您還可以搜索多個字符序列。例如,若要搜索 a-z 和 A-Z,可以使用以下搜索:
grep g[a-zA-Z]lf
脫字號 (^)
當(dāng)您的字符序列列表變得更長時,可能發(fā)現(xiàn)通過避免某些字符而不是指定想要查找的字符來進行搜索會更容易。這可以通過在搜索序列前在方括號中使用脫字符 (^) 來實現(xiàn)。這說起來挺復(fù)雜的,但是通過觀察一個示例,應(yīng)該是很容易理解的。通過使用以下 grep 命令來更改您的搜索,以避免數(shù)字但是包括所有其他字符:
grep g[^0-9]lf
此搜索類似于前面查找所有字母字符的搜索,但是此搜索還返回諸如數(shù)字符號 (#) 和美元符號 ($) 等不屬于字母并且也不在您排除的數(shù)字序列中的字符。
星號
要試驗的下一個特殊字符是星號 (*),它是若干個重復(fù)操作符之一。大多數(shù)人都非常熟悉在命令行上使用星號作為文件名搜索條件(通配符),但是在正則表達式中使用星號還是相當(dāng)新鮮的。星號指示搜索項(前一個字符或方括號表達式)可以出現(xiàn)零次、一次或多次。若要對此進行嘗試,請將以下行添加到您已經(jīng)在使用的 grep.txt 文件:
This time the o is missing in glf.Some people might say goolf.But they would not say goilf.
現(xiàn)在整個文件應(yīng)該類似如下:
I like golf.Golf is played on grass.I created gilf.What is g2lf?This time the o is missing in glf.Some people might say goolf.But they would not say goilf.
嘗試在 golf 中的 o 后面使用星號:
grep go*lf grep.txt
您的搜索將返回具有單詞 golf、glf 和 goolf 的行:
I like golf.This time the o is missing in glf.Some people might say goolf.
問號
另一個重復(fù)操作符是問號 (?)。問號的功能與星號類似,只不過搜索項可以出現(xiàn)零次或一次。多個實例將不匹配。使用問號取代星號來嘗試您剛才執(zhí)行的搜索:
grep go?lf grep.txt
可以看到,這次作為匹配結(jié)果返回了 golf 和 glf,但是沒有返回 goolf,因為其中存在問號前的搜索項 o 的多個實例:
I like golf.This time the o is missing in glf.
加號
最后一個常規(guī)重復(fù)操作符是加號 (+)。加號將查找某個搜索項出現(xiàn)一次或多次的情況。與星號不同,必須至少找到一個實例才會匹配。請嘗試以下示例:
grep go+lf grep.txt
這次,該搜索返回 golf 和 goolf,但它不返回 glf,因為沒有找到 o:
I like golf.Some people might say goolf.
行首和行尾定位點
在轉(zhuǎn)向 sed 之前,最后要學(xué)習(xí)的特殊字符是行首定位點(使用脫字符來實現(xiàn))和行尾定位點(使用美元符號來實現(xiàn))。您可能記得,您在本教程的前面使用過脫字符來對方括號表達式取反。當(dāng)在方括號之外使用脫字符時,它執(zhí)行完全不同的功能。將脫字符放在正則表達式開頭將告訴該搜索僅操作行的開頭。換句話說,正則表達式中的第一個字符(脫字符之后)必須與新行上的第一個字符匹配才能匹配該行。類似地,將美元符號放在正則表達式的結(jié)尾以指示您僅希望返回與行尾匹配的結(jié)果。換句話說,正則表達式中的最后一個字符(美元符號之前)必須與某行上的最后一個字符匹配才能匹配該行。若要對此進行測試,請將以下兩行添加到 grep.txt:
golf has been a fine examplelet's talk about something besides golf
請注意,對于此測試,您不應(yīng)該對 golf 進行大寫或加標點,因為它將演示一個針對同一單詞的搜索,此搜索使用定位點在行尾或行首以不同的方式操作。若要測試行首定位點,請輸入以下命令:
grep ^golf grep.txt
輸出應(yīng)該類似如下:
golf has been a fine example
若要測試行尾定位點,請使用同一個搜索,但是刪除脫字符并在 golf 之后添加一個美元符號。
grep golf$ grep.txt
使用行尾定位點的輸出類似如下:
let's talk about something besides golf
小結(jié)
現(xiàn)在您已經(jīng)通過在命令行上使用 grep 來學(xué)習(xí)了正則表達式的基礎(chǔ)知識。下一步,您將學(xué)習(xí)使用 sed,此實用程序不僅搜索文本,而且還對搜索結(jié)果進行替換。首先,下面是對您到目前為止已學(xué)習(xí)過的內(nèi)容的小結(jié):
. 句點表示任何單個字符[] 方括號包括一個字符序列- 短橫線在字符之間使用以創(chuàng)建一個序列(在 [] 內(nèi))^ 脫字符用于對序列(在 [] 內(nèi))取反* 星號搜索某個搜索項的零個、一個或多個實例? 問號搜索某個搜索項的零個或一個實例+ 加號搜索某個搜索項的一個或多個實例$ 美元符號搜索行尾^ 脫字符搜索行首 特殊字符前的反斜杠使該字符成為普通字符(請參見下一部分。)
使用 sed 來編輯文件
sed 是流編輯器 (stream editor) 的簡寫。文本編輯器的傳統(tǒng)、現(xiàn)代定義是可用于創(chuàng)建和編輯文本文件的交互式應(yīng)用程序。sed 也是一個文本編輯器,但它是一個命令行實用程序而不是交互式實用程序,從而使之成為一個極其強大的批處理編輯工具。sed 通常在 Unix Shell 腳本中用于過濾較大的文本文件集。在本教程的第一部分中,您使用了一個討論 golf 的小型測試文件。為了演示 sed 編輯器的高級功能,您將使用一個很小的代碼片段,開發(fā)人員可能希望在批處理過程中更改該代碼片段。
請將以下文本復(fù)制并粘貼到一個名為 sed.txt 的文件中:
system "echo 'project:$project' >> logfile";system "echo 'version:$version' >> logfile";system "echo 'optionalid:$optionalid' >> logfile";system "echo 'nodes:$nodes' >> logfile";system "echo 'threads:$threads' >> logfile";
正斜杠
前面解釋過的用于 grep 的所有特殊字符在 sed 中也有效。然而,若要使用 sed,您必須了解一些附加語法。sed 中的基本表達式由四個部分組成,各個部分之間用正斜杠 (/) 分隔。以下是用于基本 sed 命令的常見語法:
sed s/REGULAREXPRESSION/REPLACEMENTSTRING/flags INPUT_FILE
s——搜索和替換
s 指示您希望執(zhí)行搜索和替換。正斜杠用于綁定 sed 中的正則表達式。例如,如果您只希望將詞條 logfile 替換為 logfile.txt,則可以運行以下命令:
sed s/logfile/logfile.txt/ sed.txt
輸出應(yīng)該類似如下:
system "echo 'project:$project' >> logfile.txt";system "echo 'version:$version' >> logfile.txt";system "echo 'optionalid:$optionalid' >> logfile.txt";system "echo 'nodes:$nodes' >> logfile.txt";system "echo 'threads:$threads' >> logfile.txt";
在此情況下要注意的一個要點在于,sed 不會實際更改 sed.txt 的內(nèi)容。相反,它將輸出發(fā)送到標準輸出設(shè)備。對于這些示例,您將把輸出發(fā)送到標準輸出設(shè)備,以便能夠立即看到操作結(jié)果。
為便于將來參考,可以捕獲輸出或?qū)⑵浒l(fā)送到某個新文件。例如,若要將輸出發(fā)送到 sed_new.txt,可以運行以下命令:
sed s/logfile/logfile.txt/ sed.txt > sed_new.txt
反斜杠
在學(xué)習(xí)使用斜杠的同時,還有另一個非常重要的特殊字符需要學(xué)習(xí)。反斜杠 () 稱為轉(zhuǎn)義字符,因為它對正則表達式解釋中的下一個字符進行轉(zhuǎn)義。更簡單的是,將一個反斜杠放在特殊字符前,將使該字符成為普通項而不是命令項。這非常重要,因為許多文件(尤其是在編寫代碼的時候)廣泛利用了與用于執(zhí)行正則表達式的字符相同的字符。在您的 sed.txt 文件中,您會注意到美元符號的使用。如果您希望替換 $project 而不替換 project,則需要在搜索和替換中使用轉(zhuǎn)義字符:
sed s/$project/$project_name/ sed.txt
您可以在輸出中看到 $project 被更改了,但是 project 沒有被更改。
system "echo 'project:$project_name' >> logfile";system "echo 'version:$version' >> logfile";system "echo 'optionalid:$optionalid' >> logfile";system "echo 'nodes:$nodes' >> logfile";system "echo 'threads:$threads' >> logfile";
更改某個項的多個實例
這引入了 sed 中的另一個重要功能。如果您希望同時更改 project 的兩個實例,該怎么辦呢?通過到目前為止已學(xué)到的知識,合理的回答是只需使用 project 作為正則表達式,但是此回答并不是非常正確。下面將繼續(xù)并進行嘗試,以便能夠演示和解釋該過程:
sed s/project/project_name/ sed.txt
在輸出中可以看到,project 的第一個實例被更改為 project_name:
system "echo 'project_name:$project' >> logfile";system "echo 'version:$version' >> logfile";system "echo 'optionalid:$optionalid' >> logfile";system "echo 'nodes:$nodes' >> logfile";system "echo 'threads:$threads' >> logfile";
然而,第二個實例未更改,盡管它肯定匹配您的正則表達式。您從第一個示例中知道,sed 似乎更改其輸入中的每個匹配字符串,而不是僅更改第一個匹配字符串,因為它更改 logfile 的每個實例。
區(qū)別在于,logfile 的每個實例在單獨的行上,而同一行上卻有兩個 project 實例。這為什么非常重要?因為 sed 被實現(xiàn)為一個行編輯器。它一次將一個單獨的行放到內(nèi)存中,并將其作為單個單元來操作。在運行 sed 時務(wù)必記住這點,因為所有命令行選項都是按這個設(shè)計原則來設(shè)計的(從而使大多數(shù) sed 實現(xiàn)不會受到與系統(tǒng)內(nèi)存有關(guān)的文件大小限制)。缺省情況下,每一行都視為 sed 命令的一次新的執(zhí)行。盡管在第一個示例中似乎不是這樣,但是其中 sed 命令僅替換匹配字符串的第一個實例。然而,您可以簡單地使用一個 g 標志來改變此行為。
g 標志
執(zhí)行同樣的 sed 命令,但這次在結(jié)尾附加一個 g:
sed s/project/project_name/g sed.txt
這次,第一行上的兩個 project 實例都被更改為 project_name:
system "echo 'project_name:$project_name' >> logfile";system "echo 'version:$version' >> logfile";system "echo 'optionalid:$optionalid' >> logfile";system "echo 'nodes:$nodes' >> logfile";system "echo 'threads:$threads' >> logfile";
相關(guān)文章:
