Linux基礎命令介紹十:文本流編輯 sed
與vim不同,sed是一種非交互式的文本編輯器,同時它又是面向字符流的,每行數據經過sed處理后輸出。
- sed [OPTION]... [script] [file]...
sed的工作過程是這樣的:首先,初始化兩個數據緩沖區模式空間和保持空間;sed讀取一行輸入(來自標準輸入或文件),去掉結尾的換行符(\n)后置于模式空間中,然后針對模式空間中的字符串開始執行‘sed命令’,每個命令都可以有地址與之相關聯,地址可以看成是條件,只有在條件成立時,相關的命令才被執行;所有可執行命令都處理完畢后,仍處于模式空間中的字符串會被追加一個換行符后打印輸出;之后讀取下一行輸入做同樣的處理,直到主動退出(q)或輸入結束。
地址
地址可以是如下的形式
1、number 表示行號
2、first~step 表示從first(數字)行開始,每隔step(數字)行
3、$ 表示***一行(注意當出現在正則表達式中時表示行尾)
4、/regexp/ 表示匹配正則表達式regexp(關于正則表達式,請參見這一篇)
5、\%regexp% 表示匹配正則表達式regexp,%可以換成任意其他單個字符。(用于regexp包含斜線/的情況)
6、/regexp/I 匹配正則表達式regexp時不區分大小寫
7、/regexp/M 啟用正則多行模式,使$不止匹配行尾,還匹配n或r之前的位置;使^不止匹配行首,還匹配n或r之后的位置。此時可以用(\`)匹配模式空間的開頭位置,用(\')匹配模式空間的結束位置。
還可以用逗號,分隔兩個地址來表示一個范圍
表示從匹配***個地址開始,直到匹配第二個地址或文件結尾為止。如果第二個地址是個正則表達式,則不會對***個地址匹配行進行第二個地址的匹配;如果第二個地址是行號,但小于或等于***個地址匹配行行號,則只會匹配一行(***個地址匹配行)。
8、0,/regexp/ 這種情況下,正則表達式regexp會在***行就開始進行匹配。只有第二個地址是正則表達式時,***個地址才能用0。
9、addr1,+n表示匹配地址addr1和其后的n行。
10、addr1,~n表示從匹配地址addr1開始,直到n的倍數行為止。
如果沒有給出地址,所有的行都會匹配;在地址或地址范圍后追加字符!表示對地址取反,所有不匹配的行才會被處理。
選項
-n 默認時每一行處理過的字符串都會被打印輸出,此選項表示關閉此默認行為。只有被命令p作用的字符串才會被輸出。
-f file表示從file中讀取sed命令
-i 表示原地修改。應用此選項時,sed會創建一個臨時文件,并將處理結果輸出到此文件,處理完畢后,會將此臨時文件覆蓋至原文件。
-r 表示使用擴展的正則表達式
命令
p表示打印模式空間內容,通常配合選項-n一起使用
- [root@centos7 ~]# seq 5
- 1
- 2
- 3
- 4
- 5
- [root@centos7 ~]# 只輸出第二行到第四行
- [root@centos7 ~]# seq 5|sed -n '2,4p'
- 2
- 3
- 4
- [root@centos7 ~]#
d 刪除模式空間內容,立即處理下一行輸入。
- #刪除***一行
- [root@centos7 ~]# seq 5|sed '$d'
- 1
- 2
- 3
- 4
- [root@centos7 ~]#
q 立即退出,不再處理任何命令和輸入(只接受單個地址)
- [root@centos7 ~]# seq 5|sed '/3/q'
- 1
- 2
- 3
- [root@centos7 ~]#
n 如果沒有使用選項-n,輸出模式空間中內容后,讀取下一行輸入并覆蓋當前模式空間內容。如果沒有更多的輸入行,sed會退出執行。
- [root@centos7 ~]# seq 9|sed -n 'n;p'
- 2
- 4
- 6
- 8
- [root@centos7 ~]# 注意多個命令用分號分隔
s/regexp/replacement/flag 表示用replacement替換模式空間中匹配正則表達式regexp的部分。在這里符號/可以換成任意單個字符。
- [root@centos7 ~]# echo "hello123world"|sed 's/[0-9]\+/,/'
- hello,world
- #注意這里+需要轉義,如果使用選項-r則無需轉義
在replacement中
1、\n (n為1-9中的一個數字)表示對正則表達式中分組(...)的引用;
- [root@centos7 ~]# echo "hello123world"|sed -r 's/[a-z]+([0-9]+)[a-z]+/\1/'
- 123
- [root@centos7 ~]# echo "hello123world"|sed -r 's/([a-z]+)[0-9]+([a-z]+)/\1,\2/'
- hello,world
2、&表示模式空間中所有匹配regexp的部分;
- [root@centos7 ~]# echo "hello123world"|sed -r 's/[0-9]+/:&:/'
- hello:123:world
3、\L 將后面的字符轉化成小寫直到 \U 或 \E 出現;
4、\l 將下一個字符轉化為小寫;
5、\U 將后面的字符轉化成大寫直到 \L 或 \E 出現;
6、\u 將下一個字符轉化為大寫;
7、\E 停止由 \L 或 \U 起始的大小寫轉化;
- [root@centos7 ~]# echo "hello123world"|sed -r 's/^([a-z]+)[0-9]+([a-z]+)$/\U\1\E,\u\2/'
- HELLO,World
- [root@centos7 ~]#
flag
1、n數字n表示替換第n個匹配項
- [root@centos7 ~]# head -1 /etc/passwd
- root:x:0:0:root:/root:/bin/bash
- #替換冒號分隔的第五部分為空
- [root@centos7 ~]# head -1 /etc/passwd|sed 's/[^:]\+://5'
- root:x:0:0:/root:/bin/bash
2、g表示全局替換
- [root@centos7 ~]# echo "hello123world"|sed 's/./\U&\E/'
- Hello123world
- [root@centos7 ~]#
- [root@centos7 ~]# echo "hello123world"|sed 's/./\U&\E/g'
- HELLO123WORLD
- [root@centos7 ~]#
- #當數字n和g同時使用時,表示從第n個匹配項開始替換一直到***匹配項
- [root@centos7 ~]# head -1 /etc/passwd|sed 's/[^:]\+://4g'
- root:x:0:/bin/bash/
3、p表示如果替換成功,則打印模式空間內容。
4、w file表示如果替換成功,則輸出模式空間內容至文件file中。
5、I和i表示匹配regexp時不區分大小寫。
- [root@centos7 ~]# echo 'HELLO123world'|sed -r 's/[a-z]+//Ig'
- 123
- [root@centos7 ~]#
6、M和m表示啟用正則多行模式(如前所述)。(講命令N時再舉例)
- [root@centos7 ~]# echo hello|sed 'y/el/LE/'
- hLEEo
- [root@centos7 ~]#
a text表示輸出模式空間內容后追加輸出text內容
- [root@centos7 ~]# seq 3|sed '1,2a hello'
- 1
- hello
- 2
- hello
- 3
- [root@centos7 ~]#
i text表示輸出模式空間內容之前,先輸出text內容
- [root@centos7 ~]# seq 3|sed '$ihello'
- 1
- 2
- hello
- 3
- [root@centos7 ~]#
c text表示刪除匹配地址或地址范圍的模式空間內容,輸出text內容。如果是單地址,則每個匹配行都輸出,如果是地址范圍,則只輸出一次。
- [root@centos7 ~]# seq 5|sed '1,3chello'
- hello
- 4
- 5
- [root@centos7 ~]# seq 5|sed '/^[^3-4]/c hello'
- hello
- hello
- 3
- 4
- hello
=表示打印當前輸入行行號
- [root@centos7 ~]# seq 100|sed -n '$='
- 100
- [root@centos7 ~]# seq 100|sed -n '/^10\|^20/='
- 10
- 20
- 100
- [root@centos7 ~]# 轉義的|表示邏輯或
r file表示讀取file的內容,并在當前模式空間內容輸出之后輸出
- [root@centos7 ~]# cat file
- hello world
- [root@centos7 ~]# seq 3|sed '1,2r file'
- 1
- hello world
- 2
- hello world
- 3
- [root@centos7 ~]#
w file表示輸出模式空間內容至file中
N讀入一行內容至模式空間后,再追加下一行內容至模式空間(此時模式空間中內容形如 line1\nline2 ),如果不存在下一行,sed會退出。
- [root@centos7 ~]# seq 10|sed -n 'N;s/\n/ /p'
- 1 2
- 3 4
- 5 6
- 7 8
- 9 10
- [root@centos7 ~]#
- #s命令的m flag舉例
- [root@centos7 ~]# seq 3|sed 'N;s/^2/xxx/'
- 1
- 2
- 3
- [root@centos7 ~]# seq 3|sed 'N;s/^2/xxx/m'
- 1
- xxx
- 3
- [root@centos7 ~]# seq 3|sed 'N;s/1$/xxx/'
- 1
- 2
- 3
- [root@centos7 ~]# seq 3|sed 'N;s/1$/xxx/M'
- xxx
- 2
- 3
D如果模式空間中沒有新行(如命令N產生的新行),則和命令d起同樣作用;如果包含新行,則會刪除***行內容,然后對模式空間中剩余內容重新開始一輪處理。(注意:D后面的命令將會被忽略)
- [root@centos7 ~]# seq 5|sed 'N;D'
- 5
- [root@centos7 ~]# seq 5|sed 'N;N;D'
- 3
- 4
- 5
P打印模式空間中***行內容
- [root@centos7 ~]# seq 10|sed -n 'N;P'
- 1
- 3
- 5
- 7
- 9
- [root@centos7 ~]# seq 10|sed -n 'N;N;P'
- 1
- 4
- 7
- #注意另一種寫法輸出中的不同
- [root@centos7 ~]# seq 10|sed -n '1~3P'
- 1
- 4
- 7
- 10
g用保持空間中的內容替換模式空間中的內容
- [root@centos7 ~]# seq 5|sed -n 'g;N;s/\n/xx/p'
- xx2
- xx4
- [root@centos7 ~]#
G追加一個換行符到模式空間,然后再將保持空間中的內容追加至換行符之后。(此時模式空間中內容形如 PATTERN\nHOLD )
- [root@centos7 ~]# seq 5|sed 'G;s/\n/xx/'
- 1xx
- 2xx
- 3xx
- 4xx
- 5xx
h用模式空間中的內容替換保持空間中的內容(注意此時模式空間中的內容并沒有被清除)
- [root@centos7 ~]# seq 5|sed -n 'h;G;s/\n/xx/p'
- 1xx1
- 2xx2
- 3xx3
- 4xx4
- 5xx5
- [root@centos7 ~]# seq 5|sed -n 'h;G;G;s/\n/xx/gp'
- 1xx1xx1
- 2xx2xx2
- 3xx3xx3
- 4xx4xx4
- 5xx5xx5
H追加一個換行符到保持空間,然后再將模式空間中的內容追加至換行符之后。(此時保持空間中內容形如 HOLD\nPATTERN )
- [root@centos7 ~]# seq 3|sed -n 'H;G;s/\n/xx/gp'
- 1xxxx1
- 2xxxx1xx2
- 3xxxx1xx2xx3
- [root@centos7 ~]#
x交換模式空間和保持空間的內容
- [root@centos7 ~]# seq 9|sed -n '1!{x;N};s/\n//p'
- 3
- 25
- 47
- 69
- #處于{...}之中的是命令組
: label為分支命令指定標簽位置(不允許地址匹配)
b label無條件跳轉到label分支,如果省略了label,則跳轉到整條命令結尾(即開始下一次讀入)
- #如刪除xml文件中注釋部分(<!--...-->之間的部分是注釋,可以多行)
- sed '/<!--/{:a;/-->/!{N;ba};d}' server.xml
- #表示匹配<!--開始,在匹配到-->之前一直執行N,匹配到-->之后刪除模式空間中內容
- #如在nagios的配置文件中,有許多define host{...}的字段,如下所示:
- define host{
- use windows-server
- host_name serverA
- hostgroups 060202
- alias 060202
- contact_groups yu
- address 192.168.1.1
- }
- #現在需要刪除ip地址是192.168.1.1的段,可以這樣:
- sed -i '/define host/{:a;N;/}/!ba;/192\.168\.1\.1/d}' file
- #注意和前一個例子中的區別
t label在一次輸入后有成功執行的s替換命令才跳轉到label,如果省略了label,則跳轉到整條命令結尾(即開始下一次讀入)
- #如行列轉換
- [root@centos7 ~]# seq 10|sed ':a;$!N;s/\n/,/;ta'
- 1,2,3,4,5,6,7,8,9,10
- [root@centos7 ~]#
- #如將MAC地址78A35114F798改成帶冒號的格式78:A3:51:14:F7:98
- [root@centos7 temp]# echo '78A35114F798'|sed -r ':a;s/\B\w{2}\b/:&/;ta'
- 78:A3:51:14:F7:98
- [root@centos7 temp]#
- #這里\b表示匹配單詞邊界,\B表示匹配非單詞邊界的其他任意字符
- #當然也可以采用其他的方式實現:
- [root@centos7 temp]# echo '78A35114F798'|sed -r 's/..\B/&:/g'
- 78:A3:51:14:F7:98
- [root@centos7 temp]#
T label在一次輸入后只要沒有替換命令被成功執行就跳轉到label,如果省略了label,則跳轉到整條命令結尾(即開始下一次讀入)
z表示清除模式空間中內容,和s/.*//起相同的作用,但更有效。
更多例子
1、刪除匹配行的上一行和下一行
- #例如輸入數據為命令seq 10的輸出(當然也可以是任意其他文件內容)
- #要求刪除匹配5那一行的前一行和后一行
- [root@centos7 temp]# seq 10|sed -n '$!N;/\n5/{s/.*\n//p;N;d};P;D'
- 1
- 2
- 3
- 5
- 7
- 8
- 9
- 10
2、合并奇偶數行
- #輸入數據為命令seq 11的輸出,要求分別將奇數和偶數分別放在同一行
- #輸出***行`1 3 5 7 9 11`,第二行`2 4 6 8 10`
- [root@centos7 ~]# seq 11|sed -nr '$!N;2!G;s/([^\n]+)\n((.+)\n)?(.+)\n(.+)/\4 \1\n\5 \3/;h;$p'
- 1 3 5 7 9 11
- 2 4 6 8 10
- [root@centos7 ~]#
3、合并多文件
- #文本a.txt的內容:
- 01 12510101 4001
- 02 12310001 4002
- 03 12550101 4003
- 04 12610001 4004
- 05 12810001 4005
- 06 12310001 4006
- 07 12710001 4007
- 08 12310001 4008
- 09 12810101 4009
- 10 12510101 4010
- 11 12310001 4011
- 12 12610001 4012
- 13 12310001 4013
- #文本b.txt的內容:
- A 12410101 2006/02/15 2009/01/31 4002
- B 12310001 2006/08/31 2008/08/29 4001
- C 12610001 2008/05/23 2008/05/22 4002
- D 12810001 1992/12/10 1993/06/30 4001
- E 12660001 1992/05/11 1993/06/01 4005
- #要求輸出a.txt內容中第二列和b.txt中第二列相同的行,并追加b.txt中對應的兩個日期列。
- #形如:02 12310001 4002 2006/08/31 2008/08/29
- sed -rn '/^[01]/ba;H;:a;G;s/^((..)( .*)( [^\n]+)).*\3(( [^ ]*){2}).*/\1\5/p' b.txt a.txt
- #當然如果使用awk來處理的話,解決思路更容易理解一些:
- awk 'NR==FNR{a[$2]=$3FS$4;next}{if($2 in a)print $0,a[$2]}' b.txt a.txt
為加深對sed各種命令特性的理解,請自行分析這三個例子。
各種命令的組合使用,再加上正則表達式的強大能力,使得sed可以處理所有能夠計算的問題。但由于代碼可讀性不強,理解起來比較困難,通常使用sed作為一個文本編輯器,對文本做非交互的流式處理。理解上述各個命令的含義,熟練使用它們,就會發現sed的強大之處。