《Linux命令行與shell腳本編程大全》sed進階
多行命令
sed編輯器包含了3個可用來處理多行文本的命令
1.N:將數據流中的下一行加進來創建一個多行組來處理
2.D:刪除多行組中的一行
3.P:打印多行組中的一行
next命令
單行的next命令
n命令會告訴sed編輯器移動到數據流中的下一文本行,而不用重新回到命令的最開始再執行一遍
通常sed編輯器會在移動到數據流中的下一文本行前在這行上執行所有定義好的命令。單行的next命令改變了這一流程
[plain]
$ cat test.txt
line1
line2
line3
line4
如果我們想刪除line1下面的空行,我們可以先找到line1,然後刪除下面的那一行
[plain]
$ sed -i '/line/{n;d}' test.txt
$ cat test.txt
line1
line2
line3
line4
發現,不止是line1下面的那行被刪除了,line2、line3下面的空白行也被刪除了。sed執行完n之後的命令 ,還是要繼續逐行掃描的,然後對符合要求的行進行操作。
所以我們得匹配的精確點
[plain]
$ sed -i '/line1/{n;d}' test.txt
suzhaoqiang@suzhaoqiang-OptiPlex-380:~/android/source/linux_learned$ cat test.txt
line1
line2
line3
line4
合並文本行
單行next命令會將數據流中的下一文本行移動到sed編輯器的工作空間(稱為模式空間,pattern space)
多行的next命令 會將下一文本行加到已經在模式空間中的文本上,文本行之間仍用換行分隔
[plain]
$ cat test.txt
line1
line2
line3
line4
line5
下面將前兩行合並
[plain]
$ sed -i '/line1/{N;s/\n/ /}' test.txt
$ cat test.txt
line1 line2
line3
line4
line5
如果想兩行變成一行,那麼:
[plain]
$ sed -i '{N;s/\n/ /}' test.txt
多行刪除命令
可以N與d配合使用,注意下面的結果:
[plain]
$ cat test.txt
line1
line2
line3
line4
line5
$ sed 'N;/line3/d' test.txt
line1
line2
line5
發現把模式空間中的line3和line4都刪除了
sed還提供D,可以只刪除模式空間中的第一行
d清空模式空間
[plain]
$ sed 'N;/line3/D' test.txt
line1
line2
line4
line5
多行打印命令
p打印模式空間
P打印模式空間的第一行
[plain]
$ cat test.txt
line1
line2
line3
line4
line5
$ sed -n 'N;/line/p' test.txt
line1
line2
line3
line4
$ sed -n 'N;/line/P' test.txt
line1
line3
P只打印了模式空間中的第一行,p打印出模式空間中所有內容
保持空間
模式空間是sed的一塊活動緩沖區,但並不是sed編輯器保存文本唯一空間
sed有一個叫做保持空間(hold space)的另一塊緩沖區。可以在處理模式空間中其他行時用保持空間來臨時保存一些行。
sed的保持空間命令
命令 描述
h 將模式空間復制到保持空間
H 將模式空間附加到保持空間
g 將保持空間復制到模式空間
G 將保持空間附加到模式空間
x 交換保持空間和模式空間的內容
一個練習,提取文本前兩行,組合成line1,line2,line1形式
[plain]
$ cat test.txt
line1
line2
line3
line4
line5
line6
$ sed -n '/line1/{h;p;n;p;g;p}' test.txt
line1
line2
line1
先找到第一行,把第一行復制到保持空間,然後打印第一行。接著讀取第二行,打印第二行,然後把保持空間復制到模式空間,再次打印模式空間。
我們可以簡化一下過程
[plain]
$ sed -n '1{h;N;G;p}' test.txt
line1
line2
line1
尋址找到第一行,把第一行復制到保持空間,然後讀取第二行追加到模式空間上,再把保持空間追加到模式空間上,最後一起打印。
排除命令
歎號命令用來排除命令,讓原本會起作用的命令不起作用(我覺得書上的這句話只會誤導人……)
man 寫道
After the address (or address-range), and before the command, a ! may be inserted, which specifies that the command shall only be executed if the address (or
address-range) does not match.
在地址(或者一個范圍)之後,命令之前,可以插入歎號(!),它指定了這個命令只能運行在地址(范圍)之外的地方。
還是man講的清楚一些。
不打印還有數字1,2,3的行
[plain]
$ cat test.txt
line1
line2
line3
line4
line5
line6
$ sed -n '/[1-3]/!p' test.txt
line4
line5
line6
下面看一個復雜點的例子,反轉文本
1)首先sed讀取第一行到模式空間,然後將其復制到保持空間中
2)然後sed讀取第二行到模式空間,將保持空間追加到模式空間(模式空間現在為第二行、第一行),再將模式空間復制到保持空間中
然後不斷重復步驟2),最後進行打印
[plain]
$ sed -n '1!G;h;$p' test.txt
line6
line5
line4
line3
line2
line1
$ tac test.txt
line6
line5
line4
line3
line2
line1
這裡為了練習,所以使用sed,否則我們可以使用tac
[plain]
$ tac test.txt
line6
line5
line4
line3
line2
line1
畢竟sed需要講所有行都讀取出來,然後才能打印,所以有這種工作的時候,不應該使用sed
改變流
通常,sed編輯器會從腳本的頂部開始執行命令並一直處理到腳本的結尾(D例外,它會強制sed編輯器返回到腳本頂部,而不是讀取新行)
跳轉(branch)
格式如下:
[address]b [label]
與歎號類似,b可以作用在一個范圍上
label定義了要跳轉到的位置,如果沒有label,b將跳轉到腳本的結尾。
[plain]
$ sed '2,3b;s/line/Line/;s/Line/lines/' test.txt
lines1
line2
line3
lines4
lines5
lines6
上面的腳本沒有為b定義label,所以兩個替換命令都執行了。
下面重寫一下反轉文本的例子
[plain]
$ sed -n '1b test;G;:test h;$p' test.txt
line6
line5
line4
line3
line2
line1
上面為b定義了一個label叫做test
如果是第一行,那麼就執行h;$p,否則執行G;h;$p
我們也可以用b命令實現簡單的循環
下面是一個去掉逗號的例子:
[plain]
$ echo "This, is, a, test, to, remove, commas." | sed -n '{:start;s/,//p;b start}'
This is, a, test, to, remove, commas.
This is a, test, to, remove, commas.
This is a test, to, remove, commas.
This is a test to, remove, commas.
This is a test to remove, commas.
This is a test to remove commas.
label可以定義在b的前面
現在這個例子就很容易理解了,找到逗號,然後刪除,打印,然後跳轉到腳本開頭重復執行。
當所有逗號都刪除了之後,循環依然沒有停止,所以需要想辦法讓它在執行完任務就停止查找。
在重復之前,我們查找逗號,如果有,那麼就重復執行。
[plain]
$ echo "This, is, a, test, to, remove, commas." | sed -n '{:start;s/,//p;/,/b start}'
This is, a, test, to, remove, commas.
This is a, test, to, remove, commas.
This is a test, to, remove, commas.
This is a test to, remove, commas.
This is a test to remove, commas.
This is a test to remove commas.
現在就不會進入死循環了。
但是現在還有一個問題,就是我們只想要最終的結果怎麼辦?
也就是說,我們不能每次循環的時候都打印,只有在執行完任務的時候才需要打印。
[plain]
$ echo "This, is, a, test, to, remove, commas." | sed -n '{:start;s/,//;/,/b start;p}'
This is a test to remove commas.
測試
類似跳轉命令,測試命令(t)會基於替換命令的輸出跳轉到一個標簽,而不是基於地址跳轉到一個標簽。
在沒有指定標簽的情況下,如果測試成功,sed會跳轉到腳本的結尾。
[plain]
$ cat test.txt
line1
line2
line3
line4
line5
line6
$ sed 's/line[45]/Line/;t;s/line[0-9]/line/' test.txt
line
line
line
Line
Line
line
如果第一個替換命令成功替換,那麼t後面的命令就不再執行,否則執行。
下面改寫上面刪除逗號的例子:
[plain]
$ echo "This, is, a, test, to, remove, commas." | sed -n '{:start;s/,//;t start;p}'
This is a test to remove commas.
模式替代
and符號
and符號(&)和正則裡面的反向引用很相似。&用來代表替換命令中的匹配模式中匹配到的文本。
[plain]
$ cat test.txt
line1
line2
line3
line4
line5
line6
$ sed 's/[0-9]/0&/' test.txt
line01
line02
line03
line04
line05
line06
替換單獨的單詞
下面的內容就和java正則裡面的反向引用一模一樣了。
[plain]
$ sed 's/\([0-9]\)/0\1/' test.txt
line01
line02
line03
line04
line05
line06
和java正則不同的是,這裡的括號需要轉義,正好與正則相反。
下面看看如何給一個數字添加千位分割符
[plain]
$ echo "1234567" | sed ':start;s/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/;t start'
1,234,567
$ echo "12345678" | sed ':start;s/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/;t start'
12,345,678
$ echo "123456789" | sed ':start;s/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/;t start'
123,456,789
代碼看上去挺嚇人的,這裡圓括號和花括號都需要轉義,轉義之後的圓括號相當於分組,但是用反斜線+數字來引用,花括號轉義之後相當於量詞
這段代碼的意思是:從開頭查找連續的數字,前面的一組,後三個一組,然後在兩組之間添加逗號
然後再次循環這段邏輯,由於之前在最後三個數字前面添加了括號,所以這次查找不會找到最後三個數字,這次的兩組中的第二組是倒數第4、5、6個數字
我們來看一下java中如何實現這個功能:
[java]
String reg1 = "\\d(?=(\\d{3})+$)";
System.out.println("123456789".replaceAll(reg1, "$0,"));
System.out.println("12345678".replaceAll(reg1, "$0,"));
System.out.println("1234567".replaceAll(reg1, "$0,"));
輸出:
123,456,789
12,345,678
1,234,567
在腳本中使用sed
使用包裝腳本
可以包裝一下之前寫過的腳本
[plain]
$ cat sed_test
#!/bin/bash
sed -n '1!G;h;$p' "$1"
方便之後的使用
[plain]
$ cat test.txt
line1
line2
line3
line4
line5
line6
$ sed_test test.txt
line6
line5
line4
line3
line2
line1
重定向sed的輸出
默認,sed會將結果輸出到STDOUT中,我們可以在shell中使用反引號將其輸出定向到變量中。
創建sed實用工具
加倍行間距
[plain]
$ cat test.txt
line1
line2
line3
$ sed '$!G' test.txt
line1
line2
line3
保持空間默認只有一個空行,我們將其追加到模式空間即可
對可能含有空白行的文件加倍行間距
策略是先刪除原來的空白行,然後在重復上面的工作即可。
[plain]
$ sed '/^$/d;$!G' test.txt
給文件中行編號
[plain]
$ cat test.txt
line1
line2
line3
$ sed '=' test.txt | sed 'N;s/\n/ /'
1 line1
2 line2
3 line3
先給出行號,然後消除行號與行直接的換行即可
打印末尾若干行
打印出最後一行很簡單:
[plain]
$ sed -n '$p' test.txt
打印末尾三行呢?
“滾動窗口(rolling window)”是檢驗模式空間中文本行組成的塊的常用方法。
以打印最後三行為例:
[plain]
$ cat test.txt
line1
line2
line3
line4
line5
line6
line7
line8
line9
$ sed ':start;$q;N;4,$D;b start' test.txt
line7
line8
line9
1.如果當前是最後一行,退出
2.讀取下一行
3.如果當前行的行號在3之後,那麼就刪除模式空間中的第一行
4.重復上面的操作
這裡sed會先讀取前三行,讀取第四行之後,刪除第一行,讀取第五行之後,刪除第二行……
注意:如果當前行在4,$中,那麼就執行D
如果我們想除了前三行,其余行都打印,那麼可以這樣:
[plain]
$ sed ':start;$q;N;1,4D;b start' test.txt
line4
line5
line6
line7
line8
line9
刪除行
刪除連續的空白行
關鍵在於如何找到空行與非空行的交界處
[plain]
$ sed '/./,/^$/!d' test.txt
點能匹配到非空行,非空行與第一個空行直接的行不刪除,其余行刪除即可。
當然,這裡如果開頭有空行,也會被刪除掉的
刪除開頭的空白行
[plain]
$ sed '/./,$!d' test.txt
這個就比較簡單了,從第一個非空行到最後一行,不刪除,其余的全刪除即可。
刪除結尾的空白行
[plain]
$ sed '{
> :start
> /^\n*$/{$d;N;b start}
> }' test.txt
注意:這裡有兩層花括號,中間的花括號只作用在前面指定的行上,可以看作是一組命令
如果當前行是空行,那麼模式匹配成功,進入括號中的命令組
如果是最後一行,那麼刪除,否定讀取下一行到模式空間,然後重復上面的步驟
如果讀取的行不是空行,那麼模式匹配失敗,不再進入後面的命令組,sed繼續處理下一行
刪除HTML標簽
在sed中,沒有惰性匹配,所有我們不能使用.*?這種形式,當然,下面這種直白的貪婪模式也肯定是錯的
[plain]
$ sed 's/<.*>//g' test.html
我們簡單的模擬一下惰性匹配:
[plain]
$ sed 's/<[^>]*>//g' test.html
最後,如果你願意,可以加一個刪除空行的處理:
[plain]
$ sed 's/<[^>]*>//g;/^$/d' test.html