前言:環境:centos6.5sed版本:GNU sed version 4.2.1本文的代碼都是在這個環境下驗證的。一、簡介sed(Stream Editor)意為流編輯器,是Unix常見的命令行程式。是Bell實驗室的 Lee E.McMahon 在1973年到1974年之間開發完成,目前可以在... ...
前言:
環境:centos6.5
sed版本:GNU sed version 4.2.1
本文的代碼都是在這個環境下驗證的。
一、簡介
sed(Stream Editor)意為流編輯器,是Unix常見的命令行程式。是Bell實驗室的 Lee E.McMahon 在1973年到1974年之間開發完成,目前可以在大多數操作系統中使用。
sed的出現是作為grep的一個繼任者,因為grep只能簡單的進行查找和替換,但是考慮還可能會有刪除等各種需求,McMahon 開發了一個更具通用性的工具。sed著名的語法規則包括使用 / 進行模式匹配,以及 s/// 來進行替代。與同期存在的工具ed一起,sed的語法影響了後來發展的 ECMAScript 和 Perl。GNU sed 添加了很多特性,包括著名的 in-place editing。
二、處理流程
要說明sed的處理流程,需要先提它的命令語法:
sed [options] script filename
一般來說,sed是從stdin讀取輸入,並且將輸出寫出到stdout,但是filename被指定時,會從指定的文件中獲取輸入,輸出可以重定向到另外的文件中。
options指的是sed的命令行參數,比較有限,這個後面會說明。
script是指需要對輸入執行的一個或者多個操作指令,一般需要用單引號括起來,這樣可以避免shell對特殊字元的處理。sed會依次讀取輸入文件的每一行到緩存中並應用script中指定的操作指令,因此而帶來的變化並不會影響最初的文件(除非option加了-i參數)。
如果操作指令很多,為了不影響可讀性,可以將其寫入文件,並用-f參數指定該文件:
sed -f scriptfile filename
無論是將操作指令通過命令行指定,還是寫入到文件中作為一個sed腳本,必須包含至少一個指令,否則用sed就沒有意義了。
每條操作指令由pattern和procedure兩部分組成,顧名思義,pattern是匹配的規則,一般為用'/'分隔的正則表達式(也有可能是行號,具體參見Sed命令地址匹配問題總結),而procedure則是一連串編輯命令(action)。 sed的處理流程,簡化後是這樣的:
- 讀入新的一行內容到緩存空間;
- 從指定的操作指令中取出第一條指令,判斷是否匹配pattern;
- 如果不匹配,則忽略後續的編輯命令,回到第2步繼續取出下一條指令;
- 如果匹配,則針對緩存的行執行後續的編輯命令;完成後,回到第2步繼續取出下一條指令;
- 當所有指令都應用之後,輸出緩存行的內容;回到第1步繼續讀入下一行內容;
- 當所有行都處理完之後,結束;
具體流程如圖所示:
三、命令選項options
根據上面提到的,我們知道sed一般有兩種語法形式:
sed [options] script filename sed -f scriptfile filename
options常用的只有幾個:
-n :使用安靜(silent)模式。在一般 sed 的用法中,所有來自 STDIN 的數據一般都會被列出到終端上。但如果加上 -n 參數後,則只有經過sed 特殊處理的那一行(或者動作)才會被列出來。
-e :直接在命令列模式上進行 sed 的動作編輯;
-f :直接將 sed 的動作寫在一個文件內, -f filename 則可以運行 filename 內的 sed 動作;
-r :sed 的動作支持的是延伸型正規表示法的語法。(預設是基礎正規表示法語法)
-i :直接修改讀取的文件內容,而不是輸出到終端。
在這裡,-e參數實際上很多時候會讓人很迷惑。因為我們平時使用sed的時候一般都不會加-e參數,譬如:
seq 6 | sed -n '1,2p'
它和下麵的寫法是等價的:
seq 6 | sed -ne '1,2p'
所以很多文章在說到-e這個參數的時候說可以省略,實際上,這種說法是不嚴謹的。sed發展到現在,已經有各種各樣的版本,在有些版本中,-e參數是不可以省略的,但是我們一般使用的都是GNU sed(gsed),所以在這種情況可以省略。-e參數最常用的場景是對同一文件作多次修改,如:
seq 6 | sed -e 's/1/10/' -e 's/2/20/'
但是實際上多重編輯還有更簡潔的寫法,就是在同一個script中,用分號分割兩句指令:
seq 6 | sed -e 's/1/10/;s/2/20/'
在處理流程部分,我們提到,每條操作指令script一般由pattern和procedure兩部分組成,接下來說說pattern和procedure。
四、pattern
1. 模式空間
回顧一下上面講到的處理流程,我們可以知道,sed是以行的方式來處理數據的,每一行被讀入到一塊緩存空間,該空間名為模式空間(pattern space)。因此sed操作的都是最初行的拷貝,同時後續的編輯命令都是應用到前面的命令編輯後輸出的結果,所以編輯命令之間的順序就顯得格外重要。
讓我們來看一個非常簡單的例子,將一段文本中的pig替換成cow,並且將cow替換成horse:
echo "pig and cow and horse" | sed 's/pig/cow/;s/cow/hores/'
初看起來好像沒有問題,但是實際上是錯誤的,最後輸出實際上是:
hores and cow and horse
原因是第一個替換命令將所有的pig都替換成cow,緊接著的替換命令是基於前一個結果處理的,將所有的cow都替換成horse,造成的結果是全部的pig/cow都被替換成了horse,這樣違背了我們的初衷。在這種情況下,只需要調換下兩個編輯命令的順序:
echo "pig and cow and horse" | sed 's/cow/hores/;s/pig/cow/'
2. 模式空間的轉換
sed只會緩存一行的內容在模式空間,這樣的好處是sed可以處理大文件而不會有任何問題,不像一些編輯器因為要一次性載入文件的一大塊內容到緩存中而導致記憶體不足。
例如,現在要把一段文本中的Unix System與UNIX System都要統一替換成The UNIX Operating System,因此我們用兩句替換命令來完成這個目的:
echo "the Unix System" | sed 's/Unix/UNIX/;s/UNIX System/UNIX Operating System/' ==> the UNIX Operating System
過程簡單來說如下:
1)讀入一行內容到模式空間
2)應用第一條替換命令將Unix換成UNIX
3)模式空間的內容變為”the UNIX System”
4)應用第二條替換命令將UNIX System替換成UNIX Operating System
5)模式空間的內容變為”the UNIX Operating System”
6)所有編輯命令執行完畢,輸出模式空間中的行
3. 地址匹配
地址匹配限制sed的編輯命令到底應用在哪些行上。預設情況下,sed是全局匹配的,即對所有輸入行都應用指定的編輯命令,這是因為sed依次讀入每一行,每一行都會成為當前行並被處理,所以s/apple/Pear/g會將所有輸入行的apple替換成pear。
但如果通過指定地址範圍,則只會替換範圍內的行,譬如:
echo "Harry loves apple" | sed '/Mary/s/apple/pear/' ==> Harry loves apple
/Mary/是一個正則表達式,表示匹配包含Mary的行,因此”Harry loves apple”不會被替換.
sed命令中可以包含0個、1個或者2個地址(地址對)。地址可以為正則表達式(如/Mary/),行號或者特殊的行符號(如$表示最後一行):
- 如果沒有指定地址,預設將編輯命令應用到所有行;
- 如果指定一個地址,只將編輯命令應用到匹配該地址的行;
- 如果指定一個地址對(addr1,addr2),則將編輯命令應用到地址對中的所有行(包括起始和結束);
- 如果地址後面有一個感嘆號(!),則將編輯命令應用到不匹配該地址的所有行;
為了方便理解上述內容,我們以刪除命令(d)為例來說明。
1)以行號指定地址
預設不指定地址將會刪除所有行:
sed 'd' file
指定地址則刪除匹配的行,如刪除第一行:
sed '1d' file
或者刪除最後一行,$符號在這裡表示最後一行,這點要下正則表達式中的含義區別開來:
sed '$d' file
通過指定地址對可以刪除該範圍內的所有行,例如刪除第3行到最後一行:
sed '2,$d' file
這裡通過指定行號刪除,行號是sed命令內部維護的一個計數變數,該變數只有一個,並且在多個文件輸入的情況下也不會被重置。
2)通過正則表達式來指定地址
刪除包含MA的行:
sed '/MA/d' file
刪除從包含Alice的行開始到包含Hubert的行結束的所有行:
sed '/Alice/,/Hubert/d' file
當然,行號和地址對是可以混用的,刪除第二行到包含Hubert的行結束的所有行:
sed '2,/Hubert/d' file
如果在地址後面指定感嘆號(!),則會將命令應用到不匹配該地址的行:
sed '2,/Hubert/!d' file
以上介紹的都是最基本的地址匹配形式,GNU Sed基於此添加了幾個擴展的形式,具體可以看man手冊,或者可以看Sed 命令地址匹配問題總結。
上面說的內容都是對匹配的地址執行單個命令,如果要執行多個編輯命令要怎麼辦?sed中可以用{}來組合命令,就好比編程語言中的語句塊,例如:
sed -n '1,4{s/ MA/, Massachusetts/;s/ PA/, Pennsylvania/;p}' file
五、procedure
對於sed編輯命令的語法有兩種約定,分別是
[address]procedure # 第一種
[line-address]procedure # 第二種
第一種[address]是指可以為任意地址包括地址對,第二種[line-address]是指只能為單個地址。
以下是要介紹的全部基礎命令:
名稱 |
命令 |
語法 |
說明 |
替換 |
s |
[address]s/pattern/replacement/flags |
替換匹配的內容 |
刪除 |
d |
[address]d |
刪除匹配的行 |
插入 |
i |
[line-address]i\text |
在匹配行的前方插入文本 |
追加 |
a |
[line-address]a\text |
在匹配行的後方插入文本 |
行替換 |
c |
[address]c\text |
將匹配的行替換成文本text |
列印行 |
p |
[address]p |
列印在模式空間中的行 |
列印行號 |
= |
[address]= |
列印當前行行號 |
列印行 |
l |
[address]l |
列印在模式空間中的行,同時顯示控制字元 |
轉換字元 |
y |
[address]y/SET1/SET2/ |
將SET1中出現的字元替換成SET2中對應位置的字元 |
讀取下一行 |
n |
[address]n |
將下一行的內容讀取到模式空間 |
讀文件 |
r |
[line-address]r file |
將指定的文件讀取到匹配行之後 |
寫文件 |
w |
[address]w file |
將匹配地址的所有行輸出到指定的文件中 |
退出 |
q |
[line-address]q |
讀取到匹配的行之後即退出 |
1. 替換命令: s
語法:
[address]s/pattern/replacement/flags
其中[address]是指地址,pattern是替換命令的匹配表達式,replacement則是對應的替換內容,flags是指替換的標誌位,它可以包含以下一個或者多個值:
- n: 一個數字(取值範圍1-512),表明僅替換前n個被pattern匹配的內容;
- g: 表示全局替換,替換所有被pattern匹配的內容;
- p: 僅當行被pattern匹配時,列印模式空間的內容;
- w file:僅當行被pattern匹配時,將模式空間的內容輸出到文件file中;
如果flags為空,則預設替換第一次匹配,如:
echo "column1 column2 column3 column4" | sed 's/ /;/' ==> column1;column2 column3 column4
如果flags中包含g,則表示全局匹配:
echo "column1 column2 column3 column4" | sed 's/ /;/g' ==> column1;column2;column3;column4
如果flags中明確指定替換第n次的匹配,例如n=2:
echo "column1 column2 column3 column4" | sed 's/ /;/2' ==> column1 column2;column3 column4
當替換命令的pattern與地址部分是一樣的時候,比如/regexp/s/regexp/replacement/
可以省略替換命令中的pattern部分,這在單個編輯命令的情況下沒多大用處,但是在組合命令的場景下還是能省不少功夫的。
譬如我們有一個文件prince,裡面的內容是:
If someone loves a flower, of which just one single blossom grows in all the
millions and millions of stars, it is enough to make him happy just to look at
the stars. He can say to himself, "Somewhere, my flower is there…" But if the
sheep eats the flower, in one moment all his stars will be darkened… And you think that is not important!
現在要在flower後面增加(“s”),同時在被修改的行前面增加+號,以下是使用的sed命令:
sed '/flower/{s//&("s")/;s/^/+ /}' prince
這裡我們用到了組合命令,並且地址匹配的部分和第一個替換命令的匹配部分是一樣的,所以後者我們省略了,在replacement部分用到了&這個元字元,它代表之前匹配的內容,這點我們在後面介紹。執行後的結果為:
+ If someone loves a flower("s"), of which just one single blossom grows in all the
millions and millions of stars, it is enough to make him happy just to look at
+ the stars. He can say to himself, "Somewhere, my flower("s") is there…" But if the
+ sheep eats the flower("s"), in one moment all his stars will be darkened… And you think that is not important!
替換命令的一個技巧是中間的分隔符是可以更改的,這個技巧在有些地方非常有用,比如路徑替換,下麵是採用預設的分隔符和使用感嘆號作為分隔符的對比:
find /usr/local -maxdepth 2 -type d | sed 's//usr/local/man//usr/share/man/' find /usr/local -maxdepth 2 -type d | sed 's!/usr/local/man!/usr/share/man!'
替換命令中還有一個很重要的部分——replacement(替換內容),即將匹配的部分替換成replacement。在replacemnt部分中也有幾個特殊的元字元,它們分別是:
- &: 被pattern匹配的內容;
- \num: 被pattern匹配的第num個分組(正則表達式中的概念,\(..\)括起來的部分稱為分組;
- \: 轉義符號,用來轉義&,\, 回車等符號
2. 刪除命令: d
語法:
[address]d
刪除命令可以用於刪除多行內容,例如1,3d
會刪除1到3行。刪除命令會將模式空間中的內容全部刪除,並且導致後續命令不會執行並且讀入新行,因為當前模式空間的內容已經為空。我們可以試試:
sed '2,${d;=}' prince ==> If someone loves a flower, of which just one single blossom grows in all the
以上命令嘗試在刪除第2行到最後一行之後,列印當前行號(=),但是事實上並沒有執行該命令。
3. 插入行/追加行/替換行命令: i/a/c
語法:
# Append 追加 [line-address]a\text # Insert 插入 [line-address]i\text # Change 行替換 [address]c\text
以上三個命令,行替換命令(c)允許地址為多個地址,其餘兩個都只允許單個地址(註:在ArchLinux上測試表明,追加和插入命令都允許多個地址,sed版本為GNU sed version 4.2.1)
追加命令是指在匹配的行後面插入文本text;相反地,插入命令是指匹配的行前面插入文本text;最後,行替換命令會將匹配的行替換成文本text。文本text並沒有被添加到模式空間,而是直接輸出到屏幕,因此後續的命令也不會應用到添加的文本上。註意,即使使用-n參數也無法抑制添加的文本的輸出。
我們用實際的例子來簡單介紹下這三個命令的用法,用上面的文本prince:
現在,我們要在第2行後面添加'------':
sed '2a\ -------------------------\ ' prince
輸出:
If someone loves a flower, of which just one single blossom grows in all the
millions and millions of stars, it is enough to make him happy just to look at
-------------------------
the stars. He can say to himself, "Somewhere, my flower is there…" But if the
sheep eats the flower, in one moment all his stars will be darkened… And you think that is not important!
或者可以在第3行之前插入:
sed '3i\ --------------------------\ ' prince
輸出:
If someone loves a flower, of which just one single blossom grows in all the
millions and millions of stars, it is enough to make him happy just to look at
--------------------------
the stars. He can say to himself, "Somewhere, my flower is there…" But if the
sheep eats the flower, in one moment all his stars will be darkened… And you think that is not important!
我們來測試下文本是否確實沒有添加到模式空間,因為模式空間中的內容預設是會列印到屏幕的:
sed -n '2a\ ---------------------------\ ' prince
輸出:
---------------------------
通過-n參數來抑制輸出後發現插入的內容依然被輸出,所以可以判定插入的內容沒有被添加到模式空間。
使用行替換命令將第2行到最後一行的內容全部替換成'----':
sed '2,$c\ ---------------------------\ ' prince
輸出:
If someone loves a flower, of which just one single blossom grows in all the
---------------------------
4. 列印命令: p/l/=
這裡純粹的列印命令應該是指p,但是因為後兩者(l和=)和p差不多,並且相對都比較簡單,所以這裡放到一起介紹。
[address]p [address]= [address]l
p命令用於列印模式空間的內容,例如列印文件的第一行:
sed -n '1p' prince ==> If someone loves a flower, of which just one single blossom grows in all the
l命令類似p命令,不過會顯示控制字元,這個命令和vim的list命令相似,例如:
echo "column1 column2 column3^M" | sed -n 'l' ==> column1tcolumn2tcolumn3r$
=命令顯示當前行行號,例如:
sed '=' prince ==> 1 If someone loves a flower, of which just one single blossom grows in all the 2 millions and millions of stars, it is enough to make him happy just to look at 3 the stars. He can say to himself, "Somewhere, my flower is there…" But if the 4 sheep eats the flower, in one moment all his stars will be darkened… And you think that is not important! 5
5. 轉換命令: y
轉換命令的語法是:
[address]y/SET1/SET2/
它的作用是在匹配的行上,將SET1中出現的字元替換成SET2中對應位置的字元,例如1,3y/abc/xyz/
會將1到3行中出現的a替換成x,b替換成y,c替換成z。是不是覺得這個功能很熟悉,其實這一點和tr命令是一樣的。可以通過y命令將小寫字元替換成大寫字元,不過命令比較長:
echo "hello, world" | sed 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/' ==> HELLO, WORLD
6. 取下一行命令: n
語法:
[address]n
n命令為將下一行的內容提前讀入,並且將之前讀入的行(在模式空間中的行)輸出到屏幕,然後後續的命令會應用到新讀入的行上。因此n命令也會同d命令一樣改變sed的控制流程。
cat text
==>
.H1
"On Egypt"
Napoleon, pointing to the Pyramids, said to his troops:
"Soldiers, forty centuries have their eyes upon you."
現在要將.H1後面的空行刪除:
sed '/.H1/{n;/^$/d}' text ==> .H1 "On Egypt" Napoleon, pointing to the Pyramids, said to his troops: "Soldiers, forty centuries have their eyes upon you."
7. 讀寫文件命令: r/w
語法是:
[line-address]r file [address]w file
讀命令將指定的文件讀取到匹配行之後,並且輸出到屏幕,這點類似追加命令(a)。我們以書中的例子來講解讀文件命令。假設有一個文件text:
cat text ==> For service, contact any of the following companies: [Company-list] Thank you.
同時我們有一個包含公司名稱列表的文件company.list:
cat company.list ==> Allied Mayflower United
現在我們要將company.list的內容讀取到[Company-list]之後:
sed '/^[Company-list]/r company.list' text => For service, contact any of the following companies: [Company-list] Allied Mayflower United Thank you.
寫命令將匹配地址的所有行輸出到指定的文件中。假設有一個文件內容如下,前半部分是人名,後半部分是區功能變數名稱稱:
cat text ==> Adams, Henrietta Northeast Banks, Freda South Dennis, Jim Midwest Garvey, Bill Northeast Jeffries, Jane West Madison, Sylvia Midwest Sommes, Tom South
現在我們要將不同區域的人名字寫到不同的文件中:
sed '/Northeast$/w region.northeast /South$/w region.south /Midwest$/w region.midwest /West$/w region.west' text ==> Adams, Henrietta Northeast Banks, Freda South Dennis, Jim Midwest Garvey, Bill Northeast Jeffries, Jane West Madison, Sylvia Midwest Sommes, Tom South
8. 退出命令: q
語法:
[line-address]q
當sed讀取到匹配的行之後即退出,不會再讀入新的行,並且將當前模式空間的內容輸出到屏幕。例如列印前3行內容:
sed '3q' prince ==> If someone loves a flower, of which just one single blossom grows in