- 論壇徽章:
- 0
|
這是一篇從CU SHELL版移過來的文章
這篇文章是我學(xué)習(xí)sed后的一點感受,我的實際工作用不上sed,學(xué)習(xí)它僅僅是不想讓花了我42RMB的《sed&awk》一直躺在書架上。我對
本版的帖子做過一次過濾,回復(fù)較多的帖子大概地瀏覽了一遍,搜集例子中的數(shù)據(jù)做練習(xí)。sed&awk講了一些sed的基礎(chǔ)概念,我感受最深的就是
行的概念了,所以我想說說我的一點理解,顯然淺顯,但正適合初學(xué)者(如我)閱讀。
環(huán)境{
GNU/Linux
bash 3.0
GNU sed 4.1.4
}
先看兩個例子:
對于如下sed命令,大家都知道d刪除的不是字符串“pattern”,而是包含字符串pattern的一整行
sed '/pattern/d' testfile
所以d命令的操作是基于行的。類似的,p命令也是基于行的,有這樣一個例子
echo "ABA,aaa" | sed -n '/^[A-Z]*[,][a-z]*/p'
echo "ABA,aaa" | sed -n '/^[A-Z]*[^,][a-z]*/p'
兩個命令的屏幕輸出完全一樣,令人迷惑為什么“看起來相反”的正則表達(dá)式匹配的結(jié)果卻一樣,其實"顯示結(jié)果一樣!=正則表達(dá)式匹配結(jié)果相同",換成如下命令試一試
echo "ABA,aaa" | grep --color '^[A-Z]*[,][a-z]*'
echo "ABA,aaa" | grep --color '^[A-Z]*[^,][a-z]*'
結(jié)果很顯然,sed中p命令是基于行的,把包含匹配的整個行輸出到屏幕。
(包括下面的每個例子,請動手實際操作一下,觀察一下屏幕輸出與你的預(yù)想結(jié)果是否一致)
sed有一套編輯命令,都是基于行操作的,雖然功能不同,但是你用基本的sed概念去理解這些命令,將很快掌握sed。本文先介紹兩個sed的基礎(chǔ)概念,然后用這個基礎(chǔ)概念結(jié)合例子理解sed常用的編輯命令。
1.請永遠(yuǎn)記住這個規(guī)則:
[color="Red"]一次讀取一行
sed把輸入看成文本流。將一個文本文件當(dāng)成一串以'\n'分隔的有限長的字串。sed每次讀入一行(為當(dāng)前行),將其保存在內(nèi)存緩沖區(qū)(稱做模式空間),并將腳本中所有操作命令依次應(yīng)用于當(dāng)前行。(如果你讀過過UNIX入門一類的書籍,對文本流這個概念一定不會陌生,發(fā)揮你的想像,想像一下sed將一行一行的文本,看成是相連的串,用流來形容,非常貼切吧)
截取Xorg.conf中的一段文本做例子,體會一下“一次讀取一行”是怎么回事:
Section "InputDevice"
Identifier "Keyboard0"
Driver "kbd"
Option "XkbModel" "pc105"
Option "XkbLayout" "en_US,ru"
EndSection
#==================================================
Section "InputDevice"
Identifier "Mouse0"
Driver "mouse"
Option "Protocol" "auto"
Option "Device" "/dev/mouse"
Option "ZAxisMapping" "4 5"
# Option "Buttons" "5"
EndSection
執(zhí)行下面這個sed命令之后,Section和EndSection之間的行將被刪除,僅保留了中間一行
sed '/^Section/,/EndSection/d' testfile
嘗試把文本中最后一個EndSection刪除,再次執(zhí)行上面這個命令,跟你預(yù)想的結(jié)果一樣嗎?修改后的文本不匹配/^Section/,/^EndSection/,為什么還會將其刪除?
在這個例子中,sed并不把/^Section/,/^EndSection/之間的多個行一次讀入到內(nèi)存中,而僅僅是一次讀一行,將其復(fù)制到內(nèi)存中的某
個緩沖區(qū),sed將這個緩沖區(qū)叫做模式空間,模式空間存放的內(nèi)容叫做當(dāng)前行,然后在這個當(dāng)前行中搜索開始字符串Section,找到后執(zhí)行d刪除命令,繼
續(xù)讀取下一行,搜索結(jié)束字符串,如果沒有找到,sed會認(rèn)為當(dāng)前行是Section和EndSection之間的行將其刪除,而不管后面的行中是否包含
EndSection。如果后面沒有結(jié)束字符串,一直刪除到文件結(jié)束。
sed一次僅讀取一行,不可能預(yù)知后面是否含有結(jié)束字符串,對于處理這樣的例子,sed有一定的局限性。但是,一次處理一行的設(shè)計使sed簡潔而高效。
SHELL版常有處理大文件(幾百M甚至上G)時的效率問題,我對自造的一個不斷重復(fù)內(nèi)容的100M測試文件進(jìn)行替換測試,sed雖然比不上awk,但速度還是可以接受的。有興趣可以自己試一下:
for ((i=0; i> /tmp/boot.log
done && \
time sed -n 's/kernel/KERNEL/g'
在我的機(jī)器上{lenovo3110:p41.7G}測試結(jié)果如下(我的樣本文件中7500行的boot.log有896行含有kernel,重復(fù)200次內(nèi)容后文件是112MB):
time sed -n 's/kernel/KERNEL/g'
real 0m3.292s
time sed -i 's/kernel/KERNEL/g'
real 0m16.741s
雖然僅測試一個替換,不是測試一個腳本,但從中或多或少可以感受一下sed的簡潔快速。
2. 規(guī)則1的附加規(guī)則:
sed對讀取的當(dāng)前行依次運行整個腳本命令
腳本最后一個命令執(zhí)行完后sed輸出模式空間的內(nèi)容,并自動讀取下一行再次從頭執(zhí)行腳本,如此不斷循環(huán)
這是一個簡單的腳本:
:labp
假設(shè)有一個文本內(nèi)容如下:
1 channel list:
2 http://www.sopcast.com/chlist.xml
3 A simple example of sp-sc command line.
4 ./sp-sc sop://broker.sopcast.com:3912/6098 3908 8908 > /dev/null &
5 Start to transfer channel 6098, 8908 with VLC or mplayer
6 by open the url: http://localhost:8908/tv.asf
對這個文本執(zhí)行命令:
sed -f script file
我們來看一下sed是如何執(zhí)行的
sed讀取第一行到模式空間,依次執(zhí)行腳本中的所有命令,p命令將當(dāng)前模式空間的內(nèi)容輸出到屏幕,然后冒號定義一個標(biāo)簽(這個標(biāo)簽后面沒有用到,只是幫助理解腳本而設(shè)),之后由于執(zhí)行完了所有的命令,sed默認(rèn)會輸出當(dāng)前模式空間的內(nèi)容。于是在屏幕上你將看到兩行
1 channel list:
1 channel list:
執(zhí)行完了腳本中所有的命令后,sed會自動讀取一下行,再次循環(huán)整個腳本。如此不斷循環(huán),直到處理完文件的最后一行后結(jié)束退出sed。
猜測一下用下面的腳本處理后面的文本,結(jié)果會怎樣?
s/Unix/UNIX/g
s/UNIX/UNIX System/g
對下面的文本執(zhí)行命令: sed -f script file
A
beginners guide to the Unix and Linux operating system. Eight simple
tutorials which cover the basics of UNIX / Linux commands.
對輸出結(jié)果自已解釋一下吧。
3.
[color="Red"]n,d,N,t,T,b命令以不同的方式影響文本流
如果沒有if,for,while,可以想像一下C語言能夠做什么?總是讓腳本順序執(zhí)行,有很大一部分問題將不能解決,必須用某些命令控制文本流。
用上面的兩個基礎(chǔ)概念來理解這幾個命令
[color="Red"]1) n
cat -n /etc/profile > /tmp/file
sed -n 'p;n' /tmp/file
sed -n 'n;p' /tmp/file
這兩個例子非常奇妙地利用n命令打印奇數(shù)行和偶數(shù)行,簡單地分析一下:
p
n
sed
首先讀取輸入文件file中的第一行復(fù)制到模式空間,對模式空間的當(dāng)前行依次運行腳本中的每一個命令,p命令將當(dāng)前行輸出到屏幕,然后n命令迫使sed讀
取下一行(第二行),之后由于腳本所有的命令已經(jīng)執(zhí)行完畢,sed會將當(dāng)前模式空間的內(nèi)容(此時即第二行)輸出到屏幕(-n抑制了這個操作),接著讀取第
三行到模式空間,并自頂向下運行腳本命令,開始第二次循環(huán)。
在這里要注意兩次讀取操作:
1. n命令迫使sed讀取下一行到模式空間;
2. 腳本中所有的命令執(zhí)行完后{p;n},sed會自動讀取下一行,并返回腳本頂端開始下一次循環(huán)。
跳過一些不想在其上執(zhí)行命令的行,這是n命令的拿手好戲。下面例子對/var/log/xorg.0.log執(zhí)行如下腳本,將對包含"(II)"的行不執(zhí)行替換操作:
/(II)/{
n
}
s/SIS/sis/g
[color="Red"]2) d
d命令也會迫使sed讀取下一行,不過與n命令有一個微妙的區(qū)別。
d命令刪除模式空間的內(nèi)容,命令執(zhí)行后模式空間沒有內(nèi)容,導(dǎo)致d命令后面的其它命令將不能作用于模式空間,迫使sed讀取下一行,并返回腳本頂部開始下一次循環(huán)。
我碰到過這樣一個問題:寫了一個1000行的C源程序,其中用到一個變量getnum,想將其替換為get_int_num,但是函數(shù)isyn(){...}內(nèi)的getnum保持不變。
我第一次想用n命令實現(xiàn):
/isyn()/,/^}/{
n
}
s/getnum/get_int_num/g
結(jié)果失敗了,具體的例子用一個簡短的數(shù)據(jù)來說明一下:
將下面這個六行數(shù)據(jù)中除A~S的行外,所有的小寫字母替換為*號
1 channel list:
2 http://www.sopcast.com/chlist.xml
3 A simple example of sp-sc command line.
4 ./sp-sc sop://broker.sopcast.com:3912/6098 3908 8908 > /dev/null &
5 Start to transfer channel 6098, 8908 with VLC or mplayer
6 by open the url: http://localhost:8908/tv.asf
即變成:
1 ******* ****:
2 ****://***.*******.***/******.***
3 A simple example of sp-sc command line.
4 ./sp-sc sop://broker.sopcast.com:3912/6098 3908 8908 > /dev/null &
5 Start to transfer channel 6098, 8908 with VLC or mplayer
6 ** **** *** ***: ****://*********:8908/**.***
腳本如下:
/A/,/S/{
n
}
s/[a-z]/*/g
當(dāng)sed讀取第三行到模式空間為當(dāng)前行時,腳本搜索到了A并執(zhí)行n命令讀取第四行,此時sed并沒有返回腳本頂部重新執(zhí)行命令,而是繼續(xù)執(zhí)行n命令后的s命令,將替換操作作用于第四行。
當(dāng)我發(fā)現(xiàn)第四行作了替換后,嘗試多次使用n命令失敗,最后用p命令和d命令組合,變相地跳過了A~S行
/A/,/S/{
p
d
}
s/[a-z]/*/g
sed
讀取第三行到模式空間后,先輸出到屏幕后刪除,根據(jù)d命令的特性,被d刪除的模式空間沒有內(nèi)容,將不能對它進(jìn)行腳本后s/[a-z]/*/g的替換操作,
迫使sed讀取第四行并返回腳本頂部開始執(zhí)行,由于第四仍包含在/A/,/S/內(nèi),將再次執(zhí)行{p;d}操作,如此循環(huán)將/A/,/S/內(nèi)的行變相地跳過
而不作替換。
[color="Red"]3) N
N命令與n命令的區(qū)別是
n讀取下一行,并用讀取的行將當(dāng)前模式空間的內(nèi)容“擠”出去,而N命令讀取下一行后將讀取的行追加到模式空間,兩個行之間用一個'\n'分隔。這有點類似于>和>>重定向符
假如文件當(dāng)作一個模式空間的話,執(zhí)行下面的命令n次
echo "Hello,World." > file
文件內(nèi)將只有最后一次寫入到其中的Hello,World.而如果執(zhí)行的是下面的命令
echo "Hello,World." >> file
文件內(nèi)將有n行Hello,World。
回到那個輸出文件奇數(shù)/偶數(shù)行的例子,可以用下面的命令輸出奇數(shù)/偶數(shù)行:
(大寫的P命令輸出模式空間第一個'\n'前的內(nèi)容)
sed -n 'N;P' file
sed -n 'P;N' file
如果要輸出偶數(shù)行
sed -n '1!P;1!N' file
這個例子很好地說明了N命令是如何執(zhí)行的,并再一次證明了這個附加規(guī)則“腳本最后一個命令執(zhí)行完后sed輸出模式空間的內(nèi)容,并自動讀取下一行再次從頭執(zhí)行腳本,如此不斷循環(huán)”
[color="Red"]4) t,T,b (下面僅舉幾個例子,解釋起來太費勁了)
這三個都是跳轉(zhuǎn)命令。t和T是對s替換操作是否成功進(jìn)行測試,然后決定如何跳轉(zhuǎn),類似if語句;而b命令則無條件地跳轉(zhuǎn)。
返回上面的一個例子:
/A/,/S/{
p
d
}
s/[a-z]/*/g
可以將這個腳本修改為(不帶標(biāo)簽的b命令將跳轉(zhuǎn)到腳本的底部):
/A/,/S/{
b
}
s/[a-z]/*/g
請用上面的兩個基礎(chǔ)概念自行解釋一下運行這個腳本的詳細(xì)過程
下面這個例子試圖將/A/,/S/之間的多個行合并為一行,但是沒有成功,你可以用前面的文本測試一下:
/A/,/S/{
:lab
$!N
s/\n//g
t lab
}
可以將其修改為這樣
/A/{
:lab
/S/b
$!N
s/\n//g
t lab
}
最后,用這個腳本可以刪除下面的變態(tài)C注釋
/\/\*/{
/\*\//s@/\*.*\*/@@ #刪除在同一行上的 /* ...... */
t #如果替換成功則轉(zhuǎn)到腳本底部,結(jié)束一次/*.....*/替換
s@/\*.*$@@ #不成功則繼續(xù)執(zhí)行:刪除/*到行尾(/*前面可能有代碼)
:lab
n
s@.*\*/@@ #搜索是否包含結(jié)束串*/將其替換
t #如果替換成功說明已經(jīng)刪除了/*...*/
s@^.*$@@ #不成功則將行替換為空:這里不能用d,d會跳出這個循環(huán)
b lab
}
測試用的C文本
#include
main()
{
/*需要整行刪除*/
printf("本句和注釋之間的空格不要去掉\n"); /*just a test
******需要整行刪除
/////需要整行刪除
需要整行刪除,后面空行不要刪除*/
printf("漢字不能被刪除\n");
/*test 注意前面空行不能刪除
*/ printf("test:前面的空格不要去掉\n");/*a
*/printf("test3\n");
return(0);
}
不夠完美,須要執(zhí)行兩次sed:
sed -f script test.c | sed -f script
不當(dāng)之處,尚請批評指正。感謝CU SHELL版的各位xdjm,讓我在這里學(xué)會了很多東西。
本文來自ChinaUnix博客,如果查看原文請點:http://blog.chinaunix.net/u/15571/showart_426229.html |
|