sed系列文章:html
sed修煉系列(一):花拳繡腿之入門篇
sed修煉系列(二):武功心法(info sed翻譯+註解)
sed修煉系列(三):sed高級應用之實現窗口滑動技術
sed修煉系列(四):sed中的疑難雜症git
"(注:)"
,爲本人自行加入,助於理解和說明,非原文內容。PS:直到翻譯完,才發現加了不少不少我我的註釋,儘管是一篇譯文,但也捨不得刪掉這些感想。若是這些"注:"有礙觀感,還望各位能體諒。1 簡介
2 調用方式
3 sed程序
3.1 sed是如何工做的
3.2 sed定址:篩選行的方式
3.3 正則表達式一覽
3.4 sed經常使用命令
3.5 sed的s命令
3.6 比較少用的sed命令
3.7 大師級的sed命令(sed標籤功能)
3.8 GNU sed特有的命令
3.9 GNU對正則表達式的反斜線擴展
4 一些簡單的示例腳本
5 GNU sed的限制和優勢
6 學習sed的其餘資源
7 Bugs說明(建議看) 正則表達式
sed是一個流式編輯器。流式編輯器用於對輸入流(文件或管道傳遞的數據)執行基本的文本轉換操做。在某些方面上,sed有點相似於腳本化編輯的編輯器(如ed
),但sed只能經過一次輸入流,所以它的效率要更高。但sed區別於其餘類型編輯器的地方在於它能篩選過濾管道傳遞的文本數據。
(注:sed只能經過一次輸入流,意味着每次的輸入流只能處理一次,所以像(sed -n '2{p;q}';sed -n 3{p;q}) <filename
這樣的命令中,第二個sed語句讀取的輸入流是空流。)算法
一般,會使用下面的方式來調用sed: shell
sed SCRIPT INPUTFILE...
完整的調用格式爲:express
sed OPTIONS... [SCRIPT] [INPUTFILE...]
若是不指定INPUTFILE或者指定的INPUTFILE爲"-",sed將從標準輸入中讀取輸入流並進行過濾。SCRIPT
是第一個非選項參數,sed僅在沒有使用"-e"或"-f script_file"選項時纔將其看成是script部分而非輸入文件。編程
可使用下面的命令行選項來調用sed:c#
'--version'
輸出sed的版本號並退出。bash
'--help'
輸出sed命令行的簡單幫助信息並退出。markdown
'-n'
'--quiet'
'--silent'
默認狀況下,sed將在每輪script循環結束(How 'sed' works: Execution Cycle)時自動輸出模式空間中的內容。該選項禁止自動輸出動做,這種狀況下,只有顯式經過"p"命令來產生對應的輸出。
'-e SCRIPT'
'--expression=SCRIPT'
向SCRIPT中添加命令集,讓sed根據這些命令集來處理輸入流。(注:其實就是指定pattern和對應的command)
'-f SCRIPT-FILE'
'--file=SCRIPT-FILE'
指定包含command集合的script文件,讓sed根據script文件中的命令集處理輸入流。
'-i[SUFFIX]'
'--in-place[=SUFFIX]'
該選項指定要將sed的輸出結果保存(覆蓋的方式)到當前編輯的文件中。GNU sed是經過建立一個臨時文件並將輸出寫入到該臨時文件,而後重命名爲源文件來實現的。
該選項隱含了"-s"選項。
當到達了文件的尾部,臨時文件被重命名爲源文件的名稱。若是還提供了SUFFIX,則在重命名臨時文件以前,先使用該SUFFIX先修改源文件名,從而生成一個文件備份。
(注:臨時文件老是會被重命名爲源文件名稱,也就是說sed處理徹底結束後,仍使用源文件名的文件是sed修改後的文件。文件名中包含了SUFFIX的文件則是最原始文件的備份。例如源文件爲a.txt,sed -i'.log' SCRIPT a.txt
將生成兩個文件:a.txt和a.txt.log,前者是sed修改後的文件,a.txt.log是源a.txt的備份文件。)
規則以下:若是擴展名不包含符號"*",將SUFFIX被添加到原文件名的後面看成文件後綴;若是SUFFIX中包含了一個或多個字符"*",則每一個"*"都替換爲原文件名。這使得你能夠爲備份文件添加一個前綴,而不是後綴,甚至能夠將此備份文件放在在其餘已存在的目錄下。
若是沒有提供SUFFIX,源文件被覆蓋,且不會生成備份文件。
'-l N'
'--line-length=N'
爲"l"命令指定默認的換行長度。N=0意味着徹底不換行的長行,若是不指定,則70個字符就換行。
'--posix'
GNU 'sed' includes several extensions to POSIX sed. In order to simplify writing portable scripts, this option disables all the extensions that this manual documents, including additional commands. Most of the extensions accept 'sed' programs that are outside the syntax mandated by POSIX, but some of them (such as the behavior of the 'N' command described in *note Reporting Bugs::) actually violate the standard. If you want to disable only the latter kind of extension, you can set the 'POSIXLY_CORRECT' variable to a non-empty value.
'-b'
'--binary'
This option is available on every platform, but is only effective where the operating system makes a distinction between text files and binary files. When such a distinction is made--as is the case for MS-DOS, Windows, Cygwin--text files are composed of lines separated by a carriage return and a line feed character, and 'sed' does not see the ending CR. When this option is specified, 'sed' will open input files in binary mode, thus not requesting this special processing and considering lines to end at a line feed.
'--follow-symlinks'
該選項只在支持符號鏈接的操做系統上生效,且只有指定了"-i"選項時才生效。指定該選項後,若是sed命令行中指定的輸入文件是一個符號鏈接,則sed將對該符號連接的目標文件進行處理。默認狀況下,禁用該選項,所以不會修改連接的源文件。
'-r'
'--regexp-extended'
使用擴展正則表達式,而不是使用默認的基礎正則表達式。sed所支持的擴展正則表達式和egrep
同樣,使用擴展正則表達式顯得更簡潔,由於有些元字符不用再使用反斜線"\",但這是GNU擴展功能,所以應避免在可移植性腳本中使用。
'-s'
'--separate'
默認狀況下,sed會將命令行中指定文件的全部行看成一個長輸入流。此選項爲GNU sed擴展功能,指定該選項後,sed將認爲命令行中給定的每一個文件都是獨立的輸入流。所以此時範圍定址(如/abc/,/def/
)沒法跨越多個文件,行號也會在處理每一個文件時重置,"$"表明的是每一個文件的最後一行,"R"命令調用的文件將繞回到每一個文件的開頭。
'-u'
'--unbuffered'
使用盡可能少的空間緩衝輸入和輸出行。(該選項在某些狀況下尤其有用,例如輸入流的來源是"tail -f
時,指定該選項將能夠儘快返回輸出結果。)
'-z'
'--null-data'
'--zero-terminated'
以空串符號"\0"而不是換行符"\n"做爲輸入流的行分隔符。
若是未給定"-e"、"-f"、"--expression"或"--file"選項,則命令行中的第一個非選項參數將被認爲是SCRIPT被執行。
若是命令行中在上述選項後還加了任意參數,則都將被看成輸入文件。其中"-"表示的是標準輸入流。若是沒有給定任何輸入文件,則默認從標準輸入中讀取輸入裏流。
sed程序由一個或多個sed命令組成(注:請勿將sed這個工具理解爲sed命令,而應該看做是一個包含不少命令的程序,因此後文中"sed命令"表示的是sed中的子命令,而非sed自己這個命令),這些命令經過"-e"、"-f file"傳遞,或者當沒有指定這兩個選項時,經過第一個非選項參數傳遞。這些傳遞的命令集合稱爲sed的執行腳本(SCRIPT),命令的執行順序按照命令行中給定的順序依次執行。
在SCRIPT或SCRIPT-FILE中的多個命令可使用分號";"進行分隔,或者換行書寫。但有些命令,因爲它們的語法問題,致使不支持使用分號做爲命令分隔符,所以只能換行書寫,除非它們是SCRIPT或SCRIPT-FILE中的最後一個命令。容許命令的前面有空白字符。
每一個sed命令由地址或範圍地址,隨後緊跟單字符表明的命令名稱組成,其中地址部分能夠省略。
sed維護兩個數據緩衝空間:一直處於活動狀態的模式空間(pattern space)和輔助性的保持空間(hold space)。這兩個空間初始時都爲空。
sed經過執行下面的循環來操做輸入流中的每一行: 首先,sed讀取輸入流中的一行,移除該行的尾隨換行符,並將其放入到pattern space中。而後對pattern space中的內容執行SCRIPT中的sed命令,每一個sed命令均可以關聯一個地址:地址是一種條件判斷代碼,只有符合條件的行纔會執行相應的命令。當執行到SCRIPT的尾部時,除非指定了"-n"選項,不然pattern space中的內容將寫到標準輸出流中,並添加回尾隨的換行符。而後進入下一個循環開始處理輸入流中的下一行。
除非指定了特殊的命令(例如"-D"),不然pattern space中的內容在SCRIPT循環結束後會被刪除。可是hold space會保留其中的內容(參見sed命令'h', 'H', 'x', 'g', 'G'以獲知兩個buffer空間是如何互相移動數據的)。
(
注:也就是說,sed程序工做時包含內外兩個循環:內循環是SCRIPT循環,循環的過程是對模式空間中的內容執行SCRIPT中的命令集合,還包括一個隱含的自動輸出動做用於輸出模式空間的內容到標準輸出中;外循環是sed循環,讀取下一行到模式空間中,並執行SCRIPT循環,SCRIPT循環結束後再讀取下一行到模式空間中。使用編程結構描述,結構以下:
for ((line=1;line<=last_line_num;++line))
do
read $line to pattern_space;
while pattern_space is not null
do
execute cmd1 in SCRIPT;
execute cmd2 in SCRIPT;
……
auto_print;
remove_pattern_space;
done
done
通常狀況下,SCRIPT循環都只執行一輪就退出並進入外層sed循環,由於執行完一次SCRIPT循環, pattern space就清空了,但有些特殊命令(如"D"命令)會進入多行模式,使得SCRIPT循環結束 時將數據鎖在pattern space中不輸出也不清空,甚至在SCRIPT循環還沒結束時就強行進入下一 輪SCRIPT循環,在《sed & awk》一書中,這種行爲被稱之爲"回到SCRIPT的頂端"。其實就至關 於在上面的while循環結構中加上了"continue"關鍵字。此外還有命令(如"d")能夠直接退出 SCRIPT循環進入下一個sed循環,就像是在while循環中加上了"break"同樣。甚至還有直接退出 sed循環的命令(只有2個這樣的命令:"q"和"Q"),就像是加上了"exit"同樣。
auto_print
和remove_pattern_space
動做是隱含動做,分別稱之爲自動輸出和清空pattern space。"-n"選項禁用的自動輸出不是禁止隱含動做auto_print,而是讓其輸出空內容(它們是有區別的),並執行remove_pattern_space。只要沒有指定"-n"選項,在SCRIPT循環結束時必定輸出pattern space中的所有內容並清空pattern space,只不過某些特殊的命令能夠將pattern space中的內容鎖住使auto_print輸出空內容,也使得remove_pattern_space移除不了pattern space的內容。
之因此要特意指出"輸出空內容"並標明它和禁止輸出動做,是由於某些命令(如"a"、"i"、"c"命令)依賴於輸出流,沒有輸出動做就沒有輸出流,這些命令就沒法正確完成。
這幾個循環、幾個動做的細節很是重要,雖不影響後文單個選項或命令的理解,但若是將命令結合,有時候的結果極可能會出人意料,而sed難就難在命令的合理組合。例以下面的命令,"a"命令原本是要將xyz插入到aaa所在行後面的,但結果卻插在了aaa行的前面。
echo -e "aaa\nbbb" | sed '/aaa/{a\ > xyz > ;N}'
xyz
aaa
bbb
)
(注:sed SCRIPT中的命令由單字符表明的命令和地址組成,地址的做用是匹配當前正在處理的行,若是能匹配成功,就執行與該地址相關聯的命令。地址由定址表達式決定,定址的結果多是單行,多是一個範圍,省略定址表達式時表示全部行。)
可使用下面任意一種方式進行定址:
'NUMBER'
指定一個行號,sed將僅只匹配該行。(須要注意,除非使用了"-s"或"-i"選項,sed將對全部輸入文件的行連續計數。)
'FIRST~STEP'
這是GNU sed的功能,FIRST和STEP都是非負數。該定址表達式表示從第FIRST行開始,每隔STEP行就再取一次。也就是取行號知足「FIRST+(N*STEP)」 (其中N>=0)的行。所以,要選擇全部的奇數行,使用「1~2」;要從第2行開始每隔3行取一次,使用「2~3」;要從第10行開始每隔5行取一次,使用「10~5」;而「50~0」則表示只取第50行。
'$'
該符號匹配的是最後一個文件的最後一行,若是指定了"-i"或"-s",則匹配的是每一個文件的最後一行。
(注:總之,"$"匹配的是每一個輸入流的最後一行)
'/REGEXP/'
該定址表達式將選擇那些能被正則表達式REGEXP匹配的全部行。若是REGEXP中自身包含了字符"/",則必須使用反斜線進行轉義,即"\/"
。
空的正則表達式"//"表示引用最後一個正則表達式匹配的結果(命令"s"中的正則匹配部分若是是空正則"//",則同樣如此處理)。注意,正則表達式的修飾符(即下文中的"I"和"M",以及s命令中的修飾符"i"、"I"和"M")是在正則表達式編譯完成以後(注:進行數據匹配時)才生效的,所以,這些修飾符和空正則一塊兒使用時無效(注:會報錯,提示"cannot specify modifiers on empty regexp")。
(注:這裏的修飾符特指定址時可用的修飾符,即I和M。命令s也有修飾符"i"、"I"和"M"。
如:sed '/hold/Is//gogogo/g'
能成功,由於第一個定址正則表達式的修飾符"I"的對象是非空集"hold",第二個正則模式"//"沒有指定"i"、"I"或"M"修飾符,因此成功。但sed '/hold/Is//gogogo/gi'
會報錯,由於在正則編譯結束後還未開始進行匹配的時候,第二個正則表達式中的修飾符"i"的對象是空集。sed '/hold/I{//Mp}'
和sed '/hold/I{//Ip}'
也都是失敗的,緣由都是第二個正則的修飾符對象是空集。
'\%REGEXP%'
('%'可使用其餘任意單個字符替換。)
這和上一個定址表達式的做用是同樣的,只不過是使用符號"%"替換了符號"/"。當REGEXP中包含"/"符號時,使用該定址表達式就無需對"/"使用反斜線"\"轉義。但若是此時REGEXP中包含了"%"符號時,該符號須要使用"\"轉義。
總之,定址表達式中使用的分隔符在REGEXP中出現時都須要使用反斜線轉義。
'/REGEXP/I'
'\%REGEXP%I'
正則表達式的修飾符"I"是GNU的擴展功能,表示REGEXP在匹配時不區分大小寫。
'/REGEXP/M'
'\%REGEXP%M'
正則表達式的修飾符"M"是GNU的擴展功能,這可讓sed直接匹配多行模式下(multi-line mode)的位置。該修飾符使得正則表達式的元字符"^"和"$"匹配分別匹配每一個新行後的空字符和新行前的空字符。還有另外兩個特殊的字符序列(\`和\',分別是反斜線加反引號,反斜線加單引號),它們老是匹配buffer空間中的起始和結束位置。此外,元字符點"."不能匹配多行模式下的換行符。
(注:在單行模式下,使用M和不使用M是沒有區別的,但在多行模式下各符號匹配的位置將和一般的正則表達式元字符匹配的內容不一樣,各符號的匹配位置以下圖所示)
若是沒有給定地址,則會匹配輸入流中的全部行,若是給定了單地址的定址表達式,則這些行會在模式空間中被匹配條件進行匹配。
可使用逗號分隔的兩個地址表達式(ADDR1,ADDR2)來描述範圍地址。範圍地址表示從符合第一個地址條件的行開始匹配,直到符合第二個地址的行結束。
(注:須要注意,不管行是否符合地址匹配條件,它們都會被讀入pattern space,只不過讀入的行若是不符合地址匹配條件,將直接執行SCRIPT循環中的隱含動做,並結束SCRIPT循環繼續讀取輸入流的下一行)
若是第二個定址表達式是正則表達式REGEXP,將從符合第一個定址表達式的行開始逐行檢查,以匹配範圍定址的結束行:範圍定址的範圍老是會跨越至少兩行(固然,若是輸入流結束的情形除外)。
若是第二個定址表達式是一個小於或等於第一個表達式表明的行號值,則只會匹配第一個表達式搜索到的那一行。
(注:即若是範圍地址的結束行在起始行的前面,則只會匹配起始行。)
GNU sed還支持如下幾種特殊的兩地址格式,這些都是GNU的擴展功能:
'0,/REGEXP/'
使用行號0做爲起始地址也是支持的,就像此處的"0,/REGEXP/",這時sed會嘗試對第一行就匹配REGEXP。換句話說,"0,/REGEXP/"和"1,/REGEXP/"基本是相同的。但以行號0做爲起始行時,若是第一行就能被ADDR2匹配,範圍搜索當即就結束,由於第二個地址已經搜索到了;若是以行號1做爲起始行,會從第二行開始匹配ADDR2,直到匹配成功。
注意,0地址只有在這一種狀況下才是有意義的,實際上並無第0行,在其餘任什麼時候候使用行號0都會給出錯誤提示。
'ADDR1,+N'
匹配ADDR1和其後的N行。
'ADDR1,~N'
匹配ADDR1和其後的行直到出現N的倍數行,倍數可爲隨意整數倍。 (注:能夠是任意倍,只要N的倍數是最接近且大於ADDR1的便可。如ADDR1=1,N=3
匹配1到3行,ADDR1=5,N=4
匹配5-8行。而"1,+3"匹配的是第一行和其後的3行即1-4行。)
在地址的後面追加"!"符號指定反轉匹配的含義。意思是,若是"!"跟在範圍定址的後面,則那些匹配的行將不被選擇,而是不匹配的行被選擇。一樣能夠適用於單個定址甚至於有悖常理的空定址。
(注:例如,sed -n '3!p' INPUTFILE
,sed -n '3,5!p' INPUTFILE
,甚至是sed -n '!p' INPUTFILE
)
(注:
sed採用計數器計算,每讀取一行,計數器加1,直到最後一行。所以在讀到最後一行前,sed是不知道此次輸入流中總共有多上行,也不知道最後一行是第幾行。"$"符號表示最後一行,它只是一個特殊的標識符號。當sed讀取到輸入流的尾部時,sed就會爲該行打上該標記。沒法使用"$"參與數學運算,例如沒法使用$-1
表示倒數第二行,由於sed在讀到最後一行前,它並不知道這是倒數第二行,此時也還沒打"$"標記,所以$-1
是錯誤的定址表達式。另外一方面,在本譯文的最開始就說明了,sed只能經過一次輸入流,這意味着已經讀取過的行沒法再次被讀取,因此sed不提供往回取數據的定址表達式,上面的幾個定址表達式也確實證實了這一點。事實上,sed中根本就沒法使用"-"作減法或取負數,由於語法不支持。
範圍匹配的是pattern space中的內容,對於regexp1,regexp2
這樣的範圍定址天然容易理解,但若是是num1,num2
這樣的範圍定址,它可能和想象中的匹配方式不同。每讀取一行,sed內部的行號計數器都會"+1",因此行號值老是記錄在內存中。當進行行號匹配時,其本質是和內存中當前計數器的值進行匹配,若是當前計數器的值在範圍內,則能被匹配,不然沒法匹配。所以,從hold space回到pattern space的行或者被修改的行甚至是被刪除過的行,無論這一行的內容在pattern space中是否存在,只要當前計數器值在範圍內,就能匹配。例如多行模式:
echo -e "abc\nfgh" | sed -n 'N;1p'
該命令不輸出任何內容。雖然N讀取下一行後,pattern space兩行中的第一行一直原封不動的放在那,沒作任何處理,但"1p"根本就沒法匹配這一行,由於當前計數器的值爲2。
)。
(注:sed解析、編譯和匹配正則表達式的引擎和grep的引擎同樣,所以本文基本不作翻譯,能夠參考我對info grep的譯文:grep命令中文手冊。)
To know how to use 'sed', people should understand regular expressions ("regexp" for short). A regular expression is a pattern that is matched against a subject string from left to right. Most characters are "ordinary": they stand for themselves in a pattern, and match the corresponding characters in the subject. As a trivial example, the pattern
The quick brown fox
matches a portion of a subject string that is identical to itself. The power of regular expressions comes from the ability to include alternatives and repetitions in the pattern. These are encoded in the pattern by the use of "special characters", which do not stand for themselves but instead are interpreted in some special way. Here is a brief description of regular expression syntax as used in 'sed'.
'CHAR'
A single ordinary character matches itself.
'*'
Matches a sequence of zero or more instances of matches for the preceding regular expression, which must be an ordinary character, a special character preceded by '\', a '.', a grouped regexp (see below), or a bracket expression. As a GNU extension, a postfixed regular expression can also be followed by '*'; for example, 'a**' is equivalent to 'a*'. POSIX 1003.1-2001 says that '*' stands for itself when it appears at the start of a regular expression or subexpression, but many nonGNU implementations do not support this and portable scripts should instead use '\*' in these contexts.
'\+'
As '*', but matches one or more. It is a GNU extension.
'\?'
As '*', but only matches zero or one. It is a GNU extension.
'\{I\}'
As '*', but matches exactly I sequences (I is a decimal integer; for portability, keep it between 0 and 255 inclusive).
'\{I,J\}'
Matches between I and J, inclusive, sequences.
'\{I,\}'
Matches more than or equal to I sequences.
'\(REGEXP\)'
Groups the inner REGEXP as a whole, this is used to:
Apply postfix operators, like '\(abcd\)*'
: this will search for zero or more whole sequences of 'abcd', while 'abcd*'
would search for 'abc' followed by zero or more occurrences of 'd'. Note that support for '\(abcd\)*'
is required by POSIX 1003.1-2001, but many non-GNU implementations do not support it and hence it is not universally portable.
Use back references (see below).
'.'
Matches any character, including newline.
'^'
Matches the null string at beginning of the pattern space, i.e. what appears after the circumflex must appear at the beginning of the pattern space.
In most scripts, pattern space is initialized to the content of each line (*note How 'sed' works: Execution Cycle.). So, it is a useful simplification to think of '^#include' as matching only lines where '#include' is the first thing on line--if there are spaces before, for example, the match fails. This simplification is valid as long as the original content of pattern space is not modified, for example with an 's' command.
'^' acts as a special character only at the beginning of the regular expression or subexpression (that is, after '(' or '|'). Portable scripts should avoid '^' at the beginning of a subexpression, though, as POSIX allows implementations that treat '^' as an ordinary character in that context.
'$'
It is the same as '^', but refers to end of pattern space. '$' also acts as a special character only at the end of the regular expression or subexpression (that is, before ')' or '|'), and its use at the end of a subexpression is not portable.
'[LIST]'
'[^LIST]'
Matches any single character in LIST: for example, '[aeiou]' matches all vowels. A list may include sequences like 'CHAR1-CHAR2', which matches any character between (inclusive) CHAR1 and CHAR2.
A leading '^' reverses the meaning of LIST, so that it matches any single character not in LIST. To include ']' in the list, make it the first character (after the '^' if needed), to include '-' in the list, make it the first or last; to include '^' put it after the first character.
The characters '$', '*', '.', '[', and '\'
are normally not special within LIST. For example, '[\*]'
matches either '\' or '*'
, because the '\' is not special here. However, strings like '[.ch.]', '[=a=]', and '[:space:]' are special within LIST and represent collating symbols, equivalence classes, and character classes, respectively, and '[' is therefore special within LIST when it is followed by '.', '=', or ':'. Also, when not in 'POSIXLY_CORRECT' mode, special escapes like '\n' and '\t' are recognized within LIST. *Note Escapes::.
'REGEXP1\|REGEXP2'
Matches either REGEXP1 or REGEXP2. Use parentheses to use complex alternative regular expressions. The matching process tries each alternative in turn, from left to right, and the first one that succeeds is used. It is a GNU extension.
'REGEXP1REGEXP2'
Matches the concatenation of REGEXP1 and REGEXP2. Concatenation binds more tightly than '|', '^', and '$', but less tightly than the other regular expression operators.
'\DIGIT'
Matches the DIGIT-th '(...)' parenthesized subexpression in the regular expression. This is called a "back reference". Subexpressions are implicity numbered by counting occurrences of '(' left-to-right.
'\n'
匹配換行符。
'\CHAR'
Matches CHAR, where CHAR is one of '$', '*', '.', '[', '\', or '^'
. Note that the only C-like backslash sequences that you can portably assume to be interpreted are '\n' and '\\'
; in particular '\t' is not portable, and matches a 't' under most implementations of 'sed', rather than a tab character.
注意,sed支持的正則表達式是貪婪匹配的。例如,從行左向行右匹配時,若是知足匹配的匹配字符有多個,則會取最長的。(注:例如字符串'abbbc',給定正則表達式"ab*",知足該正則的字符串序列有"a","ab","abb"以及"abbb",因爲是貪婪匹配,它會取最長的,即"abbb")
示例:
'abcdef'
匹配字符串"abcdef"。
'a*b'
匹配0或多個a,但後面須要跟上字符b,例如'b'或'aaaaab'。
'a\?b'
匹配"b"或"ab"。
'a\+b\+'
匹配一個或多個a,且後面須要有一個或多個b,因此"ab"是最短的匹配序列,其餘的如'aaaab'、'abbbbb'或'aaaaaabbbbbbb'都能被匹配。
'.*'
'.\+'
都匹配全部字符;可是,第一個匹配任何字串(包含空字串),而第二個只匹配包含至少一個字符的字串(空格也是字符)。
(注:即第一種會匹配全部,包括null字符,第二種不能匹配null字串。例如:sed -n '/.*/p'
會匹配空行。sed -n '/.\+/p'
不會匹配空行,這是匹配非空行的一種簡便方法。)
'^main.*(.*)'
匹配以"main"開頭的行,且該行還包括括號。
'^#'
匹配以"#"開頭的行。
'\\$'
匹配以反斜線結尾的行。
'\$'
匹配包含美圓符號的行。
'[a-zA-Z0-9]'
在C字符集環境下,這匹配任意ASCII的字母和數字。
'[^ tab]\+'
(此處tab表明一個製表符)匹配不包含空格和製表符的行。一般用於匹配整個單詞。
'^\(.*\)\n\1$'
匹配相鄰兩行徹底相同的狀況。
'.\{9\}A$'
匹配9個任意字符,後面還帶一個字母A。
'^.\{15\}A'
匹配以任意15個字符開頭但第16個字符是A的行。
若是要掌握sed,下面幾個命令幾乎是必須掌握的。
'#'
不能跟在定址表達式後。
以"#"開頭的行表示註釋行。
若是要考慮可移植性,須要注意有些sed可能僅支持一條單行註釋,且此時"#"必須是SCRIPT中的第一個字符。
注意:若是SCRIPT中的前兩個字符是"#n",則表示強制開啓選項"-n",而非註釋。若是想要開啓一個註釋行來註釋以字母"n"開頭的字符串,那麼須要使用大寫字母N代替,或者"#"和"n"之間加上一個或多個空白字符。
'q [EXIT-CODE]'
該命令只能在單定址表達式下使用。
表示當即退出sed,而再也不處理後續的命令和輸入流。注意,退出sed前,當前pattern space中的內容會被輸出,除非使用了"-n"選項。返回一個退出狀態碼是GNU sed的擴展功能。(注:後文還有一個"Q"命令,和"q"命令做用相同,但退出sed前不輸出當前pattern space中的內容。)
'd'
刪除模式空間中的內容,並當即進入下一個sed循環(注:即以"break"的方式直接退出當前SCRIPT循環,並讀取輸入流中的下一行,再執行SCRIPT循環)。
'p'
輸出當前模式空間的內容(到標準輸出中)。該命令通常只和"-n"選項一塊兒使用。
'n'
輸出模式空間的內容(除非自動輸出功能被禁止),而後讀取下一行到模式空間中替換其中的內容。若是輸入流中沒有下一行供"n"讀取(注:即此前已經讀取了輸入流的最後一行),sed將直接退出,不會執行SCRIPT中後續的命令。
(
注:如下面兩個命令爲例:其中"i"命令爲插入字符串並輸出。
[root@xuexi ~]# echo -e "line1\nline2\nline3\nline4" | sed -n 'n;p;i aaa'
line2
aaa
line4
aaa
[root@xuexi ~]# echo -e "line1\nline2\nline3" | sed -n 'n;p;i aaa'
line2
aaa
第二個命令讀取了第三行到pattern space後執行n命令,但此時輸入流中已經沒有下一行供讀取,因而直接結束,鏈後續的"p"命令都沒有執行。
)
'{ COMMANDS }'
使用大括號將一系列命令組合成一個命令組。在某些時候特別有用,例如相對某一匹配行或某一範圍中的行作屢次處理時。
sed的"s"命令(該命令用於字符串替換)的語法格式爲"s/REGEXP/REPLACEMENT/FLAGS"
。"s"命令中的字符"/"能夠統一被替換成任意其餘單個字符(注:在定址正則表達式中斜槓也能夠被替換成其餘字符,但須要在第一個被替換字符前加上反斜線轉義,例如\%REGEXP%
,而s命令中替換時無需加反斜線)。若是斜線字符"/"(或被替換後的其餘字符)須要出如今REGEXP或REPLACEMENT中,必須使用反斜線進行轉義。
sed的"s"命令算的上是sed程序中最重要的命令,它有不少不一樣的選項。但它的基本概念很簡單:"s"命令使用REGEXP匹配pattern space中的內容,若是匹配成功,則匹配成功的那部分字符串被替換爲REPLACEMENT。
REPLACEMENT中可使用"\N"
(N是從1到9的整數)進行後向引用,所表明的是REGEXP第N個括號\(...\)
中匹配的內容。另外,REPLACEMENT中能夠包含未轉義的"&"符號,這表示引用pattern space中被匹配的整個內容(注:是pattern space中的全部匹配,不只僅只是括號的分組匹配)。最後,GNU sed爲REPLACEMENT還提供了一些"反斜線加單字母"的特殊字符序列,以下:
'\L'
將REPLACEMENTE轉換成小寫,直到遇到了"\U"或"\E"。
'\l'
將REPLACEMENTE中下一個字符轉換成小寫。
'\U'
將REPLACEMENTE轉換成大寫,直到遇到了"\L"或"\E"。
'\u'
將REPLACEMENT中下一個字符轉換成大寫。
'\E'
中止"\L"和"\E"開啓的大小寫轉換。
(
注:使用方法以下示例
shell> echo hello | sed 's/e/ \Uyour\Lname /'
h YOURname llo
)
當使用了"g"修飾符時,大小寫轉換不會從一個正則匹配事件擴展到另外一個正則匹配事件。例如,若是pattern space中的內容爲"a-b-",執行下面的命令:
s/\(b\?\)-/x\u\1/g
獲得的輸出爲"axxB"。當替換第一個"-"時,"\u"
隻影響"\1"
表明的空值。當替換"b-"時,因爲"\1"
表明的是"b",因此字符"b"會被替換爲"B",但添加到pattern space中的"x"仍然爲小寫。
另外一方面,"\l"
和"\u"
會影響REPLACEMENT中的空引用的後一個字符。例如:模式空間內容爲"a-b-"
,執行下面的命令:
s/\(b\?\)-/\u\1x/g
將使用"X"(大寫)替換"-",使用"Bx"替換"b-"。若是這樣的結果不是你想要的結果,能夠在此例中的"\1"
後加上"\E"
防止"x"被轉換。
若是想要在最後的REPLACEMENT中包含字面符號"\"
、"&"
或換行符,須要在REPLACEMENT中這些字符以前加上反斜線。
sed的"s"命令能夠接0或多個以下列出的修飾符(FLAGS):
'g'
使用REPLACEMENT替換全部被REGEXP匹配的內容,而非第一次被匹配到的。
(注:sed任何動做都是在pattern space進行的,字符串替換也如此。不加"g"修飾符時,將只pattern space中第一個被匹配到的內容,加上"g",將替換pattern space中全部被匹配到的內容,所以若是是多行工做模式,第二行或更多行只要能被匹配都會被替換。)
'NUMBER'
爲一個整數值N。表示只替換第N個被匹配到的內容。
注意:POSIX標準中沒有說明既指定"g"又指定"NUMBER"修飾符時會如何處理,而且當前各類sed的實現也沒有標準說法。對於GNU sed而言,定義以下:忽略第NUMBER個匹配前的全部匹配,而後從第NUMBER個匹配開始向後從新匹配並替換全部匹配成功的內容。
'p'
替換動做完成後打印新的模式空間中的內容。
注意:當既指定"p"又指定"e"命令時,它們的先後順序不一樣,會獲得兩種不一樣結果。通常來講,"ep"
這種順序獲得的結果多是所指望的結果,但另外一種順序"pe"
對調試頗有用。出於這個緣由,當前版本的GNU sed特意解釋了"p"命令在"e"命令前(或後)時,將在"e"命令生效前(或後)輸出內容,由於"s"命令的每一個修飾符通常都只展現一次結果。雖然這種行爲已經寫入文檔,但將來可能會改變。
'w FILE-NAME'
該子命令表示將模式空間的內容寫入到指定的文件FILE-NAME中。GNU sed支持兩個特殊的FILE-NAME:"/dev/stderr"
和"/dev/stdout"
,分別表示寫入到標準錯誤和標準輸出中。
'e'
該命令容許經過管道將shell命令的執行結果直接傳遞到pattern space中。當替換動做完成後,將搜索pattern space,發現的命令會被執行,而且執行結果覆蓋到當前pattern space中。它會禁用尾隨換行符,而且若是待執行命令中包含了NULL字符,該命令將不被定義爲命令,即表示它是普通字符而不是命令因此不執行。這是GNU sed的功能。
(
注:"s"命令的"e"修飾符彷佛只有搜索pattern space並找出其中的命令並執行的功能,沒有選項描述中第一句話所述的功能。但"e"命令有該功能,例如:
echo -e "good\nbad" | sed 'e echo haha'
haha
good
haha
bad
不討論e命令的用法,關於"s"命令的"e"修飾符的用法以下:
文件ttt.txt的內容以下:
[root@xuexi tmp]# cat ttt.txt
ls /tmp
haha
excute a command
將第二行的"haha"替換成一個命令後使用"e"修飾符,那麼在替換完成後會查找模式空間,若是找到了能夠執行的命令就執行。
[root@xuexi tmp]# sed 's/haha/du -sh \/tmp/ge' ttt.txt
ls /tmp # 注意這一行沒有執行
18M /tmp # 說明執行了du -sh /tmp的命令
excute a command
注意到第一行雖然也是能夠執行的命令,可是卻沒有執行,由於"e"是"s"命令的修飾符,須要成功匹配"haha"的行才能符合"e"修飾符。
注意模式空間中的內容是一行一行的,命令將把整行內容做爲命令行。例如,若是隻將excute替換成du -sh /tmp
,那麼模式空間中的內容將是"du -sh /tmp a command",它會對/tmp和當前目錄下的"a和"command"目錄進行"du -sh"統計,但極可能"a"或"command"目錄根本不存在,這時就會報錯。
[root@xuexi tmp]# sed 's%excute%du -sh /tmp%ge' ttt.txt
ls /tmp
haha
du: cannot access 'command': No such file or directory # 不存在command目錄因此錯誤
18M /tmp
4.0K a # 當前目錄下正好存在a目錄,因此統計了信息
而且若是替換後找到的命令不在行首(有前導空白沒有影響),將出現錯誤,由於是將整行做爲命令來執行的。
[root@xuexi tmp]# sed 's%command%du -sh /tmp%ge' ttt.txt
ls /tmp
haha
sh: excute: command not found
因此更保險的方法是對整行進行替換。
[root@xuexi tmp]# sed 's%^excute.*%du -sh /tmp%ge' ttt.txt
ls /tmp
haha
18M /tmp
固然,若是想要執行第一行的"ls /tmp"命令也很簡單,只需匹配這一行。也能夠在此命令上進行命令擴展。以下面將/tmp替換後,模式空間的內容是"ls -ld /tmp;du -sh /tmp",因此會執行這兩條命令。
[root@xuexi tmp]# sed 's!/tmp!-ld /tmp;du -sh /tmp!ge' ttt.txt
dr-xr-xr-x. 17 root root 8736768 Oct 25 14:11 /tmp
18M /tmp
haha
excute a command
sed '${p;s/.*/:>filename/e;d} filename'
)
'I'
'i'
這兩個修飾符做用相同,表示在REGEXP匹配的時候忽略大小寫。這是GNU擴展功能。
'M'
'm'
和前文定址表達式中所述的M修飾符做用一致。見M修飾符。
(注:除了上述明確的修飾符外,還有一個特殊的不算修飾符的符號"\",當它寫在"s"命令的REPLACEMENT的每一個行尾時,表示新轉義當前命令行的行尾,也就是開啓一個新行。例如:
echo 'abcdef' | sed 's/^.*$/\ &\ /'
這表示在abcdef這一行內容先後分別加一個空行,也就是將abcdef這一行嵌入到空行中間。因此可將其理解爲換行符"\n",但必須注意,若是轉義新行後有內容,則此新行必須再次使用反斜線終止該行,由於它的行尾已經被轉義了。例如:
echo 'abcdef' | sed 's/^.*$/\ &\ xyz\ /'
echo 'abcdef' | sed 's/^.*$/&\ /'
其實我的以爲使用"\n"方便多了,即容易理解又容易控制。例如上面最後一個命令改成:echo 'abcdef' | sed 's/^.*$/&\n/'
。在後文的好幾個例子中都是用了轉義行尾的技巧,因此此處提上一提。
)
雖然可能用的不如前面所述的命令頻繁,但這些小命令有時候很是有用。
'y/SOURCE-CHARS/DEST-CHARS/'
(y命令中的斜線"/"能夠統一被替換成其餘單個字符。)
轉換pattern space中能被SOURCE-CHARS匹配的字符爲DEST-CHARS,且是一一對應地轉換。
斜線"/"(或被替換爲的其餘字符)、反斜線"\"和換行符能夠出如今SOURCE-CHARS或DEST-CHARS中,但須要使用反斜線進行轉義。(轉義後的)SOURCE-CHARS和DEST-CHARS中字符的數量必須徹底相同。
'a\'
'TEXT'
是GNU sed的擴展功能,該命令能夠在兩個地址格式的定址表達式後使用。
隊列化該命令後的文本內容(最後一個反斜線"\"是文本結束符,在輸出時該符號會被移除),並在當前SCRIPT循環結束時輸出,或從輸入流中讀取下一行時輸出。(注:本質是:只要"有讀取下一行"的動做就會觸發隊列化內容的輸出,不止是"a"、還有"i"和"c"以及"r"等,另外,除了sed循環的第一個動做能夠讀取下一行,命令"N"和"n"都會讀取下一行,所以它們也會觸發這些隊列化內容的輸出)
輸入的文本內容中若是要出現反斜線字符,須要進行轉義,即"\\"
。
做爲一個GNU擴展功能,若是命令"a"和換行符之間存在非空白字符"\"序列,則此反斜線會開啓新行,而且反斜線後的第一個非空白字符做爲下一行的第一個字符。這一樣適用於下面的"i"和"c"命令。
(注:命令"a"爲尾部追加插入,其動做是將輸入文本隊列化在內存中,它不會進入模式空間,而是隱含動做自動輸出模式空間內容時,在半路追上stdout並將隊列化的文本追加到其尾部,並同時輸出。下面的"i"和"c"命令所操做都是stdout,都不會進入pattern space。也就是說,這3個命令操做的文本內容不受任何sed其餘選項和命令控制。如"-n"選項沒法禁止該輸入,由於"-n"禁止的是pattern space的自動輸出,同時,若是SCRIPT中這3個命令後還有後續的命令序列,則這些命令也沒法匹配和操做這些文本內容,例如"p"命令不會輸出這些隊列化文本,由於它不是pattern space中的內容。
這是sed程序中少見的幾個在pattern space外處理數據的動做。具體用法以下兩個示例:
[root@xuexi tmp]# cat set2.txt
carrot
cookiee
gold
[root@xuexi tmp]# sed '/carrot/a\iron\nsteel' set2.txt # 換行插入
carrot
iron
steel
cookiee
gold
[root@xuexi tmp]# sed '/carrot/a\iron\ # iron做爲第一行,其後跟\繼續輸入下一行
> steel' set2.txt # 直到遇到結束引號,隊列化文本才結束 carrot iron steel cookiee gold
)
'i\'
'TEXT'
是GNU的擴展功能,該命令能夠在兩個地址格式的定址表達式後使用。
當即輸出該命令後的文本(除了最後一行,每一行都以反斜線"\"結尾,但在輸出時會自動移除)。
(注:"i"命令同"a"命令同樣,只不過是在stdout流的頭部加上隊列化的文本,並同時輸出。它一樣不進入pattern space,操做的也是stdout流)
'c\'
'TEXT'
刪除從模式空間輸出的內容,並在最後一行處(若是沒有指定定址選項則每一行)輸出"c"命令後隊列化文本(除了最後一行,每行都以"\"結尾,輸出時會移除)。該命令處理完後會開始新的sed循環,由於模式空間的內容將會被刪除。
(注:"c"即change,表示使用隊列化文本修改輸出流,雖然說是修改,但其實是替換模式空間的輸出流並輸出。它和"i"、"a"兩個命令不同,它"冒充"了輸出流輸出後就當即進入下一個sed循環,使得SCRIPT中後續的命令不會執行,而"i"、"a"則不會退出循環,而是會繼續回到SCRIPT循環中執行後續的命令)
(注:雖然這3個命令用的遠不如"s"命令頻繁,但我我的認爲,理解了這3個命令,就理解了大半sed的工做機制。)
'='
是GNU擴展功能,能夠在兩個地址格式的定址表達式後使用。
該命令用於輸出當前正在處理的輸入流中的行號(行號後會加上尾隨換行符)。
(注:這是將sed內部保存的行號計數器的值輸出,是內存中的內容,所以不進入pattern space,不受"-n"選項影響。)
'l N'
將模式空間中的內容以一種明確的形式打印:非打印字符(和"\"字符)被打印成C語言風格的轉義形式;長行被分割後打印,使用"\"字符來表示分割的位置;每一行的結尾被標記上"$"符。
N指定了行分割時指望的行長度(即多少字符換行),長度指定爲0表示不換行。若是忽略N,則使用默認長度(默認70)。N參數是GNU sed的擴展功能。
'r FILENAME'
GNU擴展功能,該命令接受兩個地址的定址表達式。
讀取filename中的內容並按行隊列化,而後在當前SCRIPT循環的最後或者讀取下一行前插入到output流中。注意,若是filename沒法被讀取,它將被當成一個空文件而不會產生任何錯誤提示。
做爲GNU sed的擴展功能,支持特殊的filename值:/dev/stdin,表示從標準輸入中讀取內容。
(注:
"r"命令的功能和"a"命令的做用是徹底同樣的,連工做機制都徹底相同,除了"r"命令是從文件中讀取隊列化文本,而"a"讀取的是命令行中提供的。
從"r"命令以後到換行符或sed的引號之間的全部內容都做爲"r"的文件參數。所以r命令以後若是還有命令,應當分行寫。
"r"命令是一次隊列化filename中的全部行,後文還有一個"R"命令是每次隊列化filename中的一行,它們都受輸出流的影響,只要有輸出流就追加到輸出流中,並開始下一輪的隊列化過程。並不是sed每從輸入流中讀取一行就隊列化一次)
)
'w FILENAME'
將模式空間的內容寫入到filename中,做爲一個GNU sed擴展,它支持兩種特殊的filename值:/dev/stderr和/dev/stdout,分別表示將模式空間的內容寫到標準錯誤和標準輸出中。
在輸入流的第一行被讀入前,filename文件將被建立(或截斷,即清空);全部引用相同filename的"w"命令(包括替換命令"s"後的"w"修飾符)不會先關閉filename文件再打開該文件來寫入。
(注:
)
'D'
若是pattern space中未包含換行符,則像"d"命令中提到的同樣進入下一個sed循環。不然刪除pattern space中第一個換行符以前的全部內容,並從新回到SCRIPT的頂端從新對pattern space中剩下的內容進行處理,而不會讀取輸入流中的下一行。
(注:換句話說,D命令老是刪除pattern space中第一個換行符前的內容,若是刪除後pattern space中還有內容剩下,則直接回到SCRIPT頂端從新對剩下的這部份內容執行命令,即以"continue"的方式強制進入下一個SCRIPT循環,若是pattern space中沒有剩下內容,則直接退出當前SCRIPT循環,並進入下一個sed循環。)
'N'
在當前pattern space中的尾部加上一個換行符,並讀取輸入流的下一行追加到換行符後面。若是輸入流中沒有下一行供"N"讀取,則直接退出sed循環。
(注:不管是sed循環的第一步、仍是n命令或是N命令,它們讀取下一行到pattern space時總會移除行尾換行符。但N命令在讀取下一行前在當前pattern space的尾部加上了一個"\n",這使得pattern space中有了兩行,若是一個SCRIPT中有屢次N命令還可能有更多行。所以N命令是sed進入多行工做模式的方法。但要注意,雖然稱爲多行模式,但在pattern space中加入換行符並不是真的換行,在pattern space中還是一行顯示的,由於它能被匹配到,且在計算字符數量時會被計入,它僅僅只是一個特殊符號,只不過在輸出時會換行顯示)
'P'
輸出pattern space中第一個換行符"\n"前面的內容。
(注:即輸出pattern space中的第一行)
'h'
使用pattern space中的內容替換hold space中的內容。 (注:替換後,pattern space內容仍然保留,不會被清空)
'H'
在hold space的尾部追加一個換行符,並將pattern space中的內容追加到hold space的換行符尾部。
(注:追加後,pattern space內容仍然保留,不會被清空)
'g'
使用hold space中的內容替換pattern space中的內容。
'G'
在當前pattern space的尾部追加一個換行符,並將hold space中的內容追加到pattern space的換行符尾部。
(注:這是另外一個進入多行模式空間的方法。這就有了一個特殊的用法,sed 'G' filename
能夠在filename的每一行後添加一個空行,這也是不少人產生誤解的地方,認爲hold space的初始內容爲空行,追加到pattern space就成了空行。其實並不是如此,由於不管是pattern space仍是hold space在初始時都是空的,G命令在pattern space的尾部加上了換行符,並將hold space中的空內容追加到換行符後,使得這成了一個空行。)
'x'
交換pattern space和hold space的內容。
在大多數狀況下,使用這些命令意味着你可能可使用像"awk"或"perl"等的工具更好地達到目的。可是偶爾有人會堅持使用'sed',這些命令可能會使得腳本變得很是複雜。
(注:雖然說標籤功能不多使用,但有時候確實很是有用,它在sed腳本內部實現了簡單的編程結構體)
':LABEL'
不可接在定址表達式後。
設置一個分支標籤LABEL供"b"、"t"或"T"(注:"T"命令在後文的GNU sed擴展中說明)命令跳轉。除此功能,無任何做用。
(注:冒號和LABEL之間不能有空格,雖然原文中有空格,但這是錯誤的)
'b LABEL'
無條件跳轉到標籤LABEL上。若是LABEL參數省略,則跳轉到SCRIPT的尾部準備進入下一個sed循環,即跳轉到隱含動做auto_print
的前面。
't LABEL'
若是對最近讀入的行有"s"命令的替換成功了,則跳轉到LABEL標籤處。若是LABEL參數省略,則跳轉到SCRIPT的尾部準備進入下一個sed循環,即跳轉到隱含動做auto_print
的前面。(注:該命令和"T LABEL"相反,"T"是在"s"替換不成功時跳轉到指定標籤。)
(注:另外,"t"的跳轉條件是有"s"命令替換成功,不是最近一個"s"命令替換成功,因此若是有多個"s"命令,即便"t"命令最近的"s"命令不成功,則仍會跳轉,直到一個跳轉循環內都沒有替換成功的才終止跳轉。例如:
`echo 'abcdef' | sed ':x;s/haha/yyy/;s/def/haha/;s/yyy/zzz/;tx'`
abczzz
第一輪,被讀取的輸入行在第一個"s"和第3個"s"命令失敗,但第二個成功,因此仍會執行跳轉,因而進入第二輪。在第二輪,第一個"s"和第三個"s"替換成功,因此繼續跳轉,因而進入第三輪。在第三輪中,全部"s"都失敗,因此再也不跳轉。
)
(注:篇幅緣由,使用一個示例簡單解釋一下標籤和跳轉命令的使用。
auto_print
的前面。假設有以下test.txt文件,該文件中空白處都是一個個的空格。目的是多個連續的空格替換成圓圈"○",有幾個空格就替換成幾個圓圈,但初始時只有一個空格的不進行替換(即第一行的第一個空格和最後一行的最後一個空格不替換)。
[root@xuexi ~]# cat test.txt
sleep sleep
sleep sleep
sleep sleep
sleep sleep
sleep sleep
sleep sleep
sleep sleep
可使用下面簡單的命令來實現:
[root@xuexi ~]# sed 's/ /○○/g;s/○ /○○/g' test.txt
sleep○○○○○○○sleep
○○sleep○○○○○○sleep
○○○sleep○○○○○sleep
○○○○sleep○○○○sleep
○○○○○sleep○○○sleep
○○○○○○sleep○○sleep
○○○○○○○sleep sleep
若是使用標籤功能,就能夠變相地實現sed命令組的循環和條件判斷功能。例如使用"b"標籤跳轉功能來實現,語句以下:
[root@xuexi ~]# sed '
:replace;
s/ /○○/;
/ /b replace;
s/○ /○○/g' test.txt
該語句首先定義了一個標籤replace
,在其後是第一個要執行的命令,做用是將兩個空格替換成兩個圓圈的"s"命令,隨後是"b"標籤跳轉語句,表示若是行中有兩個連續的空格,就使用"b"命令跳轉到replace
標籤處,因而再次執行"s"命令,替換結束後再次回到/ /b replace
命令,因而這樣就能夠對每一輸入行實現循環替換動做,直到最後行中沒有連續空格。但此時可能還會有一個多餘的空格跟在圓圈後面,因而就能知足s/○ /○○/g
命令的替換條件。
一樣,還可使用"t"標籤完成上述要求。
[root@xuexi ~]# sed '
:replace;
s/ /○○/;
t replace;
s/○ /○○/g' test.txt sleep○○○○○○○sleep ○○sleep○○○○○○sleep ○○○sleep○○○○○sleep ○○○○sleep○○○○sleep ○○○○○sleep○○○sleep ○○○○○○sleep○○sleep ○○○○○○○sleep sleep
其實"t"標籤更可能用於實現像shell中case的條件判斷功能。例如:
sed '{ s/abc/ABC/; t; s/opq/OPQ/; t; s/xyz/XYZ/}' filename
這表示若是能替換什麼成功,就跳轉到尾部結束,是多選一的狀況。
)
這些命令是GNU sed特有的命令,所以必須謹慎使用它們,而且sed腳本沒有可移植性的要求。
'e [COMMAND]'
該命令容許將shell命令行的輸出結果插入到pattern space中。不給定參數COMMAND時,"e"命令將搜索pattern space並執行發現的命令,並將命令的執行結果替換到pattern space中(注:相似於shell下的命令替換功能)。
若是指定了"COMMAND"參數,"e"命令將解析它並認爲它是一個shell命令,並在(子)shell環境下執行後將結果發送到輸出流中。
不管是否指定了COMMAND參數,若是待執行命令中包含了空字符,則都不被認爲是一個命令。
注意,不像"r"命令,"e"命令的結果是當即輸出的;而"r"命令須要延遲到當前SCRIPT循環結束時纔會輸出。
'F'
輸出當前處理的輸入流的文件名(輸入完後會換行)。
'L N'
這是一個失敗的GNU擴展功能,無視。
'Q [EXIT-CODE]'
該命令只能在單個定址表達式下使用。
該命令和命令"q"相同,除了它不會輸出pattern space中的內容(注:"q"命令在退出前會輸出當前pattern space中的內容)。一樣,像"q"那樣能夠提供一個退出狀態碼。
該命令很是有用,由於要完成這個顯而易見的簡單功能的惟一替代方法是使用'-n'選項(這可能會使腳本沒必要要地複雜化)或使用如下代碼片斷,但這會浪費時間讀取整個文件且沒有任何效果可見:
(注:即便使用了"-n"選項,性能也不如"q"和"Q",由於它仍會讀完整個輸入流,而"q | Q"是當即退出sed程序。)
:eat
$d Quit silently on the last line
N Read another line, silently
g Overwrite pattern space each time to save memory
b eat
'R FILENAME'
每次讀取FILENAME中的一行並隊列化在內存中,其他的像"r"選項同樣,在每一個SCRIPT循環結束時追上stdout流並追加在其尾部。若是FILENAME無可讀取或到達了文件尾部,將不會給出任何報錯信息。
和"r"命令同樣,它也支持特殊的FILENAME值:/dev/stdin,表示從標準輸入中讀取數據。
(注:"R"命令是每次隊列化filename中的一行,而"r"命令是一次隊列化filename中的全部行。它們都受輸出流的影響,有一次輸出就追加到輸出流中,並開始下一輪的隊列化過程。並不是sed每從輸入流中讀取一行就隊列化一次)
'T LABEL'
和"t LABEL"相反,該命令是僅當"s"命令未完成替換時跳轉到標籤LABEL。若是LABEL參數省略,則跳轉到SCRIPT的尾部準備進入下一個sed循環,即跳轉到隱含動做auto_print
的前面。
'v VERSION'
該命令除了讓sed程序在不支持GNU sed功能的時候失敗不作任何事。此外,能夠指定一個版本號,表示你的sed腳本必需要高於此版本的sed程序才能運行,例如指定"4.0.5"。默認指定的版本號爲"4.0",由於這是第一個支持該命令的版本。
'W FILENAME'
將pattern space中第一個換行符以前的內容寫入到filename中。除此以外,和"w"命令徹底相同。
'z'
該命令用於清空pattern space。能夠認爲它等價於"s/.*//"
,但卻效率更高,且能夠在輸入流中存在無效多字節序列(例如UTF-8時,一個字符佔用多個字節)的狀況下工做。POSIX要求這樣的序列沒法使用"."進行匹配,所以沒有任何方法既能夠清空sed的buffer空間,又能保證sed腳本的可移植性。
直到目前位置,咱們只遇到了"\^"
格式的轉義,它告訴sed不要將脫字符"^"解釋爲特殊元字符,而是解釋爲一個普通的字面符號。再例如,"\*"
匹配的是單個*
字符,而不是匹配0或多個反斜線字符。
本小節介紹另外一種類型的轉義。以下:
'\a'
匹配一個BEL字符,即一個"蜂鳴警告"(ASCII 7)。
'\f'
匹配一個分頁符(ASCII 12)。
'\n'
匹配一個換行符(ASCII 10)。
'\r'
匹配一個回車符(ASCII 13)。
'\t'
匹配一個水平製表符(ASCII 9)。
'\v'
匹配一個垂直製表符(ASCII 11)。
'\cX'
Produces or matches 'CONTROL-X', where X is any character. The precise effect of '\cX' is as follows: if X is a lower case letter, it is converted to upper case. Then bit 6 of the character (hex 40) is inverted. Thus '\cz' becomes hex 1A, but '\c{' becomes hex 3B, while '\c;' becomes hex 7B.
'\dXXX'
匹配十進制ASCII值爲XXX的字符。
'\oXXX'
匹配八進制ASCII值爲XXX的字符。
'\xXX'
匹配十六進制ASCII值爲XX的字符。
'\b'
(回退刪除鍵)沒法實現,由於"\b"
用於匹配單詞的邊界。
如下轉義序列匹配特殊的字符類,且只在正則表達式中有效:
'\w'
匹配任意單詞組成字符。單詞的組成成分包括:字母、數字和下劃線。其餘任意字符都不是單詞的一部分。
'\W'
匹配人非單詞字符。即匹配除了字母、數字和下劃線的任意字符。
'\b'
匹配單詞的邊界位置。
'\B'
匹配非單詞的邊界位置。
"\`"
此爲sed獨有正則表達式。匹配pattern space中的開頭。在多行模式下,這和"^"
是不一樣的。
"\'"
此爲sed獨有正則表達式。匹配pattern space中的尾部。在多行模式時,這和"$"
是不一樣的。
(注:關於最後兩個正則表達式,它們是sed纔可使用的。在多行模式下,它們和"^"、"$"的區別,見M修飾符。)
這裏有一些sed示例腳本,能夠引導你成爲大師級的sed使用者。
(注:說實話,下面的17個示例看的我懷疑人生,真的沒想到sed功能強大如斯,但也難的想死,這絕對是骨灰級玩家才能玩的)
一些吸引人的示例:
Centering lines
Increment a number
Rename files to lower case
Print bash environment
Reverse chars of lines
模仿一些標準的工具:
tac : Reverse lines of files
cat -n : Numbering lines
cat -b : Numbering non-blank lines
wc -c : Counting chars
wc -w : Counting words
wc -l : Counting lines
head : Printing the first lines
tail : Printing the last lines
uniq : Make duplicate lines unique
uniq -d: Print duplicated lines of input
uniq -u: Remove all duplicated lines
cat -s : Squeezing blank lines
該腳本將全部行都劃分中心線,每行最多80個字符,中心線的位置指定爲第40個字符處。若是要改變行寬度,則下面的'\{...\}'
中的數字必須更改,同時須要在腳本的第一段中添加或刪除適當個數的空格。
注意此處是如何使用buffer空間的命令來分隔兩部分的,這是一個通用技巧。
(注:若是行的內容本來就超過81個字符,則這些行的行尾會被截斷)
#!/usr/bin/sed -f
# Put 80 spaces in the buffer
1 {
x
s/^$/ /
s/^.*$/&&&&&&&&/
x
}
# del leading and trailing spaces
y/tab/ / s/^ *//
s/ *$//
# add a newline and 80 spaces to end of line
G
# keep first 81 chars (80 + a newline)
s/^\(.\{81\}\).*$/\1/
# \2 matches half of the spaces, which are moved to the beginning
s/^\(.*\)\n\(.*\)\2/\2\1/
(注:今後腳本可知,雖在pattern space中加上了換行符,但其實不是真的換行,它在pattern space中還是一行,只不過在輸出時換行符使結果換了行。所以,N命令進入多行模式也是同樣的。同時,換行符是一個字符,能被匹配,也能被替換掉)
該腳本是sed中少數幾個演示瞭如何作算術計算的示例。這確實頗有可能,但只能手動實現。
爲了增加一個數值,只須要爲該數的末尾加上1,並替換原末尾便可。但有一種例外:當末尾數位9時,其前面一位也必須增加,若是這仍是9,則還需增加,直到無需進位時才結束。
Bruno Haible提供的解決方案很是聰明,由於它只使用了一個buffer空間。它是這樣工做的:將末尾9替換成下劃線,而後使用多個"s"命令增加最後一個數字(下劃線前面的數字也屬於最後一個數字),最後將全部的下劃線替換爲0。
(注:下面的td
和tn
是t d
和t n
的簡寫。)
#!/usr/bin/sed -f
/[^0-9]/ d
# replace all trailing 9s by _ (any other character except digits, could
# be used)
:d
s/9\(_*\)$/_\1/
td
# incr last digit only. The first line adds a most-significant
# digit of 1 if we have to add a digit.
s/^\(_*\)$/1\1/; tn
s/8\(_*\)$/9\1/; tn
s/7\(_*\)$/8\1/; tn
s/6\(_*\)$/7\1/; tn
s/5\(_*\)$/6\1/; tn
s/4\(_*\)$/5\1/; tn
s/3\(_*\)$/4\1/; tn
s/2\(_*\)$/3\1/; tn
s/1\(_*\)$/2\1/; tn
s/0\(_*\)$/1\1/; tn
:n
y/_/0/
(注:例如爲該sed腳本傳遞一個個位數8,因爲該數據是以8結尾的,所以將一直執行到s/8\(_*\)$/9\1/;tn
將8增加爲9,並跳轉到標籤n,標籤n後的命令不用對其處理,因此直接輸出9。假如傳遞的數字是3999,則:d;s/9\(_*\)$/_\1/;td
將一直循環,首先替換成"399_"
,再循環一次替換成"39__"
,最後一次循環替換成"3___"
,因爲此時結尾沒9了,且是以3結尾,因而繼續向下直到s/3\(_*\)$/4\1/; tn
,它會將"3___"
替換成"4___"
,並跳轉到標籤n,y/_/0/
將把下劃線替換成0獲得4000,最後輸出結果爲4000。
這是一個很是奇怪的sed應用。轉換文件名文本,並轉換其爲shell命令,而後將它們提供給shell。
該應用的主體是其中的sed腳本部分,它會從新將名稱的小寫字母映射爲大寫,甚至還檢查從新映射後的名稱是否和原始名稱相同。注意腳本是如何使用shell變量和正確的引號進行參數化的。
#! /bin/sh
# rename files to lower/upper case...
#
# usage:
# move-to-lower *
# move-to-upper *
# or
# move-to-lower -R .
# move-to-upper -R .
#
help()
{
cat << eof
Usage: $0 [-n] [-R] [-h] files...
-n do nothing, only see what would be done
-R recursive (use find)
-h this message
files files to remap to lower case
Examples:
$0 -n * (see if everything is ok, then...)
$0 *
$0 -R .
eof
}
apply_cmd='sh'
finder='echo "$@" | tr " " "\n"'
files_only=
while :
do
case "$1" in
-n) apply_cmd='cat' ;;
-R) finder='find "$@" -type f';;
-h) help ; exit 1 ;;
*) break ;;
esac
shift
done
if [ -z "$1" ]; then
echo Usage: $0 [-h] [-n] [-r] files...
exit 1
fi
LOWER='abcdefghijklmnopqrstuvwxyz'
UPPER='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
case `basename $0` in
*upper*) TO=$UPPER; FROM=$LOWER ;;
*) FROM=$UPPER; TO=$LOWER ;;
esac
eval $finder | sed -n ' # remove all trailing slashes s/\/*$// # add ./ if there is no path, only a filename /\//! s/^/.\// # save path+filename h # remove path s/.*\/// # do conversion only on filename
# (注:注意這裏是如何引用變量的。本質是將其還原爲shell的token,因此遇到變量,就在其前和後使用引號將其暴露給shell) y/'$FROM'/'$TO'/ # now line contains original path+file, while # hold space contains the new filename x # add converted file name to line, which now contains # path/file-name\nconverted-file-name G # check if converted file name is equal to original file name, # if it is, do not print anything /^.*\/\(.*\)\n\1/b # escape special characters for the shell s/["$'\\]/\\&/g
# now, transform path/fromfile\n, into
# mv path/fromfile path/tofile and print it
s/^\(.*\/\)\(.*\)\n\(.*\)$/mv "\1\2" "\1\3"/p
' | $apply_cmd
該腳本去除了'set'命令中的shell function的定義。
#!/bin/sh
set | sed -n '
:x
# if no occurrence of "=()" print and load next line
/=()/! { p; b; }
/ () $/! { p; b; }
# possible start of functions section
# save the line in case this is a var like FOO="() "
h
# if the next line has a brace, we quit because
# nothing comes after functions
n
/^{/ q
# print the old line
x; p
# work on the new line now
x; bx
'
該腳本可用反轉行中字符的位置,它使用了一次性移動兩個字符的技術,所以它的速度比直觀看上去的更快。
注意標籤訂義語句前面的"tx"命令,它用來重置"t"標籤跳轉的條件,省得被第一個"s"命令影響而跳轉。
#!/usr/bin/sed -f
/../! b
# Reverse a line. Begin embedding the line between two newlines
# (注:下面的"s"命令使用了轉義行尾的技巧,其實和"sed /^.*$/\n&\n/"是等價的)
s/^.*$/\
&\
/
# Move first character at the end. The regexp matches until
# there are zero or one characters between the markers
tx
:x
s/\(\n.\)\(.*\)\(.\n\)/\3\2\1/
tx
# Remove the newline markers
s/\n//g
(注:例如echo abcde | sed -f file.sed
,傳入的是"abcde",首先判斷它不是單字符,不然將跳轉到尾部,而後將"abcde"嵌入到兩個相同的特殊字符中,此處採用的是嵌入到兩空行中,爲了解釋方便,假設這裏的特殊字符爲"#"符號,因而獲得"#abcde#",隨後一個"t x"命令用於重置跳轉條件,避免第一個s命令的成功狀態影響跳轉條件。再執行第二個s命令,該命令將兩個"#"先後的字符反轉,第一輪循環獲得"e#dcb#a",隨後跳轉再次替換,獲得"ed#c#ba",再次跳轉,但這一次替換不成功。最後將全部的"#"刪除,即獲得最終結果"edcba")
這是一個沒什麼用但卻頗有意思的腳本,是模仿unix命令。就像tac
同樣工做。
注意,非GNU sed執行該腳本時很容易內存溢出。
#!/usr/bin/sed -nf
# reverse all lines of input, i.e. first line became last, ...
# from the second line, the buffer (which contains all previous lines)
# is *appended* to current line, so, the order will be reversed
1! G
# on the last line we're done -- print everything
$ p
# store everything on the buffer again
h
該腳本和cat -n
的功能同樣。固然,這個腳本沒什麼用,兩個緣由:第一,有人使用C語言實現了該功能,第二,下面這個shell腳本能夠實現相同的目的卻更快。
#! /bin/sh
sed -e "=" $@ | sed -e ' s/^/ / N s/^ *\(......\)\n/\1 / '
它首先使用sed來輸出行號,而後經過"N"兩行一分組並進行調整。固然,這個腳本並無下面這個腳本的引導意義多。
這個算法同時使用了兩個buffer空間,使得每一行能夠儘快被打印出來而後丟棄。而行號被分離,使得數字改變後能夠放入一個buffer空間,而沒有改變的數字在另外一個buffer空間;改變的數字使用一個"y"命令來修改。因而行號被存儲在hold space,在下一個迭代中被使用上。
#!/usr/bin/sed -nf
# Prime the pump on the first line
x
/^$/ s/^.*$/1/
# Add the correct line number before the pattern
G
h
# Format it and print it
s/^/ /
s/^ *\(......\)\n/\1 /p
# Get the line number from hold space; add a zero
# if we're going to add a digit on the next line
g
s/\n.*$//
/^9*$/ s/^/0/
# separate changing/unchanged digits with an x
s/.9*$/x&/
# keep changing digits in hold space
h
s/^.*x//
y/0123456789/1234567890/
x
# keep unchanged digits in pattern space
s/x.*$//
# compose the new number, remove the newline implicitly added by G
G
s/\n//
h
模範的是"cat -b",它不會計算空行的行號。
在此腳本中和上一個腳本相同的部分沒有給出註釋,於此處可知,對於sed腳原本說,給註釋是多麼重要的事。
#!/usr/bin/sed -nf
/^$/ {
p
b
}
# Same as cat -n from now
x
/^$/ s/^.*$/1/
G
h
s/^/ /
s/^ *\(......\)\n/\1 /p
x
s/\n.*$//
/^9*$/ s/^/0/
s/.9*$/x&/
h
s/^.*x//
y/0123456789/1234567890/
x
s/x.*$//
G
s/\n//
h
該腳本展現了sed另外一種作數學計算的方式。此處咱們必須能夠增大到一個極大的數值,所以要成功實現這一點可能不那麼容易。
解決的方法是將數字映射爲字母,這是sed的一種算盤功能實現。"a"表示個位數,"b"表示十位數,以此類推。如前所見,咱們仍然在hold space中保存總數。
在最後一行上,咱們將算盤上的值轉換爲十進制數字。爲了能適應多種狀況,這裏使用了循環而不是一大堆的"s"命令:首先轉換個位數,而後從數值中移除字母"a",而後滾動字母使得各個字母都轉換成對應的數值。
#!/usr/bin/sed -nf
# Add n+1 a's to hold space (+1 is for the newline)
s/./a/g
H
x
s/\n/a/
# Do the carry. The t's and b's are not necessary,
# but they do speed up the thing
t a
: a; s/aaaaaaaaaa/b/g; t b; b done
: b; s/bbbbbbbbbb/c/g; t c; b done
: c; s/cccccccccc/d/g; t d; b done
: d; s/dddddddddd/e/g; t e; b done
: e; s/eeeeeeeeee/f/g; t f; b done
: f; s/ffffffffff/g/g; t g; b done
: g; s/gggggggggg/h/g; t h; b done
: h; s/hhhhhhhhhh//g
: done
$! {
h
b
}
# On the last line, convert back to decimal
: loop
/a/! s/[b-h]*/&0/
s/aaaaaaaaa/9/
s/aaaaaaaa/8/
s/aaaaaaa/7/
s/aaaaaa/6/
s/aaaaa/5/
s/aaaa/4/
s/aaa/3/
s/aa/2/
s/a/1/
: next
y/bcdefgh/abcdefg/
/[a-h]/ b loop
p
該腳本和前一個差很少,每一個單詞都轉換成一個單獨的字母"a"(上一個腳本中是每個字母轉換成一個字母"a")。
有趣的是,"wc"程序對"wc -c"計算字符時作了優化,使得計算單詞的速度要比計算字符的速度慢不少。與之相反,這個腳本的瓶頸在於算術,所以單詞計算的速度要快的多(由於只需維護少許的數值計算)
此腳本中和上一個腳本的共同部分沒有給註釋,這再次說明了sed腳本中註釋的重要性。
#!/usr/bin/sed -nf
# Convert words to a's
s/[ tab][ tab]*/ /g
s/^/ /
s/ [^ ][^ ]*/a /g
s/ //g
# Append them to hold space
H
x
s/\n//
# From here on it is the same as in wc -c.
/aaaaaaaaaa/! bx; s/aaaaaaaaaa/b/g
/bbbbbbbbbb/! bx; s/bbbbbbbbbb/c/g
/cccccccccc/! bx; s/cccccccccc/d/g
/dddddddddd/! bx; s/dddddddddd/e/g
/eeeeeeeeee/! bx; s/eeeeeeeeee/f/g
/ffffffffff/! bx; s/ffffffffff/g/g
/gggggggggg/! bx; s/gggggggggg/h/g
s/hhhhhhhhhh//g
:x
$! { h; b; }
:y
/a/! s/[b-h]*/&0/
s/aaaaaaaaa/9/
s/aaaaaaaa/8/
s/aaaaaaa/7/
s/aaaaaa/6/
s/aaaaa/5/
s/aaaa/4/
s/aaa/3/
s/aa/2/
s/a/1/
y/bcdefgh/abcdefg/
/[a-h]/ by
p
就像wc -l
同樣,統計行數。
#!/usr/bin/sed -nf
$=
該腳本是sed腳本中最有用又最簡單的。它顯示了輸入流的前10行。使用"q"的做用是當即退出sed,再也不讀取額外的行浪費時間和資源。
#!/usr/bin/sed -f
10q
輸出倒數N行比輸出順數前N行要複雜的多,但確實頗有用。N值是下面腳本中1,10 !s/[^\n]*\n//
的數值10對應值,此處表示輸出倒數10行。
該腳本相似於tac
腳本,都將每次的結果保留在hold space中最後輸出。
#!/usr/bin/sed -nf
1! {; H; g; }
1,10 !s/[^\n]*\n//
$p
h
關鍵點,該腳本維持了一個10行的窗口,在向其中添加一行時滑動該窗口並刪除最舊的一行(第二個s命令有點相似於"D"命令,但卻不用從新進入SCRIPT循環)
在寫高級或複雜sed腳本時,"窗口滑動"的技術做用很是大,由於像"P"這樣的命令要實現相同的目的須要手動作不少額外的工做。
爲了介紹這種技術,在剩下的幾個示例中,均使用了"N"、"P"和"D"命令充分演示了該技術。此處是一個"窗口滑動"技術對"tail"命令的實現。
這個腳本看上去更復雜,但實際上工做方式和上一個腳本是同樣的:當踢掉了合理的行後,再也不使用hold space來保存內部行的狀態,而是使用"N"和"D"命令來滑動pattern space:
#!/usr/bin/sed -f
1h
2,10 {; H; g; }
$q
1,9d
N
D
注意在讀取了輸入流的前10行後,其中的第一、二、4行的命令就失效了。以後,全部的工做是:最後一行時推出,追加下一行到pattern space中並移除其內第一行。
這個示例充分展示了"N"、"D"和"P"命令的藝術所在,這多是成爲大師路上最難的幾個命令。
#!/usr/bin/sed -f
h
:b
# On the last line, print and exit
$b
N
/^\(.*\)\n\1$/ {
# The two lines are identical. Undo the effect of the N command.
g
bb
}
# If the 'N' command had added the last line, print and exit
$b
# The lines are different; print the first and go
# back working on the second.
P
D
正如所見,咱們使用"P"和"D"維護了一個2行的窗口空間。這種窗口(滑動)技術在高級sed腳本中常常會使用。
(注:"N"、"P"和"D"是sed中絕佳組合,一般爲這3個命令關聯不一樣的定址表達式以及感嘆號"!"時,獲得的結果變幻無窮。一方面"N"和"D"能夠"滑動窗口",另外一方面,"P"和"D"能夠輸出窗口的第一行並滑動,再配合"N"或其它進入多行模式的方法(如"G",或"s"命令添加了換行符"\n"),使得窗口的大小能夠一直維持下去。一般這3個命令同時使用時,它們的相對先後順序是"NPD"。另外,"D"比較特殊,它會從新進入SCRIPT循環,使得窗口中的內容只保留符合條件的行,所以滑動窗口變得更加"動態",要使用s命令實現這種動態窗口滑動,只能藉助"t"標籤跳轉來實現循環)
該腳本只打印重複行,就像"uniq -d"同樣。
#!/usr/bin/sed -nf
$b
N
/^\(.*\)\n\1$/ {
# Print the first of the duplicated lines
s/.*\n//
p
# Loop until we get a different line
:b
$b
N
/^\(.*\)\n\1$/ {
s/.*\n//
bb
}
}
# The last line cannot be followed by duplicates
$b
# Found a different one. Leave it alone in the pattern space
# and go back to the top, hunting its duplicates
D
該腳本移除全部重複行,就像"uniq -u"同樣。
#!/usr/bin/sed -f
# Search for a duplicate line --- until that, print what you find.
$b
N
/^\(.*\)\n\1$/ ! {
P
D
}
:c
# Got two equal lines in pattern space. At the
# end of the file we simply exit
$d
# Else, we keep reading lines with 'N' until we
# find a different one
s/.*\n//
N
/^\(.*\)\n\1$/ {
bc
}
# Remove the last instance of the duplicate line
# and go back to the top
D
做爲最後一個示例,這裏給出了3個腳本,用於增長複雜度和速度,這可使用"cat -s"來實現一樣的功能:壓縮空白行。
第一個腳本的實現方式是保留第一個空行,若是發現後續還有連續空行,則直接跳過。
#!/usr/bin/sed -f
# on empty lines, join with next
# Note there is a star in the regexp
:x
/^\n*$/ {
N
bx
}
# now, squeeze all '\n', this can be also done by:
# s/^\(\n\)*/\1/
s/\n*/\ /
下面這個腳本要更復雜一些,它會移除全部的第一個空行,並保留最後一個空行。
#!/usr/bin/sed -f
# delete all leading empty lines
1,/^./{
/./!d
}
# on an empty line we remove it and all the following
# empty lines, but one
:x
/./!{
N
s/^\n$//
tx
}
下面這個腳本會移除前導和尾隨空行,速度最快。注意,"n"和"b"會完全完成整個循環,而不會依賴於sed自動讀取下一行。
#!/usr/bin/sed -nf
# delete all (leading) blanks
/./!d
# get here: so there is a non empty
:x
# print it
p
# get next
n
# got chars? print it again, etc...
/./bx
# no, don't have chars: got an empty line
:z
# get next, if last line we finish here so no trailing
# empty lines are written
n
# also empty? then ignore it, and get next... this will
# remove ALL empty lines
/./!bz
# all empty lines were deleted/ignored, but we have a non empty. As
# what we want to do is to squeeze, insert a blank line artificially
i\
bx
對於那些想寫具備可移植性的sed腳本,須要注意有些sed程序有衆所周知的buffer大小限制,要求不能超過4000字節,在POSIX標準中明確說明了要不小於8192字節。在GNU sed則沒有這些限制。
然而,在匹配的時候是使用遞歸處理子模式和無限重複。這意味着可用的棧空間可能會限制那些能夠經過某些pattern處理的緩衝區的大小。
除了某些關於sed的書籍(專門研究或做爲某個章節討論的shell編程)以外,能夠從"sed-users"郵件列表的FAQ中(包含一些sed書籍的推薦和建議)獲取更多關於sed的信息:
http://sed.sourceforge.net/sedfaq.html
此外,還有sed的新手教程和其餘一些sed相關資源:
http://www.student.northpark.edu/pemente/sed/index.htm
"sed-user"郵件列表由Sven Guckes負責維護,能夠訪問http://groups.yahoo.com來訂閱。
如要報告bug,郵送至bug-sed@gnu.org,請同時包含在郵件的body中包含sed的版本號,即"sed --version"的輸出結果。
請不要像下面同樣報告bug:
while building frobme-1.3.4
$ configure
error--> sed: file sedscr line 1: Unknown option to 's'
如下是一些容易被報告的bug,但實際上卻不是bug:(注:也就是容易讓人疑惑的點,對於深刻理解sed幫助很大)
大多數版本的sed在"N"命令處理輸入流的最後一行時會直接退出而不打印任何東西。GNU sed會輸出pattern space的內容,除非使用了"-n"選項。這是GNU sed特地的。
例如,sed N foo bar
將依賴於"foo"是偶數行仍是奇數行。或者,當想在某個匹配行後讀取後續的幾行時,傳統的sed可能只能寫成這樣:
/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }
來替代:
/foo/{ N;N;N;N;N;N;N;N;N; }
不管什麼時候,最簡單的工做方式是使用$d;N
,或者設置"POSIXLY_CORRECT"變量爲一個非空值,來解決上述傳統的依賴行爲。
sed使用的是POSIX的基礎正則表達式語法,根據POSIX標準,有些轉義序列沒有預約義,在sed中須要注意的包括:
\|、\+、\?、\<、\>、\b、\B、\w、\W、\'和\`
因爲全部的GNU程序都是用POSIX的基礎正則表達式,sed會將這些轉義符解析爲特殊的元字符,所以"x+"會匹配一個或多個"x","abc|def"會匹配"abc"或"def"。
這些語法在用其餘版本的sed運行時可能會出現問題,特別是某些版本的sed程序會將"|"和"+"解析爲字面符號的"|"和"+"。所以在某些版本的sed上運行須要參考其所支持的正則表達式語法作相應修改。
再者說,某些腳本中"s|abc|def||g"來移除"abc"或"def"字符串,但這隻在sed 4.0.x版以前能正常工做,在新版的sed中會將其解析爲移除"abc|def"字符串。這在POSIX中一樣沒有定義對應的標準。
簡單地說,"sed -i"將刪除只讀文件中的內容。通俗地說,"-i"選項會破壞受保護的只讀文件。這不是bug,而是Unix文件系統工做方式引發的結果。
普通文件的權限表示的是能夠對該文件中的數據作什麼樣的操做,可是目錄的權限表示的是能夠對目錄中的文件作什麼樣的操做。"sed -i"毫不會爲了寫入而再次打開該文件。取而代之的是,它會先寫入一個臨時文件,最後將其重命名爲原文件名:刪除或重命名文件的權限是目錄權限控制的,所以該操做依賴的是目錄的權限,而非文件自己的權限。一樣的緣由,"sed -i"不容許修改一個可讀但其目錄只讀的普通文件。
根本就沒有0行。0行的概念只有一種狀況下使用0,/REGEXP/
,它和1,/REGEXP/
只有一點不一樣:若是REGEXP能匹配上第一行,則前者的結果是隻有第一行,然後者會繼續向下匹配直到能匹配REGEXP,由於範圍地址必需要跨越至少兩行,除非直到最後一行都沒有匹配上。
這是字符集環境設置的問題。POSIX強制'[a-z]'這樣的字符列表採用當前系統當前字符集的排序規則進行排序,C字符集環境下,它表明的是小寫字母序列,其餘字符集環境下,可能表明的是小寫和大寫字母序列,這依賴於字符集。
爲了解決這個問題,能夠設置LC_COLLATE
和LC_CTYPE
環境變量爲C。
會發生這種狀況,多是由於你的輸入流中包含了多字節序列(如UTF-8).POSIX強制這樣的序列字符沒法被"."匹配,所以s/.*//
將不會如你所願那樣清空pattern space。事實上,在絕大多數多字節序列環境下,沒有任何辦法腳本的中途清空pattern space。出於這個緣由,GNU sed提供了一個"z"命令,它將"\0"做爲輸入流的行分隔符。
爲了解決這些問題,能夠設置LC_COLLATE
和LC_CTYPE
環境變量爲C。