Sed高级用法
首先,應該明白模式空間的定義。模式空間就是讀入行所在的緩存,sed對文本行進行的處理都是在這個緩存中進行的。這對接下來的學習是有幫助的。
在正常情況下,sed將待處理的行讀入模式空間,腳本中的命令就一條接著一條的對該行進行處理,直到腳本執(zhí)行完畢,然后該行被輸出,模式空間請空;然后重復剛才的動作,文件中的新的一行被讀入,直到文件處理完備。
但是,各種各樣的原因,比如用戶希望在某個條件下腳本中的某個命令被執(zhí)行,或者希望模式空間得到保留以便下一次的處理,都有可能使得sed在處理文件的時候不按照正常的流程來進行。這個時候,sed設置了一些高級命令來滿足用戶的要求。
總的來說,這些命令可以劃分為以下三類:
1. N、D、P:處理多行模式空間的問題;
2. H、h、G、g、x:將模式空間的內(nèi)容放入存儲空間以便接下來的編輯;
3. :、b、t:在腳本中實現(xiàn)分支與條件結(jié)構(gòu)。
多行模式空間的處理:
由于正則表達式是面向行的,因此,如若某個詞組一不分位于某行的結(jié)尾,另外一部分又在下一行的開始,這個時候用grep等命令來處理就相當?shù)睦щy。然而,借助于sed的多行命令N、D、P,卻可以輕易地完成這個任務。
多行Next(N)命令是相對于next(n)命令的,后者將模式空間中的內(nèi)容輸出,然后把下一行讀入模式空間,但是腳本并不會轉(zhuǎn)移到開始而是從當前的n 命令之后開始執(zhí)行;而前者則保存原來模式空間中的內(nèi)容,再把新的一行讀入,兩者之間依靠一個換行符"\n"來分隔。在N命令執(zhí)行后,控制流將繼續(xù)用N命令以后的命令對模式空間進行處理。
值得注意的是,在多行模式中,特殊字符"^"和"$"匹配的是模式空間的最開始與最末尾,而不是內(nèi)嵌"\n"的開始與末尾。
例1:
$ cat expl.1
Consult Section 3.1 in the Owner and Operator
Guide for a description of the tape drives
available on your system.
現(xiàn)在要將"Owner and Operator Guide"替換為"Installation Guide":
$ sed '/Operator$/{
> N
> s/Owner and Operator\nGuide/Installation Guide\
> /
> }' expl.1
在上面的例子中要注意的是,行與行之間存在內(nèi)嵌的換行符;另外在用于替代的內(nèi)容中要插入換行符的話,要用如上的"\"的轉(zhuǎn)義。
再看一個例子:
例2:
$ cat expl.2
Consult Section 3.1 in the Owner and Operator
Guide for a description of the tape drives
available on your system.
Look in the Owner and Operator Guide shipped with your system.
Two manuals are provided including the Owner and
Operator Guide and the User Guide.
The Owner and Operator Guide is shipped with your system.
$ sed 's/Owner and Operator Guide/Installation Guide/
> /Owner/{
> N
> s/ *\n/ /
> s/Owner and Operator Guide */Installation Guide\
> /
}' expl.2
結(jié)果得到:
Consult Section 3.1 in the Installation Guide
for a description of the tape drives
available on your system.
Look in the Installation Guide shipped with your system.
Two manuals are provided including the Installation Guide
and the User Guide.
The Installation Guide is shipped with your system.
看上去sed命令中作了兩次替換是多余的。實際上,如果去掉第一次替換,再運行腳本,就會發(fā)現(xiàn)輸出存在兩個問題。一個是結(jié)果中最后一行不會被替換(在某些版本的sed中甚至不會被輸出)。這是因為最后一行匹配了"Owner",執(zhí)行N命令,但是已經(jīng)到了文件末尾,某些版本就會直接打印這行再退出,而另外一些版本則是不作出打印立即退出。對于這個問題可以通過命令"$!N"來解決。這表示N命令對最后一行不起作用。另外一個問題是"look manuals"一段被拆為兩行,而且與下一段的空行被刪除了。這是因為內(nèi)嵌的換行符被替換的結(jié)果。因此,sed中做兩次替換一點也不是多余的。
例3:
$ cat expl.3
<para>
This is a test paragraph in Interleaf style ASCII. Another line
in a paragraph. Yet another.
<Figure Begin>
v.1111111111111111111111100000000000000000001111111111111000000
100001000100100010001000001000000000000000000000000000000000000
000000
<Figure End>
<para>
More lines of text to be found after the figure.
These lines should print.
我們的sed命令是這樣的:
$ sed '/<para>{
> N
> c\
> .LP
> }
> /<Figure Begin>/,/<Figure End>/{
> w fig.interleaf
> /<Figure End>/i\
> .FG\
> <insert figure here>\
> .FE
> d
> }
> /^$/d' expl.3
運行后得到的結(jié)果是:
.LP
This is a test paragraph in Interleaf style ASCII. Another line
in a paragraph. Yet another.
.FG
<insert figure here>
.FE
.LP
More lines of text to e found after the figure.
These lines should print.
而<Figure Begin>與<Figure End>之間的內(nèi)容則寫入文件"fig.interleaf"。值得注意的是命令"d"并不會影響命令i插入的內(nèi)容。
命令"d"作用是刪除模式空間的內(nèi)容,然后讀入新的行,sed腳本從頭再次開始執(zhí)行。而命令"D"的不同之處在于它刪除的是直到第一個內(nèi)嵌換行符為止的模式空間的一部分,但是不會讀入新的行,腳本將回到開始對剩下內(nèi)容進行處理。
例4:
$ cat expl.4
This line is followed by 1 blank line.
This line is followed by 2 blank line.
This line is followed by 3 blank line.
This line is followed by 4 blank line.
This is the end.
不同的刪除命令獲得不同的結(jié)果:
$ sed '/^$/{ $ sed '/^$/{
> N > N
> /^\n$/d > /^\n$/D
> }' expl.4 > }' expl.4
sed對文件中每一行(不管處理與否)的默認動作是將其輸出,如果加上選項"-n",則輸出動作會被抑制,這時還希望輸出就需要打印命令。單行模式空間的打印命令是"p",多行模式空間的打印命令是"P"。P命令打印的是模式空間中直到第一個內(nèi)嵌換行符為止的一部分。
P命令通常出現(xiàn)在N命令之后D命令之前,由此構(gòu)成一個輸入輸出循環(huán)。在這種情況下,模式空間中始終存在兩行文本,而輸出始終是一行文本。使用這種循環(huán)的目的在于輸出模式空間中的第一行,然后腳本回到起始處,再對空間中的第二行進行處理。設想一下,如果沒有這個循環(huán),當腳本執(zhí)行完備,模式空間中的內(nèi)容都會被輸出,可能就不符合使用者的要求或者降低了程序執(zhí)行的效率。
下面是一個例子:
例5:
$ cat expl.5
Here are examples of the UNIX
System. Where UNIX
System appears, it should be the UNIX
Operating System.
$ sed '/UNIX$/{
> N
> /\nSystem/{
> s// Operating &/
> P
> D
> }
> }' expl.5
替換的結(jié)果是:
Here are examples of the UNIX Operating
System. Where UNIX Operating
System appears, it should be the UNIX
Operating System.
可以將sed命令中的"P"、"D"換作小寫,比較一下兩種類型的命令的不同之處。
下面的例子就有相當?shù)碾y度了:
例6:
$ cat expl.6
I want to see @fl(what will happen) if we put the
font change commands @fl(on a set of lines). If I understand
things (correctly), the @fl(third) line causes problems. (No?).
Is this really the case, or is it (maybe) just something else?
Let's test having two on a line @fl(here) and @fl(there) as
well as one that begins on one line and ends @fl(somewhere
on another line). What if @fl(it is here) on the line?
Another @fl(one).
現(xiàn)在要作的就是將"fl@(…)替換為"\fB(…)\fR。以下就是滿足條件的sed命令:
$ sed 's/@fl(\([^)]*\))/\\fB\1\\fR/g
> /@fl(.*/{
> N
> s/@fl(\(.*\n[^)]*\))/\\fB\1\\fR/g
> P
> D
> }' expl.6
然而,如果不使用這種輸入輸出循環(huán),而是單單用N來實現(xiàn)的話,就會出現(xiàn)問題:
$ sed 's/@fl(\([^)]*\))/\\fB\1\\fR/g
> /@fl(.*/{
> N
> s/@fl(\(.*\n[^)]*\))/\\fB\1\\fR/g
> }' expl.6
這樣的sed腳本是有漏洞的。
對行進行存儲:
前面已經(jīng)解釋了模式空間的定義,而在sed中還有一個緩存叫作存儲空間。在模式空間和存儲空間中的內(nèi)容可以通過一組命令互相拷貝:
命令 簡寫 功能
Hold h或H 將模式空間的內(nèi)容拷貝或附加到存儲空間
Get g或G 將存儲空間的內(nèi)容拷貝或附加到模式空間
Exchange x 交換模式空間和存儲空間中的內(nèi)容
命令的大小寫的區(qū)別在于大寫的命令是將源空間的內(nèi)容附加到目標空間,而小寫的命令則是用源空間的內(nèi)容覆蓋目標空間。值得注意的是,不管是Hold命令還是Get命令,都會在目的空間的原有內(nèi)容之后加上一個換行符,然后才把源空間中的內(nèi)容加到換行符的后面。
從下面這個例子,可以體會這部分內(nèi)容的初步應用:
例7:
$ cat expl.7
1
2
11
22
111
222
我們要做的工作就是將第一行與第二行,第三行與第四行,第五行與第六行互換。sed的命令各式是:
$ sed '
> /1/{
> h
> d
> }
> /2/{
> G
> }' expl.7
這個過程是這樣的:首先,sed將第一行讀入模式空間,然后h命令將其放入存儲空間保存起來,一個d命令又把模式空間中的內(nèi)容清空;接著sed把第二行讀入模式空間,然后G命令把存儲空間中的內(nèi)容附加到模式空間(注意的是在模式空間的原內(nèi)容末尾是加了一個換行符的)。
最后得到的結(jié)果如下:
2
1
22
11
222
111
使用H或h命令的時候,比較常見的是在這個命令之后加上d命令,這樣一來,sed腳本不會到達最后,因而模式空間中的內(nèi)容也就不會輸出了。另外,如果把d換作n,或者把G換作g,都不會達到目的的。
子母的大小寫轉(zhuǎn)換什么最方便,估計是tr了。
$ tr "[a-z]" "[A-Z]" File
很利害的是sed也可以完成這個轉(zhuǎn)換。相應的命令是y:
$ sed '
> /[address]/y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/' File
然而y命令是對整個行完全進行修改,因此如果只是將行里面的幾個字符變換大小寫的話,這樣做是行不通的。為完成這個工作,需要借助上面剛提到的Hold和Get命令了。
cat expl.8
find the Match statement
Consult the Get statement
using the Read statement to retrieve data
$ sed '/the .* statement/{
> h
> s/.*the \(.*\) statement.*/\1/
> y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
> G
> s/\(.*\)\n\(.*the \).*\( statement.*\)/\2\1\3/
> }' expl.8
以第一行的處理過程來說明這段命令的含意:
(1) "find the Match statement"被放入存儲空間;
(2) 替換改行得到:Match;
(3) 將(2)的結(jié)果轉(zhuǎn)換為大寫:MATCH;
(4) 從存儲空間去處(1)保留的內(nèi)容附加到模式空間,此時模式空間的內(nèi)容為:
MATCH\nfind the Match statement
(5) 再次對模式空間的內(nèi)容替換得到:find the MATCH statement。
下面將舉到的例子要用到比較扎實的正則表達式,不過沒有關系,慢慢來,一切問題都是可以解決的。另外這個例子用到的文本主要是和編輯排版有關的,這方面我不大會,所以我就只是把sed腳本拿出來,抓住核心,省掉那些細枝末節(jié)的東西:
例9:
$ cat expl.9.sed
h
s/[][\\*.]/\\&/g
x
s/[\\&]/\\&/g
s/^\.XX //
s/$/\//
x
s/^\\\.XX \(.*\)$/\/^\\.XX \/s\/\1//
G
s/\n//
(1) h:講文本行放入存儲空間。
(2) s/[][\\*.]/\\&/g:這個表達式難度比較大,如果在類表達,也就是"[]"中的第一個字符是"]"的話,那么"]"就喪失了它的特殊含意;另外,唉"[]"中,僅僅只有"\"是有特殊含意的,言下之意就是"*"、"."都是理解為字面意思,要使他們具有特殊意義就必須使用"\"的轉(zhuǎn)義了;雖然在表達式中沒有出現(xiàn),也要提一下,在"[]"中只有"^"出現(xiàn)在第一的位置時,表示"非"的含意,其余情況就是字面解釋,而"$"僅僅是在正則表達式的末尾時才有特殊含意。"\\"去掉了"\"的特殊含意,"&"表示向前引用,因此,第二個命令的意思就是:將模式空間中的"["、"] "、"\"、"*"、"."依次用"\["、"\]"、"\\"、"\*"、"\."來替換。
(3) x:交換模式空間和存儲空間。執(zhí)行這個命令后模式空間的內(nèi)容是原文的內(nèi)容,而存儲空間中的內(nèi)容發(fā)生變化,各個特殊字符都被替換成為了"\&"。
(4) s/[\\&]/\\&/g:對模式空間處理,出現(xiàn)的"\"或者"&"都會替換為"\\"或者"\&"。
(5) s/$/\//:這個好理解,就是在模式空間的結(jié)尾加上一個"/"。
(6) x:再次交換兩個空間的內(nèi)容。
(7) s/^\\\.XX \(.*\)$/\/^\\.XX \/s\/\1//:這個沒有什么難度,就是那幾個引用容易把人看暈了,仔細一點,不會有問題的,就略過吧。
(Cool G:略了。
(9) s/\n//:刪除換行符。
這個腳本有什么用呢?用以下的文本實驗就清楚了:
.XX "asterisk (*) metacharacter"
下面是每次命令的結(jié)果,第一行和第二行分別表示模式空間和存儲空間的內(nèi)容:
1. .XX "asterisk (*) metacharacter"
.XX "asterisk (*) metacharacter"
2. \.XX "asterisk (\*) metacharacter"
.XX "asterisk (*) metacharacter"
3. .XX "asterisk (*) metacharacter"
\.XX "asterisk (\*) metacharacter"
4. .XX "asterisk (*) metacharacter"
\.XX "asterisk (\*) metacharacter"
5. "asterisk (*) metacharacter"
\.XX "asterisk (\*) metacharacter"
6. "asterisk (*) metacharacter"/
\.XX "asterisk (\*) metacharacter"
7. \.XX "asterisk (\*) metacharacter"
"asterisk (*) metacharacter"/
8. /^\.XX /s/"asterisk (\*) metacharacter"/
"asterisk (*) metacharacter"/
9. /^\.XX /s/"asterisk (\*) metacharacter"/\n/"asterisk (*) metacharacter"/
10./^\.XX /s/"asterisk (\*) metacharacter"/"asterisk (*) metacharacter"/
看到?jīng)]有,其實"s/[\\&]/\\&/"沒有在我們的例子中沒有起作用,但是它不可少,因為在s命令的第二部分,"\"和"&"都是有特殊含意的,所以要預先轉(zhuǎn)義掉其特殊含意。
明白了嗎?當你希望用一個shell腳本自動生成一個主要是替換命令的sed腳本的時候,會發(fā)現(xiàn)這個以上的內(nèi)容對特殊字符的處理是多么得關鍵。
出了上面的應用,存儲空間甚至還能夠?qū)⒑芏嘈械膬?nèi)容存儲起來供以后的輸出。實際上,這一功能對html等具有非常明顯的結(jié)構(gòu)的文本非常有效。下面是相關的例子:
例10
cat expl.10
<p>My wife won't let me buy a power saw. She is afraid of an
accident if I use one.
So I rely on a hand saw for a variety of weekend projects like
building shelves.
However, if I made my living as a carpenter, I would
have to use a power
saw. The speed and efficiency provided by power tools
would be essential to being productive.</p>
<p>For people who create and modify text files,
sed and awk are power tools for editing.</p>
<p>Most of the things that you can do with these programs
can be done interactively with a text editor. However,
using these programs can save many hours of repetitive
work in achieving the same result.</p>
$ sed '/^$/!{
> H
> d
> }
> /^$/{
> x
> s/^\n/<p>/
> s/$/<\/p>/
> G
> }' expl.10
運行一下這個命令,看看結(jié)果是怎樣的。其實結(jié)果已經(jīng)不重要了。通過這個子,應該學會的是腳本中體現(xiàn)的流程控制的思想。腳本的第一部分使用"!"表示對不匹配的行進行處理,但是這種處理因為"d"的存在,不會走腳本的底部,自然也就不會有任何的輸出;在腳本的第二部分中,腳本的確是到了最后的,相應的也清除了模式空間和存儲空間的內(nèi)容,為讀入下一段做好了準備。
本來這個例子已經(jīng)完了,但是還有種情況,如果文件的最后一行不是空行會出現(xiàn)什么結(jié)果?顯然,文本的最后一段不會被輸出。這種情況怎么處理呢?最明智的辦法就是自己"制造"一個空行。新的腳本是這樣的:
$ sed '${
> /^$/!{
> H
> s/.*//
> }
> }
> /^$/!{
> H
> d
> }
> /^$/{
> x
> s/^\n/<p>/
> s/$/<\/p>/
> G
> }' expl.10
流程控制命令
為了使使用者在書寫sed腳本的時候真正的"自由",sed還允許在腳本中用":"設置記號,然后用"b"和"t"命令進行流程控制。顧名思義,"b"表示"branch","t"表示"test";前者就是分支命令,后者則是測試命令。
首先來看標簽的各式是什么。這個標簽放置在你希望流程所開始的地方,單獨放一行,以冒號開始。冒號與變遷之間不允許有空格或者制表符,標簽最后如果有空格的話,也會被認為是標簽的一部分。
再來說b命令。它的格式是這樣的:
[address]b[label]
它的含意是,如果滿足address,則sed流程跟隨標簽跳轉(zhuǎn):如果標簽指明的話,腳本首先假設這個標簽在b命令以下的某行,然后轉(zhuǎn)入該行執(zhí)行相應的命令;如果這個標簽不存在的話,控制流程就直接跳到腳本的末尾。否則繼續(xù)執(zhí)行后續(xù)的命令。
在某些情況下,b命令和!命令有些相似,但是!命令只能對緊挨它的{}中的內(nèi)容起作用,而b命令則給予使用者足夠的自由在sed腳本中選擇哪些命令應該被執(zhí)行,哪些命令不應該被執(zhí)行。下面提供幾種b命令的經(jīng)典用法:
(1) 創(chuàng)建循環(huán):
:top
command1
command2
/pattern/b top
command3
(2) 忽略某些不滿足條件的命令:
command1
/patern/b end
command2
:end
command3
(3) 命令的兩個部分只能執(zhí)行其中一個:
command1
/pattern/b dothere
command
b
:dothere
command3
t命令的格式和b命令是一樣的:
[address]t[label]
它表示的是如果滿足address的話,sed腳本就會根據(jù)t命令指示的標簽進行流程轉(zhuǎn)移。而標簽的規(guī)則和上面講的b命令的規(guī)則是一樣的。下面也給出一個例子:
s/pattern/replacement/
t break
command
:break
還是用例6的sed腳本為例子。其實仔細思考一下就會發(fā)現(xiàn)這個腳本不是足夠強大:如果某個@fl結(jié)構(gòu)跨越了兩行,比如說三行怎么辦?這就需要下面這個加強版的sed了:
$ cat expl.6.sed
:begin
/@fl(\([^)]*\))/{
s//\\fB\1\\fR/g
b begin
}
/@fl(.*/{
N
s/@f1(\([^)]*\n[^)]*\))/\\fB\1\\fR/g
t again
b begin
}
:again
P
D
?
?
轉(zhuǎn):http://club.topsage.com/thread-2372858-1-1.html?
總結(jié)
- 上一篇: AWK介绍
- 下一篇: Text Mining Blog