sed 命令進階
sed 是面向行處理的,但是有時可能希望針對多行作為一個單位進行處理,在 sed 中這也是可行的,本文將介紹如何使用 sed 來同時處理多行文字
理論基礎
模式空間(Pattern Space):是一塊活躍的緩衝區,sed 命令處理行時都
會將文字行讀入到該空間執行相關的指令碼,預設情況下每次只會從輸入中讀取一行輸入文字到該空間中進行處理。
保持空間(Hold Space):該空間的作用是臨時儲存 “模式空間” 中處理的文字行
由於 sed 一般是直接在 “模式空間” 中對文字資料進行處理的,因此如果能夠改變 “模式空間” 讀取文字資料的行為,即可實現將“多行文字資料作為一個處理單位進行處理” 的功能。而要改變 “模式空間” 的預設行為,可以通過 D
G
、H
、N
、P
等命令進行處理
多行文字處理
讀取行
如果只是希望讀取下一行,那麼普通的 n
命令就可以做到,例如,如果希望將所有出現 “the” 的的行的下一行進行刪除,可以執行如下的命令:
# n 命令讀取下一行,d 命令進行刪除
sed '/the/{n ; d}' data.txt
這裡的 n
命令只會讀取單行資料文字,然後進行對應的指令碼進行處理,本質上在 “模式空間” 中還是隻針對單行。如果要合併出現 ‘,’ 的兩行,可以使用 N
命令來將讀取到的文字行加入到 “模式空間” 中,如下所示:
# 注意將 \n 替換成空格,否則可能無法看到合併的結果 sed '/,/{N; s/\n/ /}' data.txt
注意: 由於 N
命令是獲取下一行,因此如果處理的是最後一行的話,由於最後一行沒有資料了,因此在讀取到最後一行時,會停止 sed,使得最後一行的替換操作無法進行。
刪除行
d
命令會刪除匹配的行,當和 N
命令一起使用時可能不會達到你想要的結果,因此應該避免在使用 N
命令的同時使用 d
命令,為此可以考慮採用 D
命令來代替 d
命令進行刪除
D
命令和 d
命令最大的不同在於 D
命令只會刪除 “模式空間” 中的第一行,而不是將整個 “模式空間” 的所有文字行作為一個單元進行處理
D
命令的獨特之處在於強制 sed 回到指令碼的執行處,對同一 “模式空間” 中的內容重新執行這些指令碼內容,
多行列印
當多行匹配時,通過 p
命令可以打印出當前匹配模式中存在的所有文字行,有時可能並不希望發生這樣的行為,為此,可以通過 P
命令,該列印命令只會列印 “模式空間” 中的第一行文字內容
取反命令
可以通過在命令選項前加上 !
來使得原有的命令無法生效,例如,前文提到,N
命令在處理最後一行時會關閉 sed,使得最後一行的內容無法被正確處理,為此,可以通過加入 !
命令來禁用最後一行的行為,如下所示:
sed '$!N; s/the/a/' data.txt
現在,sed 就能夠按照預期地將所有的文字行進行相關的處理
保持空間的相關操作
”保持空間“ 有以下五種選項進行對應的操作,如下表所示:
命令 | 描述 |
---|---|
h | 將模式空間中的內容複製到保持空間 |
H | 將模式空間中的內容附加到保持空間 |
g | 將保持空間中的內容複製到模式空間 |
G | 將保持空間中的內容附加到模式空間 |
x | 交換模式空間和保持空間的內容 |
例如,如果想要反向輸出輸入流中的文字內容,結合上文的取反命令,可以執行如下的指令碼:
sed -n '1!G; h; $p' data.txt
解釋:G
命令本身會將保持空間的內容附加到模式空間中,1G
的命令就是將保持空間的內容附加到模式空間的第一行,然而,加上 !
排除命令之後,使得 1G
的行為變成反向附加到模式空間中;通過 h
命令將模式空間中的內容複製到保持空間;$p
表示每次當到達資料流的末尾時,列印模式空間中的內容
改變流
sed 通過順序處理輸入流來對每一行的資料執行對應的指令碼,直到資料流結束(D
命令除外),sed 編輯器提供了一個方法來改變指令碼的執行流程。
分支
分支的主要目的是排除某些文字行,使得它們不會執行對應的指令碼,具體的使用格式如系所示:
[address]b [label]
例如,如果不希望 data.txt
中的 \([2,3]\) 的文字行不會執行替換指令碼,可以執行類似如下的命令:
sed '2,3b; s/the/a/' data.txt
[label] 是類似 goto
語句的結構,如果希望當匹配到 first
文字時,跳轉到執行不同的指令碼,可以執行類似下面的命令:
sed '{/first/b jump1; s/This/No Jump/
:jump1
s/This/Jump Here/
}' data.txt
當匹配到 “first” 時,則會將執行將 ”This“ 替換為 ”Jump Here“ 的指令碼,而不是替換成為 ”No Jump“
注意: 標籤名的最大長度為 \(7\) 個字元
測試
和分支命令類似,測試命令 t
(test)也可以用來改變 sed 編輯器指令碼的執行流程,該命令和 if
語句類似。
使用格式如下:
[address]t [label]
例如,如下的指令碼:
sed '{
s/first/matched/
t
s/This/No Matched/
}' data.txt
當處理的文字包含 ”first“ 時,會將 ”first“ 替換為 ”matched“,否則將該文字中的 ”This“ 替換為 ”No Matched“
測試命令 t
同樣可以使用和分支類似的標籤語義
模式替代
替換命令 s
已經十分了解,試想一下這樣一種情況:希望為某個單詞加上引號,似乎一般的 s
命令不能完美解決這個問題(無法知道匹配到的實際單詞),為了解決這個問題,在傳統的 Unix 中提供了 ‘&’ 符號用於表示匹配到的字串,因此,如果希望給 ”the“ 加上引號,可以執行如下的指令碼:
sed 's/th./"&"/' data.txt
有時可能希望替換掉一個字串中的單詞,那麼可以考慮使用括號來進行單獨的替換,括號表示一個子模式,如下所示:
# \1 表示匹配到的子模式
echo "This is System Administrator manual" | sed 's/\(System\).Administrator/\1 User/'
注意:子模式的括號需要使用 \
進行轉義,否則會被視為一般的字元
”\1“ 表示第一個括號的子模式,”\2“ 表示第二個括號的子模式…………
結合正則表示式可能使用得更加得心應手
參考:
[1] 《Linux 命令列與 Shell 指令碼程式設計大全》