sed命令_Linux sed命令:替換、刪除、更新檔案中的內容

2020-07-16 10:04:31
sed 是 stream editor 的縮寫,中文稱之為“流編輯器”。

sed 命令是一個面向行處理的工具,它以“行”為處理單位,針對每一行進行處理,處理後的結果會輸出到標準輸出(STDOUT)。你會發現 sed 命令是很懂禮貌的一個命令,它不會對讀取的檔案做任何貿然的修改,而是將內容都輸出到標準輸出中。

我們來看看 sed 的命令格式:
sed command file
  • command 部分:針對每行的內容所要進行的處理(這部分很重要很重要)。
  • file 部分:要處理的檔案,如果忽略 file 引數,則 sed 會把標準輸入作為處理物件。

sed 的工作原理是什麼

剛才我們說了,sed 命令是面向“行”進行處理的,每一次處理一行內容。處理時,sed 會把要處理的行儲存在緩衝區中,接著用 sed 命令處理緩衝區中的內容,處理完成後,把緩衝區的內容送往螢幕。接著處理下一行,這樣不斷重複,直到檔案末尾。這個緩衝區被稱為“模式空間”(pattern space)。

如前面所說,在這個處理過程中,sed 命令並不會對檔案本身進行任何更改。

我們來一起看一個最最簡單的 sed 命令的例子,讓大家對它有個感性的認識。
#我們先來看看原檔案的內容
[[email protected] ~]$ cat roc.txt
test 1
test2
testtest
XtestX
BBtest
 
#我們想用sed命令來刪除檔案中帶字元“2”的行
[[email protected] ~]$ sed '/2/d' roc.txt
test 1
testtest
XtestX
BBtest

此例子是利用 sed 來刪除 roc.txt 檔案中含有字元“2”的行。看到了吧,例子很簡單,這個命令的 command 部分是/2/d,而且它是用單引號括起來的。你也一定要學著這樣做,用到 sed,別忘了用單引號將 command 部分括起來。

/2/d中的 d 表示刪除,意思是說,只要某行內容中含有字元 2,就刪掉這一行。(sed 所謂的刪除都是在模式空間中執行的,不會真正改動 roc.txt 原檔案。)

想用 sed 命令實現 cut 命令的效果

假如我們想實現類似於 cut-d:-f 1/etc/passwd 的效果,也就是以冒號為間隔符提取第 1 個域,用 sed 命令應該怎麼操作呢?
#先來一起看看/etc/passwd檔案的內容
[[email protected] ~]$ head -n 5 /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
 
#我們這回用sed命令來提取檔案中每行的第一個域, 間隔符是冒號
[[email protected] ~]$ head -n 5 /etc/passwd|sed 's/:.*$//'
root
bin
daemon
adm
lp

看到了吧,我們將 command 部分指定成了's/:.*$//',表示我們要把每一行的第一個冒號到結尾的部分都清空,這樣留下的便是第一個冒號前的內容啦。

sed 都有哪些好用的選項

說到 sed 命令的選項,就不得不提-n選項,想把這個選項介紹清楚,還是要費一些腦子和筆墨的。

前面提到,sed 會將模式空間裡的行經過處理後輸出到標準輸出,這是預設的處理方式。也就是說,除非你使用“d”來刪除此行,否則經過“模式空間”處理的行都是會被輸出到標準輸出(螢幕)上的。我們一起來看下面的例子:
#還是先來看看原檔案的內容
[[email protected] ~]$ cat roc.txt
1
2
3
4
5
 
#仔細看, 輸出中出現了兩個“4”
[[email protected] ~]$ sed ‘/4/p’ roc.txt
1
2
3
4
4
5
看,所有的原始檔案內容都被輸出來了,而且含有字元4的行被輸出了兩遍。

但這就是 sed 命令的工作原理,它會不問青紅皂白地把經過處理的行先輸出出來,然後再執行後面的動作。(在這裡我們設定了 p,表示列印此行。)這明顯不符合我們的初衷,我們只是想讓 sed 命令找到含有 4 的行再輸出。

這時候,不妨加上-n選項試一試,你會發現,結果變得如你所願了。
[[email protected] ~]$ sed -n '/4/p' roc.txt
4

-n選項會很嚴肅地警告 sed 命令:除非是明確表明要輸出的行,否則不要給我胡亂輸出。-n選項經常和 p 配合使用,其含義就是,輸出那些匹配的行。

command 部分花樣很多

還記得我們在前面介紹過的,sed 命令的命令格式是這樣的:
$ sed command file
其中,command 部分是 sed 命令的精髓,對 command 部分的掌握程度決定了你是不是 sed 高手。

command 部分可以分為兩塊知識:一塊是範圍設定,一塊是動作處理。

範圍設定,可以採用兩種不同的方式來表達:
  1. 指定行數:比如‘3,5’表示第 3、第 4 和第 5行;而‘5,$’表示第 5 行至檔案最後一行。
  2. 模式匹配:比如/^[^dD]/表示匹配行首不是以 d 或 D 開頭的行。

而動作處理部分,會提供很豐富的動作供你選擇,下面就來介紹幾個最常用的動作吧:
  • d:表示刪除行。
  • p:列印該行。
  • r:讀取指定檔案的內容。
  • w:寫入指定檔案。
  • a:在下面插入新行新內容。

展示 sed 實力的時候到了

事實勝於雄辯,我們打算通過四個例子,讓大家感受到 sed 命令真的是一位“實力派”選手。

第一個例子,我們來顯示 test 檔案的第 10 行到第 20 行的內容:
#我們採用了剛才提到的指定行區間的方法
[[email protected] ~]$ sed -n '10,20p' test

第二個例子,我們嘗試將所有以 d 或 D 開頭的行裡的所有小寫 x 字元變為大寫 X 字元:
[[email protected] ~]$ sed '/^[dD]/s/x/X/g' test

這個用法值得一講,我們在 command 部分採用了 /AA/s/BB/CC/g 的語法格式,這表示我們要匹配到檔案中帶有 AA 的行,並且將這些行中所有的 BB 替換成 CC。

第三個例子,我們要刪除每行最後的兩個字元:
#點號表示一個單個字元, 兩個點號就表示兩個單個字元
[[email protected] ~]$ sed 's/..$//' test

有人可能會問,用 sed‘/..$/d’test 為什麼不行呢,d 不是表示刪除麼?用 d 是不行的,這是因為 d 表示刪除整行內容,而非字元。'/..$/d'表示的是匹配所有末尾含有兩個字元的行,然後刪除這一整行內容,顯然這和我們的初衷是相悖的。

第四個例子,我們希望刪除每一行的前兩個字元:
[[email protected] ~]$ sed 's/..//' test

通過這四個例子,相信大家對 sed 命令的最常見用法已經有了很直觀的認識了,不妨在自己的實際工作中把這些知識用起來吧。

& 符號的妙用

我們仍然通過一個場景來講解這個知識點。
#按照慣例, 先展示檔案的內容
[[email protected] ~]$ cat mysed.txt
Beijing
London
 
#我們使用到了&符號, 大家試著猜一猜它的作用
[[email protected] ~]$ sed 's/B.*/&2008/' mysed.txt
Beijing2008
London

不賣關子,答案揭曉啦,這個命令的作用是將包含‘B.*’的字串後面加上 2008 四個字元。這個命令中我們用到了 & 字元,在 sed 命令中,它表示的是“之前被匹配的部分”,在我們的例子中當然就是 Beijing 啦!

我們再通過一個例子強化一下大家對&符號的理解:
#這個例子或許更易理解
[[email protected] 20160229]$ sed 's/Bei/&2008/' mysed.txt
Bei2008jing
London

sed 中的括號有深意

在 sed 命令中,其實小括號‘()’也是有深意的。我們開門見山,通過一個例子,讓大家見識一下小括號的威力:
[[email protected] ~]$ echo "hello world" | sed 's/(hello).*/world 1/'
world hello
我們看到,原本是“hello world”,經過 sed 的處理,輸出變成了“world hello”。

這個例子中就用到了小括號的知識,我們稱之為“sed 的預儲存技術”,也就是命令中被“(”和“)”括起來的內容會被依次暫存起來,儲存到 1、2…裡面。這樣你就可以使用‘N’形式來呼叫這些預儲存的內容了。

來繼續看一個例子,我們希望只在每行的第一個和最後一個 Beijing 後面加上 2008 字串,言下之意就是,除了每行的第一個和最後一個 2008 之外,這一行中間出現的 Beijing 後面就不要加 2008 啦。這個需求,真的是很複雜很個性化,但 sed 命令仍然可以很好地滿足:
#先看下檔案內容, 第一行中出現了4個Beijing
[[email protected] ~]$ cat mysed.txt
Beijing Beijing Beijing Beijing
London London London London
 
#效果實現啦, 可是, 命令真的好複雜
[[email protected] ~]$ sed 's/(Beijing)(.*)(Beijing)/12008232008/' mysed.txt
Beijing2008 Beijing Beijing Beijing2008
London London London London

這個命令確實足夠複雜,用流行的語言說就是“足夠虐心”。這個例子中我們再次使用了預儲存技術,儲存了三項內容,分別代表第一個 Beijing、中間的內容、最後的 Beijing。而針對13,我們在其後面追加了 2008 這個字串。

更聰明的定位行範圍

實踐是學習知識最好的方法,相信大家看了這個例子後,就明白如何更好地定位行範圍了:
#檔案內容展示一下
[[email protected] ~]$ cat mysed.txt
Beijing 2003
Beijing 2004
Beijing 2005
Beijing 2006
Beijing 2007
Beijing 2008
Beijing 2007
 
#我們想展示匹配了2005的行和2007的行之間的內容
[[email protected] ~]$ sed -n ‘/2005/,/2007/p’ mysed.txt
Beijing 2005
Beijing 2006
Beijing 2007

我們使用 /2005/ 來匹配行範圍的首行,用 /2008/ 來匹配行範圍的尾行。可以看到,在匹配尾行時,只要遇到第一個符合要求的行,就會停止,而不會再繼續向后匹配了。所以,sed 命令只是匹配到了第一個 2007,並沒有匹配到第二個 2007。

用 -e 選項來設定多個 command

還記得 command 部分吧,現在有一個好訊息要告訴你,那就是 sed 命令可以包含不只一個 command。如果要包含多個 command,只需在每個 command 前面分別加上一個-e選項即可。
#我們通過2個-e選項設定了兩個command
[[email protected] ~]$ sed -n -e ‘1,2p’ -e ‘4p’ mysed.txt
Beijing 2003
Beijing 2004
Beijing 2006

有一個地方值得大家注意,那就是-e選項的後面要立即接 command 內容,不允許再夾雜其他選項。

-e選項支援設定多個 command,這原本是一件好事情,讓我們可以更方便地實現一些替換效果。但是,這也給我們帶來了幸福的煩惱,假如我們設定了很多個 command,那它們的執行順序是怎樣的呢?

如果這一點不搞明白,-e選項帶來的或許只有混亂而非便捷。我們來一起看看下面的例子:
#先看看檔案的內容
[[email protected] ~]$ cat mysed.txt
Beijing 2003
Beijing 2004
Beijing 2005
Beijing 2006
Beijing 2007
Beijing 2008
 
#我們設定了兩個command
[[email protected] ~]$ sed -e ‘s/Beijing/London/g’ -e ‘/Beijing/d’ mysed.txt
London 2003
London 2004
London 2005
London 2006
London 2007
London 2008

前一個 command 表示將 Beijing 替換為 London,而後一個 command 表示要刪除包含了 Beijing 字串的行,但是最後的結果卻是輸出了所有行,並沒有發現被刪除的行。這是因為第一個 command 已經將 Beijing 都替換成了 London,所以怪第二個 command 找不到 Beijing 了。

我們再來把上面例子中的 command 顛倒一下位置,看看效果如何:
#我們先指定刪除動作, 再指定替換動作
[[email protected] 20160229]$ sed -e '/Beijing/d' -e 's/Beijing/London/g' mysed.txt
[[email protected] 20160229]$

通過這兩個小例子,我們可以很清晰地看到,多個 command 之間,是按照在命令中的先後順序來執行的。

用 -f 選項設定 command 檔案

如果你的 sed 命令的 command 部分很長,那麼可以將內容寫到一個單獨的檔案中,然後使用-f選項來指定這個檔案作為我們 sed 命令的 command 部分:
#這是我們事先寫好的檔案
[[email protected] ~]$ cat callsed
/2004/,/2006/p
 
#我們用-f選項來指定command檔案
[[email protected] ~]$ sed -n -f callsed mysed.txt
Beijing 2004
Beijing 2005
Beijing 2006

很好理解吧,-f選項並不難,而且我會經常使用,因為一些比較常用的匹配規則,我都會存到單獨的檔案中,不用再費腦子記憶啦。

內容插入

sed 命令遠比你想象的要強大,它不僅可以處理本行內容,還可以在這一行的後面插入內容:
#我們將要插入的內容儲存到一個單獨的檔案中
[[email protected] ~]$ cat ins.txt
====China====
 
#展示一下我們要處理的檔案
[[email protected] ~]$ cat mysed.txt
Beijing 2003
Beijing 2004
Beijing 2005
Beijing 2006
Beijing 2007
Beijing 2008
 
#看, 我們使用r來實現插入
[[email protected] ~]$ sed ‘/2005/r ins.txt’ mysed.txt
Beijing 2003
Beijing 2004
Beijing 2005
====China====
Beijing 2006
Beijing 2007
Beijing 2008
通過效果可以看出來,我們在檔案中的含有 2005 字串的行的下面一行插入了 ins.txt 檔案的內容。

除了可以通過指定檔案來插入外,其實還可以使用 a 在特定行的“下面”插入特定內容:
#檔案內容
[[email protected] ~]$ cat new.txt
Beijing 2004
Beijing 2005
Beijing 2006
 
#我們希望在2004的下一行插入China
[[email protected] ~]$ sed ‘/2004/aChina’ mysed.txt
Beijing 2003
Beijing 2004
China
Beijing 2005
Beijing 2006
Beijing 2007
Beijing 2008
可以看到,我們只要使用a然後加上要插入的內容就可以輕鬆實現啦。

有些同學會問,既然可以在一行的下面插入內容,那是否可以在一行的上面插入內容呢?答案是當然可以,那就是使用i動作:
[[email protected] ~]$ sed ‘/2004/iChina’ mysed.txt
Beijing 2003
China
Beijing 2004
Beijing 2005
Beijing 2006
Beijing 2007
Beijing 2008 

說一說 y 動作

在介紹 y 動作之前,我們先來看看它可以實現什麼效果:
#原檔案內容
[[email protected] ~]$ cat mysed.txt
Beijing 2003
Beijing 2004
Beijing 2005
Beijing 2006
Beijing 2007
Beijing 2008
 
#y就是按照字元順序, 實現前後的替換
[[email protected] 20160229]$ sed 'y/ei/ie/' mysed.txt
Biejeng 2003
Biejeng 2004
Biejeng 2005
Biejeng 2006
Biejeng 2007
Biejeng 2008
這個例子其實已經很清楚了,我們希望將所有的 e 和 i 互換。

有些同學會問,y///s///有什麼區別呢?主要有以下兩點:
  • y 的語法格式是 y/source/dest/,表示將 source 中的字元對位替換為 dest 中的字元。而 s 的語法格式是 s/regexp/replacement/,表示通過正則匹配到的內容替換為 replacement 部分。
  • y 只是簡單的逐字替換,沒有很多花樣。s 支援 & 符號和預儲存等特性,可以實現更多靈活的替換效果。

這時,一些 GEEK 或許會想到一種情況,那就是 y/ee/ei/ 會產生什麼效果呢?因為這裡面出現了兩個同樣的字元,我們還是通過例子來看一下:
[[email protected] 20160229]$ sed 'y/ee/ie/' mysed.txt
Biijing 2003
Biijing 2004
Biijing 2005
Biijing 2006
Biijing 2007
Biijing 2008

看到了吧,如果 source 部分出現了重複的字元,則只有第一次出現的對位替換會產生效果,後面的並不會起作用。或許下面這個例子更加清晰些:
#原檔案內容
[[email protected] ~]$ cat mysed.txt
Beijing 2003
Beijing 2004
Beijing 2005
Beijing 2006
Beijing 2007
Beijing 2008
 
#iji到iba的替換中, 只有j到b起到了效果
[[email protected] 20160229]$ sed 'y/iji/iba/' mysed.txt
Beibing 2003
Beibing 2004
Beibing 2005
Beibing 2006
Beibing 2007
Beibing 2008

通過 n 動作來控制行的下移

有時我們希望實現隔行處理的效果,比如只需對偶數行做某個替換,這時候,我們就需要 n 動作的幫忙啦:
#原檔案內容
[[email protected] ~]$ cat mysed.txt
Beijing 2003
Beijing 2004
Beijing 2005
Beijing 2006
Beijing 2007
Beijing 2008
 
#我們同時使用了n動作和y動作
[[email protected] ~]$ sed ‘/200/{n;y/eijng/EIJNG/;}’ mysed.txt
Beijing 2003
BEIJING 2004
Beijing 2005
BEIJING 2006
Beijing 2007
BEIJING 2008

你會發現,大寫的 BEIJING 是隔行出現的。這就是n選項在起作用,它的真實作用是將下一行內容放到處理快取中,這樣,就讓當前這一行躲避開了替換動作,是不是有點像小時候玩遊戲時通過左右鍵躲避開 BOSS 的大招,哈哈。

將指定行寫入到特定檔案中

文章要進入尾聲了,我們最後再教大家一個非常實用的動作,那就是 w 動作,它可以將匹配到的內容寫入到另一個檔案中,即用來實現內容的篩選與儲存:
#將包含2004、2005、2006的行儲存到new.txt檔案中
[[email protected] ~]$ sed ‘/200[4-6]/w new.txt’ mysed.txt
Beijing 2003
Beijing 2004
Beijing 2005
Beijing 2006
Beijing 2007
Beijing 2008
 
#我們要的內容已經乖乖到碗裡來了
[[email protected] ~]$ cat new.txt
Beijing 2004
Beijing 2005
Beijing 2006

好了,sed 的流藝術系列到這裡就全部結束啦,相信你對 sed 已經有了初步的認識,可以在實戰中露兩手了。