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. 把tony和jack兩個單詞對換了. \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'
就是錯誤的, 這個很好理解.