目前爲止, shell腳本常見的一個用途就是處理文本文件。檢查日誌文件、讀取配置 文件、處理數據元素,shell腳本可以幫助我們將文本文件中各種數據的日常處理任務自動化。但僅靠shell腳本命令來處理文本文件的內容有點勉爲其難。如果想在shell腳本中處理任何類型的數據,你得熟悉Linux中的sed和gawk工具。這兩個工具能夠極大簡化需要進行的數據處理任務。
1.1 文本處理
1.1.1 sed編輯器
sed編輯器被稱作流編輯器(stream editor),和普通的交互式文本編輯器恰好相反。在交互式文本編輯器中(比如vim),你可以用鍵盤命令來交互式地插入、刪除或替換數據中的文本。流編輯器則會在編輯器處理數據之前基於預先提供的一組規則來編輯數據流。
sed編輯器可以根據命令來處理數據流中的數據,這些命令要麼從命令行中輸入,要麼存儲在一個命令文本文件中。
sed編輯器會執行下列操作。
(1) 一次從輸入中讀取一行數據。
(2) 根據所提供的編輯器命令匹配數據。
(3) 按照命令修改流中的數據。
(4) 將新的數據輸出到STDOUT。
在流編輯器將所有命令與一行數據匹配完畢後,它會讀取下一行數據並重復這個過程。在流編輯器處理完流中的所有數據行後,它就會終止。
由於命令是按順序逐行給出的,sed編輯器只需對數據流進行一遍處理就可以完成編輯操作。 這使得sed編輯器要比交互式編輯器快得多,你可以快速完成對數據的自動修改。
sed命令的格式如下:
sed options script filesed命令選項
----------------------------------------------------------------------------------------------------------------------------------------------------------------
選項 描述
-e script 在處理輸入時,將script中指定的命令添加到已有的命令中
-f file 在處理輸入時,將file中指定的命令添加到已有的命令中
-n 不產生命令輸出,使用print命令來完成輸出
----------------------------------------------------------------------------------------------------------------------------------------------------------------
script參數指定了應用於流數據上的單個命令。如果需要用多個命令,要麼使用-e選項在命令行中指定,要麼使用-f選項在單獨的文件中指定。有大量的命令可用來處理數據。
① 在命令行定義編輯器命令
默認情況下,sed編輯器會將指定的命令應用到STDIN輸入流上。這樣你可以直接將數據通過管道輸入sed編輯器處理。這裏有個簡單的示例
這個例子在sed編輯器中使用了s命令。s命令會用斜線間指定的第二個文本字符串來替換第一個文本字符串模式。即:在本例中是big test替換了test
在運行這個例子時,結果應該立即就會顯示出來。這就是使用sed編輯器的強大之處。你可以同時對數據做出多處修改,而所消耗的時間卻只夠一些交互式編輯器啓動而已
下面來嘗試編輯整個文件,看下處理速度如何
sed命令幾乎瞬間就執行完並返回數據。在處理每行數據的同時,結果也顯示出來了。可以 在sed編輯器處理完整個文件之前就開始觀察結果
sed編輯器並不會修改文本文件的數據。它只會將修改後的數據發送到 STDOUT。如果你查看原來的文本文件,它仍然保留着原始數據
② 在命令行使用多個編輯器命令
要在sed命令行上執行多個命令時,只要用 -e選項 就可以了
兩個替換命令都作用到文件中的每行數據上。命令之間必須用分號隔開,並且在命令末尾和分號之間不能有空格
如果不想用分號,也可以用bash shell中的次提示符來分隔命令。只要輸入第一個單引號標示出sed程序腳本的起始(sed編輯器命令列表),bash會繼續提示你輸入更多命令,直到輸入了標示結束的單引號
( 文本中沒有的就不進行替換,比如此處的將A改爲a就沒有執行 )
必須記住,要在結束的單引號 所在行結束命令。bash shell一旦發現了封尾的單引號,就會執行sed命令
③ 從文件中讀取編輯器命令
如果有大量要處理的sed命令,那麼將它們放進一個單獨的文件中通常會更方便一些。可以在sed命令中用 -f 選項來指定文件
在這種情況下,不用在每條命令後面放一個分號。sed編輯器知道每行都是一條單獨的命令。 跟在命令行輸入命令一樣,sed編輯器會從指定文件中讀取命令,並將它們應用到數據文件中的每一行上
爲了避免把sed編輯器腳本文件與bash shell腳本文件搞混,可以使用.sed作爲sed腳本文件的擴展名
1.1.2 gawk 程序
gawk能提供一個個類編程環境來修改和重新組織文件中的數據。
gawk程序是Unix中的原始awk程序的GNU版本。gawk程序讓流編輯邁上了一個新的臺階,它提供了一種編程語言而不只是編輯器命令。在gawk編程語言中,你可以做下面的事情:
定義變量來保存數據;
使用算術和字符串操作符來處理數據;
使用結構化編程概念(比如if-then語句和循環)來爲數據處理增加處理邏輯;
通過提取數據文件中的數據元素,將其重新排列或格式化,生成格式化報告。
gawk程序的報告生成能力通常用來從大文本文件中提取數據元素,並將它們格式化成可讀的報告。其中最完美的例子是格式化日誌文件。在日誌文件中找出錯誤行會很難,gawk程序可以讓你從日誌文件中過濾出需要的數據元素,然後你可以將其格式化,使得重要的數據更易於閱讀
① gawk 命令格式
gawk options program file
--------------------------------------------------------------------------------------------------------------------------------------------
gawk選項
選項 描述
-F fs 指定行中劃分數據字段的字段分隔符
-f file 從指定的文件中讀取程序
-v var=value 定義gawk程序中的一個變量及其默認值
-mf N 指定要處理的數據文件中的最大字段數
-mr N 指定數據文件中的最大數據行數
-W keyword 指定gawk的兼容模式或警告等級
--------------------------------------------------------------------------------------------------------------------------------------------
命令行選項提供了一個簡單的途徑來定製gawk程序中的功能。gawk的強大之處在於程序腳本。可以寫腳本來讀取文本行的數據,然後處理並顯示數據,創 建任何類型的輸出報告。
② 從命令行讀取程序腳本
gawk程序腳本用一對花括號來定義。你必須將腳本命令放到兩個花括號{ }中。如果你錯誤地使用了圓括號來包含gawk腳本,就會得到一條類似於下面的錯誤提示
由於gawk命令行假定腳本是單個文本字符串,你還必須將腳本放到單引號中
所以格式應該爲 gawk ' { } '
如果運行這個命令,什麼都不會發生。原因在於沒有在命令行上指定文件名,所以gawk程序會從STDIN接收數據。在運行這個程序時,它會一直等待從STDIN輸入的文本
如果你輸入一行文本並按下回車鍵,gawk會對這行文本運行一遍程序腳本。跟sed編輯器一 樣,gawk程序會針對數據流中的每行文本執行程序腳本。由於程序腳本被設爲顯示一行固定的文本字符串,因此不管你在數據流中輸入什麼文本,都會得到同樣的文本輸出。 ( 如下 )
要終止這個gawk程序,你必須表明數據流已經結束了。bash shell提供了一個組合鍵來生成 EOF(End-of-File)字符。Ctrl+D組合鍵會在bash中產生一個EOF字符。這個組合鍵能夠終止該gawk 程序並返回到命令行界面提示符下
③ 使用數據字段變量
gawk的主要特性之一是其處理文本文件中數據的能力。它會自動給一行中的每個數據元素分配一個變量。默認情況下,gawk會將如下變量分配給它在文本行中發現的數據字段:
$0代表整個文本行;
$1代表文本行中的第1個數據字段;
$2代表文本行中的第2個數據字段;
$n代表文本行中的第n個數據字段
在文本行中,每個數據字段都是通過字段分隔符劃分的。gawk在讀取一行文本時,會用預定義的字段分隔符劃分每個數據字段。gawk中默認的字段分隔符是任意的空白字符(例如空格或製表符)
在下面的例子中,gawk程序讀取文本文件,通過$1變量只讓其顯示第1個數據字段的值。
如果你要讀取採用了其他字段分隔符的文件,可以用-F 選項指定
這個簡短的程序顯示了系統中密碼文件的第1個數據字段。由於/etc/passwd文件用冒號來分隔數字字段,因而如果要劃分開每個數據元素,則必須在gawk選項中將冒號指定爲字段分隔符
④ 在程序腳本中使用多個命令
如果一種編程語言只能執行一條命令d的話,那麼它不會有太大用處。gawk編程語言允許你將多條命令組合成一個正常的程序。要在命令行上的程序腳本中使用多條命令,只要在命令之間放個分號即可
第一條命令會給字段變量$4賦值。第二條命令會打印整個數據字段。注意, gawk程序在輸出中已經將原文本中的第四個數據字段替換成了新值
也可以用次提示符一次一行地輸入程序腳本命令
在你用了表示起始的單引號後,bash shell會使用次提示符來提示你輸入更多數據。你可以每次在每行加一條命令,直到輸入了結尾的單引號。
如果是沒有在命令行中指定文件名,gawk程序會從STDIN中獲得數據。當運行這個程序的時候,它會等着讀取來自STDIN的文本。要退出程序, 只需按下Ctrl+D組合鍵來表明數據結束
⑤ 從文件中讀取程序
跟sed編輯器一樣,gawk編輯器允許將程序存儲到文件中,然後再在命令行中引用
script.gawk程序腳本會使用分隔符爲:,print命令打印/etc/passwd文件的主目錄數據字段(字段變量$6),以及userid數據字段(字段變量$1)
可以在程序文件中指定多條命令。要這麼做的話,只要一條命令放一行即可,不需要用分號
script1.gawk程序腳本定義了一個變量來保存print命令中用到的文本字符串。注意,gawk 程序在引用變量值時並未像shell腳本一樣使用美元符。
⑥ 在處理數據前運行腳本
gawk還允許指定程序腳本何時運行。默認情況下,gawk會從輸入中讀取一行文本,然後針對該行的數據執行程序腳本。有時可能需要在處理數據前運行腳本,比如爲報告創建標題。BEGIN關鍵字就是用來做這個的。BEGIN關鍵字也就是會強制gawk在讀取數據前執行BEGIN關鍵字後所指定的程序命令或腳本。
這次print命令會在讀取數據前顯示文本。但在它顯示了文本後,它會快速退出,不等待任何數據。如果想使用正常的程序腳本中處理數據,必須用另一個腳本區域來定義程序
在gawk執行了BEGIN腳本後,它會用第二段腳本來處理文件數據。這麼做時要小心,兩段 腳本仍然被認爲是gawk命令行中的一個文本字符串。你需要相應地加上單引號。
⑦ 在處理數據後運行腳本
與BEGIN關鍵字類似,END關鍵字允許你指定一個程序腳本,gawk會在讀完數據後執行它
當gawk程序打印完文件內容後,它會執行END腳本中的命令。這是在處理完所有正常數據後給報告添加頁腳的最佳方法。 可以將所有這些內容放到一起組成一個漂亮的小程序腳本文件,用它從一個簡單的數據文件中創建一份完整的報告
#cat script1.gawk
這個腳本用BEGIN腳本來爲報告創建標題。它還定義了一個叫作FS的特殊變量。這是定義字段分隔符的另一種方法。這樣你就不用依靠腳本用戶在命令行選項中定義字段分隔符了。
下面是這個gawk程序腳本的輸出(有部分刪節)。
與預想的一樣,BEGIN腳本創建了標題,程序腳本處理特定數據文件(/etc/passwd)中的信息,END腳本生成頁腳。
1.2 sed編輯器基礎
成功使用sed編輯器的關鍵在於掌握其各式各樣的命令和格式,他們能夠幫助你定製文本編輯行爲。
1.2.1 更多的替換選項
上面已經講述瞭如何使用 s(命令)來在行中替換文本,這個命令還有另外一些選項讓事情變得更爲簡單
① 替換標記
替換命令在替換多行中的文本時能正常工作,但默認情況下它只替換每行中出現的第一處。 要讓替換命令能夠替換一行中不同地方出現的文本必須使用替換標記(substitution flag)。替換標記會在替換命令字符串之後設置
s/pattern/replacement/flags 即 s/匹配字段/替換字段/標記
有4種可用的替換標記:
數字,表明新文本將替換第幾處模式匹配的地方;
g,表明新文本將會替換所有匹配的文本;
p,表明原先行的內容要打印出來;
w file,將替換的結果寫到文件中。
在第一類替換中,可以指定sed編輯器用新文本替換第幾處模式匹配的地方
將替換標記指定爲2的結果就是:sed編輯器只替換每行中第二次出現的匹配模式。g替換標記能替換文本中匹配模式所匹配的每處地方。
p替換標記會打印與替換命令中指定的模式匹配的行。這通常會和sed的-n選項一起使用
-n選項將禁止sed編輯器輸出。但p替換標記會輸出修改過的行。-n 與 p 二者配合使用的效果就是 只輸出被替換命令修改過的行。
sed 的標記 只有g標記替換所有匹配到的字符,其他標記都是默認只替換每行中第一次出現的字符。
w替換標記會產生同樣的輸出,不過會將匹配到的修改行輸出保存到指定文件中。
② 替換字符
有時你會在文本字符串中遇到一些不太方便在替換模式中使用的字符。Linux中一個常見的 例子就是正斜線(/)。由於正斜線通常用作字符串分隔符,因而如果它出現在了模式文本中的話,必須用反斜線來轉義。這通常會帶來一些困惑和錯誤。
1.2.2. 使用地址
默認情況下,在sed編輯器中使用的命令會作用於文本數據的所有行。如果只想將命令作用於特定行或某些行,則必須用行尋址(line addressing)
在sed編輯器中有兩種形式的行尋址:
以數字形式表示行區間
用文本模式來過濾出行
兩種形式都使用相同的格式來指定地址:
[address] command
也可以將特定地址的多個命令分組:
address {
command1
command2
command3
}
sed編輯器會將指定的每條命令作用到匹配指定地址的行上。本節將會演示如何在sed編輯器腳本中使用兩種尋址方法。
① 數字方式的行尋址
當使用數字方式的行尋址時,可以用行在文本流中的行位置來引用。sed編輯器會將文本流中的第一行編號爲1,然後繼續按順序爲接下來的行分配行號。
在命令中指定的地址可以是單個行號,或是用起始行號、逗號以及結尾行號指定的一定區間範圍內的行。這裏有個sed命令作用到指定行號的例子。
sed編輯器只修改地址指定的第二行的文本。這裏有另一個例子,這次使用了行地址區間
如果想將命令作用到文本中從某行開始的所有行,可以用特殊地址——美元符
可能你並不知道文本中到底有多少行數據,因此美元符用起來通常很方便。
② 使用文本過濾器
另一種限制命令作用到哪些行上的方法會稍稍複雜一些。sed編輯器允許指定文本模式來過濾出命令要作用的行。格式如下:
/pattern/command必須用正斜線將要指定的pattern封起來。sed編輯器會將該命令作用到包含指定文本模式的行上
例如,如果你想修改用戶ZHJIAB的默認shell,可以使用sed命令
( 本例使用 -n選項禁止輸出,有使用 p 標記輸出匹配的行,同時使用)
該命令只作用到匹配文本模式的行上。雖然使用固定文本模式能幫你過濾出特定的值,就跟上面這個用戶名的例子一樣,但其作用難免有限。sed編輯器在文本模式中採用了一種稱爲正則表達式(regular expression)的特性來幫助你創建匹配效果更好的模式。
正則表達式允許創建高級文本模式匹配表達式來匹配各種數據。這些表達式結合了一系列通配符、特殊字符以及固定文本字符來生成能夠匹配幾乎任何形式文本的簡練模式。
③ 命令組合
如果需要在單行上執行多條命令,可以用花括號將多條命令組合在一起。sed編輯器會處理地址行處列出的每條命令。
兩條命令都會作用到該地址上。當然,也可以在一組命令前指定一個地址區間
sed編輯器會將所有命令作用到該地址區間內的所有行上。
1.2.3 刪除行
文本替換命令不是sed編輯器唯一的命令。如果需要刪除文本流中的特定行,可以用刪除命令。
刪除命令d名副其實,它會刪除匹配指定尋址模式的所有行。使用該命令時要特別小心,如果你忘記加入尋址模式的話,流中的所有文本行都會被刪除。
當和指定地址一起使用時,刪除命令顯然能發揮出大的功用。可以從數據流中刪除特定的文本行,通過行號指定:
或者通過特定區間指定:
或者通過特殊的文件結尾字符:
sed編輯器的模式匹配特性也適用於刪除命令
.
sed編輯器會刪掉包含匹配指定模式的行
注意:
sed編輯器不會修改原始文件。你刪除的行只是從sed編輯器的輸出中消失了。原始 文件仍然包含那些“刪掉的”行。
也可以使用兩個文本模式來刪除某個區間內的行,但這麼做時要小心。你指定的第一個模式會“打開”行刪除功能,第二個模式會“關閉”行刪除功能。sed編輯器會刪除兩個指定行之間 的所有行(包括指定的行)
除此之外,你要特別小心,因爲只要sed編輯器在數據流中匹配到了開始模式,刪除功能就會打開。這可能會導致意外的結果
第二個出現數字“1”的行再次觸發了刪除命令,因爲沒有找到停止模式,所以就將數據流中的剩餘行全部刪除了。當然,如果你指定了一個從未在文本中出現的停止模式,顯然會出現另外一個問題
因爲刪除功能在匹配到第一個模式的時候打開了,但一直沒匹配到結束模式,所以整個數據流都被刪掉了
1.2.4 插入和附加文本
跟其他編輯器類似,sed編輯器允許向數據流插入和附加文本行。兩個操作的區別可能比較讓人費解:
插入(insert)命令(i)會在指定行前增加一個新行;
附加(append)命令(a)會在指定行後增加一個新行。
這兩條命令的費解之處在於它們的格式。它們不能在單個命令行上使用。你必須指定是要將 行插入還是附加到另一行。格式如下
sed '[address]command\ new line'
new line中的文本將會出現在sed編輯器輸出中你指定的位置。記住,當使用插入命令時,文本會出現在數據流文本的前面。 當使用附加命令時,文本會出現在數據流文本的後面
在命令行界面提示符上使用sed編輯器時,你會看到次提示符來提醒輸入新的行數據。你必須在該行完成sed編輯器命令。一旦你輸入了結尾的單引號,bash shell就會執行該命令
這樣能夠給數據流中的文本前面或後面添加文本,但如果要向數據流內部添加文本呢?
要向數據流行內部插入或附加數據,你必須用尋址來告訴sed編輯器你想讓數據出現在什麼位置。可以在用這些命令時只指定一個行地址。可以匹配一個數字行號或文本模式,但不能用地址區間。這合乎邏輯,因爲你只能將文本插入或附加到單個行的前面或後面,而不是行區間的前面或後面。
下面的例子是將一個新行插入到數據流第三行前
下面的例子是將一個新行附加到數據流中第三行後
它使用與插入命令相同的過程,只是將新文本行放到了指定的行號後面。如果你有一個多行數據流,想要將新行附加到數據流的末尾,只要用代表數據後一行的美元符就可以了
同樣的方法也適用於要在數據流起始位置增加一個新行。只要在第一行之前插入新行即可。
要插入或附加多行文本,就必須對要插入或附加的新文本中的每一行使用反斜線,直到後 一行。
1.2.5 修改行
修改(change)命令允許修改數據流中整行文本的內容。它跟插入和附加命令的工作機制 一樣,你必須在sed命令中單獨指定新行( 這個是指定修改第3行的數據 )
在這個例子中,sed編輯器會修改第三行中的文本。也可以用文本模式來尋址。
( 這個是匹配Line 3,然後修改其所在行 )
文本模式修改命令會修改它匹配的數據流中的任意文本行:
在修改命令中使用地址區間,但結果未必如願
sed編輯器會用這一行文本來替換數據流中的兩行文本,而不是逐一修改這兩行文本
1.2.6 轉換命令
轉換(transform)命令(y)是唯一可以處理單個字符的sed編輯器命令。轉換命令格式 如下。
[address] y/inchars/outchars/轉換命令會對inchars和outchars值進行一對一的映射。inchars中的第一個字符會被轉 換爲outchars中的第一個字符,第二個字符會被轉換成outchars中的第二個字符。這個映射過 程會一直持續到處理完指定字符。如果inchars和outchars的長度不同,則sed編輯器會產生一 條錯誤消息
這裏有個使用轉換命令的簡單例子
如你在輸出中看到的,inchars模式中指定字符的每個實例都會被替換成outchars模式中 相同位置的那個字符。 轉換命令是一個全局命令,也就是說,它會文本行中找到的所有指定字符自動進行轉換,而 不會考慮它們出現的位置
sed編輯器轉換了在文本行中匹配到的字符1的兩個實例。你無法限定只轉換在特定地方出現 的字符
1.2.7 回顧打印
前面介紹瞭如何使用p標記和替換命令顯示sed編輯器修改過的行。另外有3個命令也能用 來打印數據流中的信息:
p命令用來打印文本行;
等號(=)命令用來打印行號;
l(小寫的L)命令用來列出行。
① 打印行
跟替換命令中的p標記類似,p命令可以打印sed編輯器輸出中的一行。如果只用這個命令, 也沒什麼特別的。
它所做的就是打印已有的數據文本。打印命令常見的用法是打印包含匹配文本模式的行
在命令行上用-n選項,你可以禁止輸出其他行,只打印包含匹配文本模式的行。 也可以用它來快速打印數據流中的某些行
如果需要在修改之前查看行,也可以使用打印命令,比如與替換或修改命令一起使用。可以創建一個腳本在修改行之前顯示該行
( p標記與p命令作用不一樣,p標記顯示符合規則的結果,p命令打印 )
sed編輯器命令會查找包含數字3的行,然後執行兩條命令。首先,腳本用p命令來打印出原始行;然後它用s命令替換文本,並用p標記打印出替換結果。輸出同時顯示了原來的行文本和新的行文本
② 打印行號
等號命令會打印行在數據流中的當前行號。行號由數據流中的換行符決定。每次數據流中出 現一個換行符,sed編輯器會認爲一行文本結束了
sed編輯器在實際的文本行出現前打印了行號。如果你要在數據流中查找特定文本模式的話, 等號命令用起來非常方便
利用-n選項,你就能讓sed編輯器只顯示包含匹配文本模式的行的行號和文本
③ 列出行
列出(list)命令(l)可以打印數據流中的文本和不可打印的ASCII字符。任何不可打印字符要麼在其八進制值前加一個反斜線,要麼使用標準C風格的命名法(用於常見的不可打印字符),比如\t,來代表製表符
製表符的位置使用\t來顯示。行尾的美元符表示換行符。如果數據流包含了轉義字符,列出 命令會在必要時候用八進制碼來顯示
1.2.8 使用sed處理文件
替換命令包含一些可以用於文件的標記。還有一些sed編輯器命令也可以實現同樣的目標, 不需要非得替換文本。 ① 寫入文件
w命令用來向文件寫入行。該命令的格式如下:
[address]w filename
filename可以使用相對路徑或絕對路徑,但不管是哪種,運行sed編輯器的用戶都必須有文件的寫權限。地址可以是sed中支持的任意類型的尋址方式,例如單個行號、文本模式、行區間或文本模式。 下面的例子是將數據流中的前兩行打印到一個文本文件中。
下面的例子是將數據流中的前兩行打印到一個文本文件中
當然,如果你不想讓行顯示到STDOUT上,你可以用sed命令的-n選項。 如果要根據一些公用的文本值從主文件中創建一份數據文件,比如下面的郵件列表中的,那麼w命令會非常好用
sed編輯器會只將包含文本模式的數據行寫入目標文件
② 從文件讀取數據
讀取(read)命令(r)允許你將一個獨立文件中的數據插入到數據流中。
讀取命令的格式如下:
[address]r filename
filename參數指定了數據文件的絕對路徑或相對路徑。你在讀取命令中使用地址區間,只 能指定單獨一個行號或文本模式地址。sed編輯器會將文件中的文本插入到指定地址後
sed編輯器會將數據文件中的所有文本行都插入到數據流中。同樣的方法在使用文本模式地址時也適用
讀取命令的另一個很酷的用法是和刪除命令配合使用:利用另一個文件中的數據來替換文件中的佔位文本。舉例來說,假定你有一份套用信件保存在文本文件中: