sed學習筆記

海鳥發表於2014-11-14

sed介紹

sed是個流編輯器, 你可以想象成有一個管道, 檔案從一端流進, 經過裡面一系列加工後, 從另一端流出. 恩, 就這樣理解算了. 這是一個很牛逼的工具, 作為Linux管理員, 最好熟悉下這個(還有awk, grep之類). 從一個簡單例子開始:

echo 'hello, tony' | sed 's/tony/jack/'

輸出: hello, tony. s替換的命令, /是分隔符. 就是把每中的tony換成jack. 當然, 你不一定要用/作為分隔符, 只要你願意, 用什麼都可以, 比如:

echo 'hello, tony' | sed 's:tony:jack:'

這裡就用的:作的分隔符. 有一個要注意的是, sed預設替換行為是隻影響到每一行中的第一個匹配項, 如果要影響所有的, 則要在後面加上g:

echo 'hello, tony! my name is tony too~' | sed 's/tony/jack/g'

輸出: hello, jack! my name is jack too~

&可以引用被匹配的內容

有時候我們替換的內容是基於被匹配的內容上加工後形成的, 所以要替換的文字是不確定的. &可以引用前面匹配的內容:

echo 'hello, tony! my name is tony too~' | sed 's/tony/&2/g'

輸出: hello, tony2! my name is tony2 too~. 注意上面把前面匹配的tony替換成了tony2.

sed和擴充套件正規表示式

我們知道在Linux系統中正規表示式有基本的和擴充套件的, 所謂擴充套件, 就是功能更豐富一些. 如下:

echo '123 abc' | sed -r 's/[1-9]+/& &/'

輸出: 123 123 abc. 這裡, 我們把一個數字組成的詞複製一份. 在sed中, -r選項就是開啟擴充套件正則功能, 因為+屬於擴充套件正則的功能, 如果不開啟的話, 會被解析成普通的字元.

利用分組(\1, \2, ... , \9)

我們知道在正規表示式裡, 可以用()進行分組, 這在sed裡同樣可以利用. 例子:

 echo 'hello, tony jack' | sed -r 's/([a-z]+) ([a-z]+)/\2 \1/'

輸出: hello, jack tony. 把tonyjack兩個單詞對換了. \1, \2, ... , \9 分別對應前面正則裡的分組1, 分組2, ... , 分組9, 最多9個. 注意一點, 分組引用不只是只能出現在分隔符右邊, 也可以出現在左邊, 如:

echo 'hello, tony tony yes ok ok' | sed -r 's/([a-z]+) \1/\1/g'

輸出: hello, tony yes ok. 這裡的功能是去重.
在這前提到s命令後面加g, 會切換到全匹配模式, 即會掃描整行全部查詢(預設只匹配第一個). 有時候我們的需求是確定匹配替換指定的第幾個而不是全部, 可以在s的命令後面加上數字來指示. 例子:

echo 'hello world haha!' | sed 's/[a-z][a-z]*/T/2'

輸出: hello T haha! 可以看到現在匹配替換的是第2個. 我們也可以結合g數字一起放到命令的後面組合使用, 表示從能匹配的第n個開始的後續所有匹配項. 舉例:

echo 'hello world haha!' | sed 's/[a-z][a-z]*/T/2g'

輸出: hello T T!. 可以看到, 現在是替換了從第2個開始的所有單詞(由小寫的a~z組成的單詞).

/p 列印匹配行

sed預設行為是列印所有的行, 如果是s命令, 就用處理過的行替換處理前的行. 用g標識可以改變這一行為, 它會單獨列印所匹配或者處理過的行. 例子:

printf 'my name is\nhaha, jack\n' | sed 's/my/your/p'
# 輸出
# your name is
# your name is
# haha, jack

your name is這行輸出了兩次, 一次是sed命令自動輸出, 另一次是標識p影響的作用. 如果我們現在只想列印匹配的行, 不要自動輸出所有的行, 可以用-n選項, 例子:

