您好,登錄后才能下訂單哦!
下面以sed -f script_file input_file為例,其工作原理如下圖所示:
其中input_file表示處理文件,script_file表示腳本命令。
工作原理如下:
a) 首先將處理文件的第一行讀入模式空間。
b) 接著對模式空間中的這一行內容執行腳本命令中設置的命令,從上至下依次執行腳本命令中設置的命令。
c) 腳本命令執行完成后,輸出模式空間中的內容。
d) 清空模式空間中得內容,并讀入處理文件中得第二行內容,并重復b) 和 c) 步驟的內容,直至處理完文件中的所有內容。
苦逼的碼農可以查看圖片右上角的偽代碼,可能解釋得更加清晰一點。
模式空間
以上描述中出現了一個詞叫模式空間,下面對其進行解釋。由于sed命令的執行并不修改原始文件,也就是說輸入文件是什么,執行完sed命令后,輸入文件沒有變化,這么說來,肯定不能在輸入文件的基礎上對其進行編輯,所以需要一塊單獨的空間,用于轉存文件中得內容,然后進行處理并輸出。模式空間就是這么一塊轉存輸入文件內容的空間,并且sed命令一次讀入輸入文件中得一行內容到模式空間,使用sed命令支持的腳步執行處理完模式空間中得內容后,輸出處理完的結果并刪除模式空間中得內容,準備讀入下一行輸入文件中得內容。
多行模式空間
如上所述,模式空間每次讀入輸入文件中的一行進行處理,有時只讀入一行內容到模式空間對輸入文件的處理能力很有限,比如它很難處理一個在一行末尾處開始,并在下一行開始處結束的短語。而多行模式空間就是為了解決這個問題而提出的,他允許將模式空間中的內容從一行擴展到多行。具體內容在本文后面會有講解。
保持空間
模式空間是容納當前輸入行的緩沖區,而保持空間是預留的一部分緩沖區,用于臨時存儲模式空間中的內容。模式空間中的內容可以復制到保持空間,保持空間中的內容也可以復制回模式空間。具體內容在本文后面會有講解。
正則表達式元字符
元字符 | 功能 | 示例 |
^ | 行首定位符 | /^my/ 匹配所有以my開頭的行 |
$ | 行尾定位符 | /my$/ 匹配所有以my結尾的行 |
. | 匹配除換行符以外的單個字符 | /m..y/ 匹配包含字母m,后跟兩個任意字符,再跟字母y的行 |
* | 匹配零個或多個前導字符 | /my*/ 匹配包含字母m,后跟零個或多個y字母的行 |
[] | 匹配指定字符組內的任一字符 | /[Mm]y/ 匹配包含My或my的行 |
[^] | 匹配不在指定字符組內的任一字符 | /[^Mm]y/ 匹配包含y,但y之前的那個字符不是M或m的行 |
\(..\) | 保存已匹配的字符 | 1,20s/\(you\)self/\1r/ 標記元字符之間的模式,并將其保存為標簽1,之后可以使用\1來引用它。最多可以定義9個標簽,從左邊開始編號,最左邊的是第一個。此例中,對第1到第20行進行處理,you被保存為標簽1,如果發現youself,則替換為your。 |
& | 保存查找串以便在替換串中引用 | s/my/**&**/ 符號&代表查找串。my將被替換為**my** |
\< | 詞首定位符 | /\<my/ 匹配包含以my開頭的單詞的行 |
\> | 詞尾定位符 | /my\>/ 匹配包含以my結尾的單詞的行 |
x\{m\} | 連續m個x | /9\{5\}/ 匹配包含連續5個9的行 |
x\{m,\} | 至少m個x | /9\{5,\}/ 匹配包含至少連續5個9的行 |
x\{m,n\} | 至少m個,但不超過n個x | /9\{5,7\}/ 匹配包含連續5到7個9的行 |
常用命令與選項
命令 | 功能 | 示例 |
a\ | 在當前行后添加一行或多行。多行時除最后一行外,每行末尾需用“\”續行 | |
c\ | 用此符號后的新文本替換當前行中的文本。多行時除最后一行外,每行末尾需用"\"續行 | |
i\ | 在當前行之前插入文本。多行時除最后一行外,每行末尾需用"\"續行 | |
d | 刪除行 | |
h | 把模式空間里的內容復制到暫存緩沖區 | |
H | 把模式空間里的內容追加到暫存緩沖區 | |
g | 把暫存緩沖區里的內容復制到模式空間,覆蓋原有的內容 | |
G | 把暫存緩沖區的內容追加到模式空間里,追加在原有內容的后面 | |
l | 列出非打印字符 | |
p | 打印行 | |
n | 讀入下一輸入行,并從下一條命令而不是第一條命令開始對其的處理 | |
q | 結束或退出sed | |
r | 從文件中讀取輸入行 | |
! | 對所選行以外的所有行應用命令 | |
s | 用一個字符串替換另一個 | |
g | 在行內進行全局替換 | |
w | 將所選的行寫入文件 | |
x | 交換暫存緩沖區與模式空間的內容 | |
y | 將字符替換為另一字符(不能對正則表達式使用y命令 | |
-e | 進行多項編輯,即對輸入行應用多條sed命令時使用 | |
-n | 取消默認的輸出 | |
-f | 指定sed腳本的文件名 | |
應用實例整理
高級應用實列整理
首先,應該明白模式空間的定義。模式空間就是讀入行所在的緩存,sed對文本行進行的處理都是在這個緩存中進行的。這對接下來的學習是有幫助的。
在正常情況下,sed將待處理的行讀入模式空間,腳本中的命令就一條接著一條的對該行進行處理,直到腳本執行完畢,然后該行被輸出,模式空間請空;然后重復剛才的動作,文件中的新的一行被讀入,直到文件處理完備。
但是,各種各樣的原因,比如用戶希望在某個條件下腳本中的某個命令被執行,或者希望模式空間得到保留以便下一次的處理,都有可能使得sed在處理文件的時候不按照正常的流程來進行。這個時候,sed設置了一些高級命令來滿足用戶的要求。
總的來說,這些命令可以劃分為以下三類:
1. N、D、P:處理多行模式空間的問題;
2. H、h、G、g、x:將模式空間的內容放入存儲空間以便接下來的編輯;
3. :、b、t:在腳本中實現分支與條件結構。
多行模式空間的處理:
由于正則表達式是面向行的,因此,如若某個詞組一不分位于某行的結尾,另外一部分又在下一行的開始,這個時候用grep等命令來處理就相當的困難。然而,借助于sed的多行命令N、D、P,卻可以輕易地完成這個任務。
多行Next(N)命令是相對于next(n)命令的,后者將模式空間中的內容輸出,然后把下一行讀入模式空間,但是腳本并不會轉移到開始而是從當前的n 命令之后開始執行;而前者則保存原來模式空間中的內容,再把新的一行讀入,兩者之間依靠一個換行符"\n"來分隔。在N命令執行后,控制流將繼續用N命令以后的命令對模式空間進行處理。
值得注意的是,在多行模式中,特殊字符"^"和"$"匹配的是模式空間的最開始與最末尾,而不是內嵌"\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.
現在要將"Owner and Operator Guide"替換為"Installation Guide":
$ sed '/Operator$/{
> N
> s/Owner and Operator\nGuide/Installation Guide\
> /
> }' expl.1
在上面的例子中要注意的是,行與行之間存在內嵌的換行符;另外在用于替代的內容中要插入換行符的話,要用如上的"\"的轉義。
再看一個例子:
例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
結果得到:
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命令中作了兩次替換是多余的。實際上,如果去掉第一次替換,再運行腳本,就會發現輸出存在兩個問題。一個是結果中最后一行不會被替換(在某些版本的sed中甚至不會被輸出)。這是因為最后一行匹配了"Owner",執行N命令,但是已經到了文件末尾,某些版本就會直接打印這行再退出,而另外一些版本則是不作出打印立即退出。對于這個問題可以通過命令"$!N"來解決。這表示N命令對最后一行不起作用。另外一個問題是"look manuals"一段被拆為兩行,而且與下一段的空行被刪除了。這是因為內嵌的換行符被替換的結果。因此,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
運行后得到的結果是:
.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>之間的內容則寫入文件"fig.interleaf"。值得注意的是命令"d"并不會影響命令i插入的內容。
命令"d"作用是刪除模式空間的內容,然后讀入新的行,sed腳本從頭再次開始執行。而命令"D"的不同之處在于它刪除的是直到第一個內嵌換行符為止的模式空間的一部分,但是不會讀入新的行,腳本將回到開始對剩下內容進行處理。
例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.
不同的刪除命令獲得不同的結果:
$ sed '/^$/{ $ sed '/^$/{
> N > N
> /^\n$/d > /^\n$/D
> }' expl.4 > }' expl.4
sed對文件中每一行(不管處理與否)的默認動作是將其輸出,如果加上選項"-n",則輸出動作會被抑制,這時還希望輸出就需要打印命令。單行模式空間的打印命令是"p",多行模式空間的打印命令是"P"。P命令打印的是模式空間中直到第一個內嵌換行符為止的一部分。
P命令通常出現在N命令之后D命令之前,由此構成一個輸入輸出循環。在這種情況下,模式空間中始終存在兩行文本,而輸出始終是一行文本。使用這種循環的目的在于輸出模式空間中的第一行,然后腳本回到起始處,再對空間中的第二行進行處理。設想一下,如果沒有這個循環,當腳本執行完備,模式空間中的內容都會被輸出,可能就不符合使用者的要求或者降低了程序執行的效率。
下面是一個例子:
例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
替換的結果是:
Here are examples of the UNIX Operating
System. Where UNIX Operating
System appears, it should be the UNIX
Operating System.
可以將sed命令中的"P"、"D"換作小寫,比較一下兩種類型的命令的不同之處。
下面的例子就有相當的難度了:
例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).
現在要作的就是將"fl@(…)替換為"\fB(…)\fR。以下就是滿足條件的sed命令:
$ sed 's/@fl(\([^)]*\))/\\fB\1\\fR/g
> /@fl(.*/{
> N
> s/@fl(\(.*\n[^)]*\))/\\fB\1\\fR/g
> P
> D
> }' expl.6
然而,如果不使用這種輸入輸出循環,而是單單用N來實現的話,就會出現問題:
$ sed 's/@fl(\([^)]*\))/\\fB\1\\fR/g
> /@fl(.*/{
> N
> s/@fl(\(.*\n[^)]*\))/\\fB\1\\fR/g
> }' expl.6
這樣的sed腳本是有漏洞的。
對行進行存儲:
前面已經解釋了模式空間的定義,而在sed中還有一個緩存叫作存儲空間。在模式空間和存儲空間中的內容可以通過一組命令互相拷貝:
命令 簡寫 功能
Hold h或H 將模式空間的內容拷貝或附加到存儲空間
Get g或G 將存儲空間的內容拷貝或附加到模式空間
Exchange x 交換模式空間和存儲空間中的內容
命令的大小寫的區別在于大寫的命令是將源空間的內容附加到目標空間,而小寫的命令則是用源空間的內容覆蓋目標空間。值得注意的是,不管是Hold命令還是Get命令,都會在目的空間的原有內容之后加上一個換行符,然后才把源空間中的內容加到換行符的后面。
從下面這個例子,可以體會這部分內容的初步應用:
例7:
$ cat expl.7
1
2
11
22
111
222
我們要做的工作就是將第一行與第二行,第三行與第四行,第五行與第六行互換。sed的命令各式是:
$ sed '
> /1/{
> h
> d
> }
> /2/{
> G
> }' expl.7
這個過程是這樣的:首先,sed將第一行讀入模式空間,然后h命令將其放入存儲空間保存起來,一個d命令又把模式空間中的內容清空;接著sed把第二行讀入模式空間,然后G命令把存儲空間中的內容附加到模式空間(注意的是在模式空間的原內容末尾是加了一個換行符的)。
最后得到的結果如下:
2
1
22
11
222
111
使用H或h命令的時候,比較常見的是在這個命令之后加上d命令,這樣一來,sed腳本不會到達最后,因而模式空間中的內容也就不會輸出了。另外,如果把d換作n,或者把G換作g,都不會達到目的的。
子母的大小寫轉換什么最方便,估計是tr了。
$ tr "[a-z]" "[A-Z]" File
很利害的是sed也可以完成這個轉換。相應的命令是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)的結果轉換為大寫:MATCH;
(4) 從存儲空間去處(1)保留的內容附加到模式空間,此時模式空間的內容為:
MATCH\nfind the Match statement
(5) 再次對模式空間的內容替換得到:find the MATCH statement。
下面將舉到的例子要用到比較扎實的正則表達式,不過沒有關系,慢慢來,一切問題都是可以解決的。另外這個例子用到的文本主要是和編輯排版有關的,這方面我不大會,所以我就只是把sed腳本拿出來,抓住核心,省掉那些細枝末節的東西:
例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:這個表達式難度比較大,如果在類表達,也就是"[]"中的第一個字符是"]"的話,那么"]"就喪失了它的特殊含意;另外,唉"[]"中,僅僅只有"\"是有特殊含意的,言下之意就是"*"、"."都是理解為字面意思,要使他們具有特殊意義就必須使用"\"的轉義了;雖然在表達式中沒有出現,也要提一下,在"[]"中只有"^"出現在第一的位置時,表示"非"的含意,其余情況就是字面解釋,而"$"僅僅是在正則表達式的末尾時才有特殊含意。"\\"去掉了"\"的特殊含意,"&"表示向前引用,因此,第二個命令的意思就是:將模式空間中的"["、"] "、"\"、"*"、"."依次用"\["、"\]"、"\\"、"\*"、"\."來替換。
(3) x:交換模式空間和存儲空間。執行這個命令后模式空間的內容是原文的內容,而存儲空間中的內容發生變化,各個特殊字符都被替換成為了"\&"。
(4) s/[\\&]/\\&/g:對模式空間處理,出現的"\"或者"&"都會替換為"\\"或者"\&"。
(5) s/$/\//:這個好理解,就是在模式空間的結尾加上一個"/"。
(6) x:再次交換兩個空間的內容。
(7) s/^\\\.XX \(.*\)$/\/^\\.XX \/s\/\1//:這個沒有什么難度,就是那幾個引用容易把人看暈了,仔細一點,不會有問題的,就略過吧。
(Cool G:略了。
(9) s/\n//:刪除換行符。
這個腳本有什么用呢?用以下的文本實驗就清楚了:
.XX "asterisk (*) metacharacter"
下面是每次命令的結果,第一行和第二行分別表示模式空間和存儲空間的內容:
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"/
看到沒有,其實"s/[\\&]/\\&/"沒有在我們的例子中沒有起作用,但是它不可少,因為在s命令的第二部分,"\"和"&"都是有特殊含意的,所以要預先轉義掉其特殊含意。
明白了嗎?當你希望用一個shell腳本自動生成一個主要是替換命令的sed腳本的時候,會發現這個以上的內容對特殊字符的處理是多么得關鍵。
出了上面的應用,存儲空間甚至還能夠將很多行的內容存儲起來供以后的輸出。實際上,這一功能對html等具有非常明顯的結構的文本非常有效。下面是相關的例子:
例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
運行一下這個命令,看看結果是怎樣的。其實結果已經不重要了。通過這個子,應該學會的是腳本中體現的流程控制的思想。腳本的第一部分使用"!"表示對不匹配的行進行處理,但是這種處理因為"d"的存在,不會走腳本的底部,自然也就不會有任何的輸出;在腳本的第二部分中,腳本的確是到了最后的,相應的也清除了模式空間和存儲空間的內容,為讀入下一段做好了準備。
本來這個例子已經完了,但是還有種情況,如果文件的最后一行不是空行會出現什么結果?顯然,文本的最后一段不會被輸出。這種情況怎么處理呢?最明智的辦法就是自己"制造"一個空行。新的腳本是這樣的:
$ 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流程跟隨標簽跳轉:如果標簽指明的話,腳本首先假設這個標簽在b命令以下的某行,然后轉入該行執行相應的命令;如果這個標簽不存在的話,控制流程就直接跳到腳本的末尾。否則繼續執行后續的命令。
在某些情況下,b命令和!命令有些相似,但是!命令只能對緊挨它的{}中的內容起作用,而b命令則給予使用者足夠的自由在sed腳本中選擇哪些命令應該被執行,哪些命令不應該被執行。下面提供幾種b命令的經典用法:
(1) 創建循環:
:top
command1
command2
/pattern/b top
command3
(2) 忽略某些不滿足條件的命令:
command1
/patern/b end
command2
:end
command3
(3) 命令的兩個部分只能執行其中一個:
command1
/pattern/b dothere
command
b
:dothere
command3
t命令的格式和b命令是一樣的:
[address]t[label]
它表示的是如果滿足address的話,sed腳本就會根據t命令指示的標簽進行流程轉移。而標簽的規則和上面講的b命令的規則是一樣的。下面也給出一個例子:
s/pattern/replacement/
t break
command
:break
還是用例6的sed腳本為例子。其實仔細思考一下就會發現這個腳本不是足夠強大:如果某個@fl結構跨越了兩行,比如說三行怎么辦?這就需要下面這個加強版的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
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。