printf 'my name is\nhaha, jack\n' | sed -n 's/my/your/p'
# 輸出:
# your name is

可以看到, 現在只有能匹配的行才能輸出了.

I標識

可以通過I標識來開啟不區分大小寫匹配. 例子:

echo 'hello, tony' | sed 's/Tony/jack/I'
# 輸出:
# hello, jack

如果後面不加I標識, 則不會匹配成功.

一行執行多個命令

前面例子中每次只能執行一次s命令, 如果要處理多個命令怎麼做呢, 當然我們可用管道把命令串起來, 就像:

echo 'hello, tony and jack' | sed 's/tony/Tony/' | sed 's/jack/Jack/'
# 輸出:
# hello, Tony and Jack

但是在Linux中能一句話說清楚的就不繞個圏. 可以用-e選項來執行多個命令, 這樣在sed處理每行時, 會依次執行. 例子:

echo 'hello, tony and jack' | sed -e 's/tony/Tony/' -e 's/jack/Jack/' 
# 輸出:
# hello, Tony and Jack

選擇要處理的行(addresses)

有時候我們我們只希望處理一些特定的行而並不是檔案的所有行, sed當然支援這樣的功能, 它通restriction command這樣的語法實現. 其中restriction就是限制要處理的行. 比如下面一些場景:

  • 指定要處理的行數, 比如第8行
  • 指定某個範圍內的所有行, 比如第1行到第8行
  • 包括某個模式匹配的所有行
  • 從檔案的第一行到指定匹配某個模式的行
  • 從指定匹配的行到檔案的最後一行
  • 從一個模式匹配到別一個模式匹配行當中的所有行

指定行數

這是最簡單的指定限制, 通過指定行數來處理, 例子:

sed '1 s/hello/byby/' test.txt

s替換命令只會在第一行生效

正則模式匹配

只有那些符合指定正則模式匹配的行才會處理, 例子:

sed '/^[0-9][0-9]*/ s/hello/byby/' test.txt 

只處理那些開頭是數字的行.

行數範圍

我們可以處理指定從第幾行到第幾行之間的所有行處理, 例子:

# 處理第10行到第20行之間的資料
sed '10,20 s/hello/byby/' test.txt
# 處理第10到最後一行的所有資料
sed '10,$ s/hello/byby/' test.txt 

模式範圍匹配

從匹配的第一個模式開始, 一直到第二個模式匹配結束.

sed '/start/,/end/ s/hello/byby/' test.txt 

start開始處理, 到end結束.

刪除命令d

前面講的s是替換命令, d是刪除命令. 例子:

# 刪除第2行到第三行之間的所有行
sed '2,10 d' test.txt

我們經常遇到一個場景是刪除一個配置檔案(properties)的所有註釋, 可以這樣寫:

sed -e 's/#.*//' -e 's/[ ^I]//' -e '/^$/ d'  test.txt

上面這句由三個子命令組成, 它們的執行順序也很重要. 分別來看一下. s/#.*//是把所有由#開頭的內容去掉, 但是如果#前面有空格(space or tab), 就要做第二個命令處理: s/[ ^I]//, 就是把所有的空格去掉, 注意的是^I就是表示Tab. 最後一個命令就是所有空行(空行有兩個來源, 一是檔案本身的空行, 二是通過前面兩條命令產生的空行)都通過命令d刪除.

反轉命令: !

!可以置反命令操作, 比如!d就是不刪除的意思. 如下幾個命令返回的結果是一樣的, 都是隻只輸出第1到10行:

sed -n '1,10 p'
sed -n '11,$ !p'
sed '1,10 !d'
sed '11,$ d'

退出命令

遇到滿足條件時退出流編輯操作, 如下:

sed '11 q' test.txt

只列印到第10行. 到第11行時就會退出. 因這退出操作的特殊性, 這個命令不支援範圍匹配操作, 比如sed '2,3 q'就是錯誤的, 這個很好理解.