流編輯器 SED 十分鐘入門全教程

這裏借用一下酷殼網sed博文的圖來開題,超讚的~~


1. sed 簡介及原理簡析

1.1 sed 簡介

Sed 是什麼?相信很多人都有所瞭解,sed 全稱StreamEDitor 即流編輯器。生於1973年or 1974年by 貝爾實驗室的 Lee E. McMahon(已故),是基於交互式編輯器ed("editor", 1971)的腳本功能及更早的qed(quick editor ,1965-1966)(Sed 比 awk 要大那麼幾歲,所以客官莫急,過幾天我們再來詳解 awk)。Sed 是最早支持正則表達式的工具之一,並且至今仍然被人們用做文本處理,特別是在其強大的替代命令(sed中最常用的s命令,下文會有詳述!)。


Sed 是一個腳本型的編譯器,是非交互式的編輯器,也就是說sed與常見的編譯器不同(比如說vim),sed沒有交互式的編輯界面以及光標移動或者龐大的快捷鍵/功能,sed 的使用就是很簡單的一個腳本行,相當極客吧?



1.2 sed 原理簡析

Sed 從系統的標準輸入或者文本獲取輸入,經過處理之後輸出到系統的標準輸出(屏幕)

那麼其處理過程是什麼樣的呢?Sed 是流編輯器,它一次只處理一行信息,也就是說 sed 以行爲處理單位,每次從標準輸入/文本獲取一行信息,存儲到其“ 模式空間 ”(pattern space,實際上是一個臨時緩衝區)中,在這個模式空間中,sed 就會將腳本中的處理命令做完,然後就將處理完的數據輸出到標準輸出(屏幕)。然後sed 再獲取下一行至模式空間處理,這樣循環直至文件末尾。


這裏說的只是 sed 的一個簡單的工作流程,也有可能有別的情況發生,比如限定行,重定向標準輸出等。此外對於 sed 還有其他的討論在第三節進階中。


1.3 sed 的特點

sed 作爲編輯器有如下特點讓人難以拒絕:

(1) 非交互,基於模式匹配的過濾及修改文本

(2) 逐行處理,所以那些對舒適的交互式編輯而言太大的文件使用sed 會顯得格外有優勢

(3) 可實現對文本的輸出、刪除、替換、複製、剪切、導入、導出等各種編輯操作

(4) 腳本化,在shell 腳本編程中你總不能打開vim 吧?


2. 系統性講解 sed 的常用函數

在UNIX 系操作系統,遇到不會的,你首先想到的不是Google,不是找書,最簡單方便的方法是查找學習其manpage! 

那麼我們來看一下 sed 使用:

                       sed [OPTION]... {script-only-if-no-other-script} [input-file]...

所以對於一個 sed 命令主要有四個字段:

第一個是sed (擦,你不廢話麼?大笑);

第二是  [OPTION] 字段,用於指定後面的腳本字段的工作模式;

第三是  {script...} 字段,幹哈的?當然是如何處理的命令部分;

第四個是輸入文件字段!

下面我們對sed 命令行中最重要的第二和第三個字段展開討論!


2.1 sed 的簡單使用

上文介紹sed 運行原理時,我們說到 sed 的輸入是標準輸入 或 文本文件!

所以sed 命令的使用也是可以有兩種方式的:

(1) $ echo "hello,world" | sed "s/hello/hi/"
      hi,world

(2) $ echo "hello,world" > file
      $ sed 's/hello/hi/' file
      hi,world


2.2 sed 命令之 OPTION 字段選項

在sed 的 manpage 中,對於OPTION字段的所有選項都簡單地使用一句話描述,那麼這顯然是不夠的,下文我們一個一個討論:


2.2.1 選項 -n

manpage 中對於 -n 選項是這麼描述的:

 -n, --quiet, --silent
              suppress automatic printing of pattern space  即關閉模式空間的自動打印

所以上文說到 sed 在模式空間中處理好一行文本之後,它就直接打印到屏幕上(再不贅述標準輸出了阿),那麼使用-n 選項後,sed 命令做完工作也不打印信息!那麼這有什麼用?在2.3 中我們介紹到 p 函數再結合它來介紹!


2.2.2 選項 -e

manpage 中對於 -e 選項是這麼描述的:

-e script, --expression=script

              add the script to the commands to be executed   即添加將要執行的腳本命令

使用 -e 選項後接 將要執行的命令,這也是sed 的最常用的形式,即:  sed  -e   ' ... '  input_file


2.2.3 選項 -f

manpage 中對於 -f 選項是這麼描述的:

-f script-file, --file=script-file
              add the contents of script-file to the commands to be executed   即添加腳本文件的內容來執行

作爲腳本型編輯器,sed 當然也支持使用較本文件咯,下面的小例子我們也可以清晰的看出sed 的 -f 執行sed 腳本文件的方法:

$ echo "hello,world" > hi
$ cat a.sed
s/hello/hi/
$ sed -f a.sed hi 
hi,world



2.2.4 選項 -i

manpage 中對於 -i 選項是這麼描述的:

-i[SUFFIX], --in-place[=SUFFIX]
              edit files in place (makes backup if extension supplied)   --  直接編輯原文件(儘量添加備份)

這裏需要詳述一下,前面提到的sed 工作原理:sed 獲取一行--> 處理--> 打印,我們可以看到,sed 所作的只是將命令執行後打印,而沒有保存到任何地方,所以如果我們需要保存處理後的內容,有兩種方法:

(1) 重定向

使用Linux 的童鞋們別偷笑阿,就是這麼簡單,使用">" 將輸出重定向到文件即可。

(2) 使用 -i 選項

使用 -i 選項,sed 所作的操作將會直接在原文件中操作!是的,你可以想象,這纔是編輯器該乾的事,但是如果你修改的是比較重要的文件,建議還是使用"-i.***" 格式的選項,比如使用sed -i.bak  "{...}" file ,這樣雖然會直接在file文件中進行修改,但是 -i.bak 選項在進行操作之前就將 file 文件做了一個 file.bak的備份!

但是還是儘量不要使用 -i 選項!


2.2.5 選項 -r

manpage 中對於 -r 選項是這麼描述的:

-r, --regexp-extended
              use extended regular expressions in the script.   即在腳本中使用擴展正則表達式


sed 默認情況下是基礎正規表示法語法,使用 -r 選項是支持擴張的正則表達式。說簡單點就是,使用 -r 選項的話,一些元字符(比如:+,*,(,),{,})都不需要轉義符\ 了!用一個簡單的例子說明基礎的正則表達式和擴展正則表達式的區別,下面的sed 語句很簡單,就是將hello 中的多個l合併成一個:
long@zhouyl:/tmp$ echo "hello" | sed 's/l+/l/'      -- 不填加 -r 選項,l+ 並沒有匹配上多個l
hello   
long@zhouyl:/tmp$ echo "hello" | sed 's/l\+/l/'     -- 給+ 添加上轉義字符\ 後,l\+匹配了多個l並替換爲一個l
helo
long@zhouyl:/tmp$ echo "hello" | sed -r 's/l+/l/'   -- 添加-r選項,使用l+即可匹配上ll併成功替換爲一個l
helo



2.3 sed 命令之 腳本字段函數使用方法

Sed 中的腳本字段纔是重中之重,這裏麪包括sed函數、正則表達式等等,語法以及使用也都因人而異,也是編寫/分析sed 腳本最難的部分!但是萬變不離其宗,sed 函數的使用是確定的,所以本節圍繞着sed  的常用函數展開分析,畢竟標題入門教程,如有遺漏也請見諒!


2.3.1 各種打印 p(print)

sed 的p 函數當然是用作打印的了!但是sed 支持的打印方法比較靈活:

*    sed "np" test             打印test文件的第n行

*    sed "1,3p" test          打印test文件的第1,3行

*    sed "/pattern/p" test          /pattern/是sed的匹配語法,sed 讀一行信息到模式空間,如此行匹配到pattern,執行後面的操作p即打印到屏幕,即打印匹配行

這裏使用一個小例子我們看一下執行效果:

long@zhouyl:/tmp$ sed "2p" test 
hello ,   world
my name is zhouyunlong
my name is zhouyunlong
what's   your name?


我們可以看到,sed 語句中爲打印第二行,可是執行結果中每行都打印了,而第二行打印了兩次,這是爲什麼?

這還得從sed 的執行原理來看,上面一直重複,sed 是讀一行到模式空間,執行腳本,然後把模式空間的內容打印到屏幕,所以上面的測試是,先讀第一行到模式空間,發現腳本不是對此行的操作,打印到屏幕。sed再讀第二行到模式空間,執行腳本發現腳本是打印第二行,於是打印了到屏幕 ,腳本執行完,再把模式空間的打印到屏幕,所以第二行會打印2 遍! sed再繼續讀後面的行直至文件結束!


那麼我想只打印第二行怎麼辦? 對,上面說到的 -n 選項!添加 -n 選項後,sed 先讀一行到模式空間,執行腳本,本來執行完腳本後應該是把模式空間的內容再打印到屏幕上,而此時打印被 -n 選項屏蔽了!所以不打印接着讀第二行,執行腳本,腳本中的p 函數的打印不會被 -n 選項屏蔽,所以會打印,然後再讀第三行...

long@zhouyl:/tmp$ sed -n "2p" test 
my name is zhouyunlong
long@zhouyl:/tmp$ sed -n "1,2p" test 
hello ,   world
my name is zhouyunlong
long@zhouyl:/tmp$ sed -n "/zhou/p" test 
my name is zhouyunlong


但是有時候匹配打印時會打印多個值,比如上面我們匹配 o ,打印結果:

long@zhouyl:/tmp$ sed -n "/o/p" test 
hello ,   world
my name is zhouyunlong
what's   your name?

這時候我們該怎麼辦? 對,使用行號加匹配雙限定!語法要求爲 'linenumber,/pattern/p' ,執行效果爲:如果給定行沒有此匹配就往下找,直到找到需要的匹配位置;而如果給定的行直接就找到了匹配,sed 會打印接下來的所有行:

long@zhouyl:/tmp$ sed -n '1,/long/p' test
hello ,   world                     
my name is zhouyunlong                          -- 第一行沒有匹配long,在第二行找到。腳本中的"1,/long/" 的效果則類似於從第一行到匹配到long 的行! 
long@zhouyl:/tmp$ sed -n '1,/hello/p' test
hello ,   world                                 -- 給定行找到匹配,sed 會打印接下來的所有行!
my name is zhouyunlong
what's   your name?


打印第一行很簡單 sed -n '1p' test 即可,這我們都知道,那麼打印文件的最後一行呢?我總不能先查找一下文件共多少行,然後使用 sed -n '1,np' test 來打印吧?放心,sed 設定 $ 爲最後一行。所以,sed -n "$p" test 即爲打印最後一行,而 sed -n "1,$p" test 爲打印全部文件

long@zhouyl:/tmp$ sed -n '1p' test
hello ,   world
long@zhouyl:/tmp$ sed -n '$p' test 
what's   your name?
long@zhouyl:/tmp$ sed -n '1,$p' test 
hello ,   world
my name is zhouyunlong
what's   your name?


上面的$ 被預設爲最後一行,與此類似,還有其他關於正則表達式的元字符,對於這些元字符我們該如何查找?聰明的你肯定知道使用轉義符' \ '的,對吧?

long@zhouyl:/tmp$ sed -n '/\$/p' test 
$what's   your name?


注:上文介紹了 -n 選項與 p 共用,可以只打印匹配行做操作後的輸出,但是在這羅嗦一句,請客官你在平時使用時切記 -n + p 不要和 -i 共用
我們都知道 -i 選項會直接將輸出保存到原文件,而 -n +p 只打印匹配行操作後的輸出,那麼會發生什麼? 原文件將只剩下修改的部分!下面我們模擬一個場景,比如說我們想通過sed 修改 /etc/network/interfaces 文件,比如我想修改eth0 爲eth1 ,那麼如果我們使用 -n -i "...p" 會發生什麼?更不幸的是,如果你的命令寫錯了,會發生什麼?
long@zhouyl:/tmp$ cat interfaces        -- 當前我們的interfaces文件
# The loopback network interface
auto lo
iface lo inet loopback


auto eth0
iface eth0 inet static
address 192.168.2.239
netmask 255.255.255.0
gateway 192.168.2.1


long@zhouyl:/tmp$ sed -i -n "s/eth0/eth1/gp" interfaces 
long@zhouyl:/tmp$ cat interfaces    -- 使用-n -i p後,interfaces文件只剩下我們修改的內容!
auto eth1
iface eth1 inet static 
long@zhouyl:/tmp$ sed -i -n "s/eth0/eth1/gp" interfaces -- 此時文件中已全是eth1,我們用"s/eth0/eth1/gp" 來模擬錯誤的匹配
long@zhouyl:/tmp$ cat interfaces            -- 經過錯誤的匹配之後,因爲沒有匹配任何內容,interfaces文件已爲空!!!


所以,即使你想用 -n -i p三個操作的組合,你也要使用"i.bak"模式來進行:
long@zhouyl:/tmp$ sed -i.bak -n "s/eth0/eth1/gp" interfaces
long@zhouyl:/tmp$ cat interfaces.bak    -- 即使此時interfaces 已經出錯了,但是我們仍然有個備份文件
# The loopback network interface
auto lo
iface lo inet loopback


auto eth0
iface eth0 inet static
address 192.168.2.239
netmask 255.255.255.0
gateway 192.168.2.1

所以,儘量避免使用sed  -n  -i '...p'  file 這種 -n -i p三者共存的情況!還要牢記:每次使用 -i 選項時,都要使用 "-i.***" 形式


2.3.2 打印行號 =

如上面的例子匹配時,如果文件很大,我想知道在第幾行匹配的該怎麼辦? 別急, sed 預定了 = 來完成此重任!

long@zhouyl:/tmp$ sed -n '/\$/=' test 
3
long@zhouyl:/tmp$ sed -n '/long/=' test 
2


2.3.3 附加文本 a(append)

有時候我們想找到一行信息,然後在其後添加一句該怎麼辦? 別急 sed 提供了附加信息 a 方法,它會在匹配行之後添加上你想要添加的信息:

long@zhouyl:/tmp$ sed "/long/a\I\'m glad to see you" test 
hello ,   world
my name is zhouyunlong          --- 匹配上long,並在此行之後附加了腳本中 a\ 之後的字符串
I'm glad to see you               
$what's   your name?


注: 爲什麼我使用雙引號? 因爲有時候使用單引號會不支持轉義,這個好像在shell 中也有類似效果:

long@zhouyl:/tmp$ echo "my name is $name"
my name is zhou
long@zhouyl:/tmp$ echo 'my name is $name'
my name is $name

所以你只要記住,以後統一使用雙引號即可!


2.3.4 插入文本 i(insert)

插入文本的使用格式與附加文本一樣,只不過執行效果是在匹配行之前添加信息:

long@zhouyl:/tmp$ sed "/long/i\I\'m glad to see you" test 
hello ,   world
I'm glad to see you
my name is zhouyunlong       --- 匹配上long,並在此行之前附加了腳本中 i\ 之後的字符串
$what's   your name?


2.3.5 修改文本 c(change)

插入文本的使用格式與附加文本一樣,只不過執行效果是直接替換匹配行:

long@zhouyl:/tmp$ sed "/long/c\I\'m glad to see you" test 
hello ,   world
I'm glad to see you                --- 匹配上long,並直接使用腳本中 c\ 之後的字符串 替換了改行
$what's   your name?

注: 因爲附加文本,插入文本以及修改文本的使用方法一樣,所以在此添加的註釋是對這三條全體!

(1) 除了使用"/pattern/a\string" 這樣的格式對匹配行做操作外,sed還支持 "linenumber a\string" 這樣直接對指定行進行操作!

long@zhouyl:/tmp$ sed "$ a\I\'m glad to see you" test 
hello ,   world
my name is zhouyunlong
$what's   your name?
I'm glad to see you          ---這裏是在最後行 $ 之後追加文本!


(2) 如果不爲 a/i/c 操作指定匹配內容或者行號,它會應用到每一行!!!(這種情況在sed 中比比皆是,如 d ,p 等~)

long@zhouyl:/tmp$ sed "a\For play" test 
hello ,   world
For play
my name is zhouyunlong
For play
$what's   your name?
For play


2.3.6 刪除文本 d(delete)

sed 的刪除文本使用更簡單,一種是使用 "/pattern/d" 刪除匹配行,一種是使用 "linenumber d" 刪除指定某一行!

long@zhouyl:/tmp$ sed "1d" test         -- 刪除第一行
my name is zhouyunlong
$what's   your name?
long@zhouyl:/tmp$ sed "/long/d" test    -- 刪除匹配long 的行
hello ,   world
$what's   your name?    
long@zhouyl:/tmp$ sed "d" test          -- 不指定全部刪除!

注: 還記得上面的行號和匹配混合模式麼? 還記得匹配後打印的內容麼?對d 方法同樣管用哦:

long@zhouyl:/tmp$ sed "1,/long/d" test     --- 上面使用"1,/long/p" 會打印第一行第二行,而此處第一行正常打印,第二行被d 操作刪除!
$what's   your name?
long@zhouyl:/tmp$ sed "1,/hello/d" test    ---- 上面使用"1,/hello/p" 打印了全文,此處全文都被d 操作刪除!



2.3.7 替換文本 s(substitute)

如同簡介中所說,sed 至今接近40餘載仍然活躍在腳本中,與之有着強大的替換文本方法有着莫大的關係!此處我們着重講解:

替換文本的使用格式爲    [address[,address]]   s   /pattern-to-find/replace-pattern/[gpwn]   即使用 replace-pattern 去替換超找到的 pattern-to-find 字段!

簡而言之就是 s/old/new/flag ,下面我們將根據此處的幾段分別展開介紹:

*    old部分: 可以使用正則表達式,此處不贅述!如果你還沒有正則表達式的概念,建議你想去看看正則表達式!


*    new部分: 可以使用\U,\u,\L,\l,&,\E以及正則表達式的向後引用
        >>>  \U: \U後接的部分全部改爲大寫

long@zhouyl:/tmp$ sed -n "s/zhou/\Uzhou/gp" test 
my name is ZHOUyunlong

        >>>  \u: \U後接的部分僅首字母改爲大寫
long@zhouyl:/tmp$ sed -n "s/zhou/\uzhou/gp" test 
my name is Zhouyunlong

        >>>  \L: \L後接的部分全部改爲小寫
        >>>  \l: \l後接的部分僅首字母改爲小寫
        >>>  &:   這裏用來替換old部分被匹配的文字
long@zhouyl:/tmp$ sed -n "s/zhou/Mr.\u&/gp" test 
my name is Mr.Zhouyunlong

        >>>  \E:  以\E後接的部分終止
long@zhouyl:/tmp$ sed -n "s/zhou/Mr.\u&\E\!/gp" test 
my name is Mr.Zhou!yunlong

        >>>  向後引用,即用\加上前面正則表達式分組,如\1,\2 
long@zhouyl:/tmp$ sed -n -r "s/(zhou)(yunlong)/\u\1 \u\2/gp" test 
my name is Zhou Yunlong 


*    flag部分:flag部分可以使用g,p,w,數字,n,N
        >>>  g: global 全局替換,flag部分使用g,sed會替換所有文本中的匹配部分
        >>>  p: 打印,上文已詳細說了,如上面的例子,p經常與-n 選項一起使用
        >>>  w: 保存文檔,如果flag 部分使用 w file,會將匹配內容寫入file文件中
long@zhouyl:/tmp$ sed -n -r "s/(zhou)(yunlong)/\u\1 \u\2/gpw file" test
my name is Zhou Yunlong
long@zhouyl:/tmp$ cat file
my name is Zhou Yunlong

        >>>  數字: 如果flag 部分使用的是數字,表示對匹配行中的第幾次匹配進行替換
long@zhouyl:/tmp$ sed -n -r "s/o/O/p2" test
hello ,   wOrld
my name is zhouyunlOng



注:在替換命令的格式 's/.../.../' 中,使用 “/” 作爲分隔符! 有時候,無論是在匹配項中需要匹配 / 還是在替換內容中要添加/ ,使用 's/.../.../'格式都顯得非常混亂,而且可讀性變得極弱!所以sed 可以很方便快捷地指定使用其他分隔符!只要將你想要替換的分隔符緊接着 s 即可,sed 即可自動識別!

比如:

long@zhouyl:/tmp$ echo "/home/long/hello/test/abc" | sed -e  "s/\//\:/g"
:home:long:hello:test:abc
long@zhouyl:/tmp$ echo "/home/long/hello/test/abc" | sed -e  "s#\/#\:#g"
:home:long:hello:test:abc


在上面的例子中你覺得使用# 替代 / 作爲分隔符是不是清晰許多? 再來看個噁心的,"s/\//\\\/g"整個看花了
long@zhouyl:/tmp$ echo "/home/long/hello/test/abc" | sed -e  "s/\//\\/g"
sed: -e expression #1, char 8: unterminated `s' command
long@zhouyl:/tmp$ echo "/home/long/hello/test/abc" | sed -e  "s/\//\\\/g"
\home\long\hello\test\abc
long@zhouyl:/tmp$ echo "/home/long/hello/test/abc" | sed -e  "s#\/#\\\#g"
\home\long\hello\test\abc



2.3.8 從文件中讀文本 r(read)

sed 還支持從文件中讀取內容並將讀出來的文本添加到匹配內容行之後

long@zhouyl:/tmp$ cat hi
hello,world
long@zhouyl:/tmp$ sed  -e "$ r hi" test         -- 將從文件中讀出的文本行放在最後一行之後
hello ,   world
my name is zhouyunlong
$what's   your name?
hello,world
long@zhouyl:/tmp$ sed  -e "/zhou/r hi" test     -- 將從文件中讀出的文本行放在匹配zhou字段行之後
hello ,   world
my name is zhouyunlong
hello,world
$what's   your name?



2.3.9 讀取下一行 n/N(next)

n和N都是讀下一行的函數,但是n 與N 的區別是:使用n 讀取下一行,會直接替換當前模式空間的內容,而N爲讀取下一行並追加在模式空間內容之後。

long@zhouyl:/tmp$ sed -n 1"p" test      -- 只打印第一行
hello ,   world
long@zhouyl:/tmp$ sed -n 1"{n;p}" test  -- 當讀取第一行時,腳本命令爲讀取下一行並打印!所以打印出來的爲第二行
my name is zhouyunlong
long@zhouyl:/tmp$ sed -n 1"{N;p}" test  -- 當讀取第一行時,腳本命令爲讀取下一行並追加到當前模式空間之後,打印!
hello ,   world
my name is zhouyunlong


注意: sed 的追加命令(包括下面進階用法中的保持空間中的追加),如上雖然打印出來的爲兩行,但是其實在sed看來只有一行,以上面的測試用例爲例, 雖然打印出來爲兩行,但是此時對於sed來說是 : “ ^hello ,   world\nmy name is zhouyunlong$ ”。也就是說本來第一行行末的$ 被追加操作替換成換行符\n, 而第二行首^也被清除了!不信?用事實說話,下面我們在打印之前使用替換命令將換行符 \n 替換成 --- !

long@zhouyl:/tmp$ sed -n 1"{N;l;s/\n/---/p}" test 
hello ,   world\nmy name is zhouyunlong$
hello ,   world---my name is zhouyunlong


2.3.10 匹配後退出 q(quit)

有時候需要在模式匹配之後退出sed命令,以便執行其他處理腳本,所以q 方法就可以用來幫你完成這個使命!

long@zhouyl:/tmp$ sed  -e "2q" test 
hello ,   world
my name is zhouyunlong
long@zhouyl:/tmp$ sed  -e "/zhou/q" test 
hello ,   world
my name is zhouyunlong


2.3.11 更多的打印 l (list)

上面已經提到打印使用p,那這個l 函數打印什麼?manpage上說它是以一種視覺上更明確的方式打印,也就是說不是標準輸出,l 會打印比p 更多的內容,有一些在p 標準輸出的情況下會被系統掩蓋,比如"$""\n":

long@zhouyl:/tmp$ sed '1,$l' test 
hello ,   world$          --- 這是l 打印的信息!
hello ,   world            --- 這是執行完操作後系統把模式空間的內容打印到標準輸出!
my name is zhouyunlong$
my name is zhouyunlong
$what's   your name?$
$what's   your name?



3. sed 進階

3.1 Sed 進階之執行多語句

sed 中有時候你需要一次執行較多的語句,這裏有兩種方法:

(1) sed [OPTION]  "{cmd1; cmd2; cmd3 ... }"  input_file

(2) sed -e "cmd1" -e "cmd2" -e "cmd3"  ... input_file 

那麼這兩種方法有什麼區別呢?其實很簡單,方法一中的 cmd1; cmd2; cmd3 ... 讓你想到了什麼?對,C語言中的一個一個語句,事實是這樣,方法一中有着執行順序,也就是說,先執行 cmd1 再執行cmd2 ...

方法二中的每個cmd 都以 -e 鏈接,所以他們都處於同一級別

long@zhouyl:/tmp$ sed -n -e 1"{p;n;p;n;p}" test
hello ,   world
my name is zhouyunlong
$what's   your name?

我們可以利用這種順序性,完成上述這樣的操作,雖然限定了第一行,但是語句中使用n讀取下一行然後打印,再在第二行之上讀下一行再打印,我們就這樣打印了三行~在下一個進階用法中,我們再深入使用這種順序性!


long@zhouyl:/tmp$ sed -n -e "1p" -n -e "2p" test
hello ,   world
my name is zhouyunlong
long@zhouyl:/tmp$ sed -n -e "2p" -n -e "1p" test
hello ,   world
my name is zhouyunlong


我們可以看到,-e 鏈接的命令並沒有順序性,"1p"和"2p"的順序顛倒並沒有對結果有任何影響!


3.2 Sed 進階之保持空間及其函數

前面介紹 sed 的 運行原理以及介紹其他一些方法時,我們不斷提及sed 的模式空間(Pattern Space)sed 讀取文件的一行,放入模式空間,並在模式空間做命令對應的操作,再將模式空間的內容打印到屏幕上。所以,模式空間整個過程中就相當於一個車間!但是有時候,車間當前所製作的東西還不足以打印出來,我們還需要後面的其他原料製作的東西與這個東西一組合才能生產出我們想要的東西! 所以,還需要一個倉庫來存儲一些當前還不足以打印的內容!

實際上,sed 總共是有兩個緩衝區的,一個叫做模式空間,還有一個叫做保持空間(Hold Space),而這個保持空間就是我們上面說到的倉庫! 有了一個車間一個倉庫,我們就能製作出更加豐富更加多元的東西!當然,倉庫和車間之間需要交流,所以sed 提供了幾組命令用來完成 模式空間和保持空間的內容複製工作:

(1) h / H(hold): 保存命令,用於將模式空間的內容複製/追加到保持空間
(2) g / G(get): 取回命令,用於將保持空間的內容複製/追加到模式空間
(3) x (exchange): 交換命令,用於將模式空間和保持空間的內容做交換


那麼,有了保持空間,我們可以完成什麼?結合上面的命令,我們可以完成很多給力的操作:

long@zhouyl:/tmp$ cat abc 
Tom
male
Jerry
male
Marry
female
Linux
penguin
long@zhouyl:/tmp$ sed -n -e "{h;n;G;p}" abc        -- 我們可以隔兩行掉轉順序,想打印性別再打應名字
male
Tom
male
Jerry
female
Marry
penguin
Linux
long@zhouyl:/tmp$ sed -n -e "{h;n;H;g;s/\n/:/g;p}" abc      ---  我們還可以將名字和性別讀到一行並改用: 鏈接
Tom:male
Jerry:male
Marry:female
Linux:penguin
long@zhouyl:/tmp$ sed -n -e "{h;n}" -e '/male/{g;p}' abc  --- 我們可以打印男性的名字
Tom
Jerry
Marry       
long@zhouyl:/tmp$ sed -n -e "{h;n}" -e "/^male/{g;p}" abc   ---上面的匹配中不幸的是,female也匹配到了。我們只能改用"^male"進行匹配,結果正確!
Tom
Jerry
long@zhouyl:/tmp$ sed  -e '{1!G;h;$!d}' abc      -- 我們還可以掉轉整個文件的順序!
penguin
Linux
female
Marry
male
Jerry
male
Tom


對於最後一個關於掉轉文件中行的順序的sed 命令,可能還需要詳述一下,使用的命令爲 sed  -e '{1!G;h;$!d}'  input_file 。 此句的功能與Linux下的 tac 命令比較接近!在命令腳本中也只有三句!  1!G; h ;$!d 。其中每一個函數我們都已經在上面有所講解了,那麼爲什麼這三句命令能夠完成這麼好玩的任務呢?簡單分析一下:

首先第一句是 1!G,G我們在上面說過,是將當前的保持空間的內容取回並追加在當前的模式空間內容之後,1!G表示除了第一行不用取回之外每行都需要取回追加!

第二句是h ,我們都知道是將當前的模式空間的內容複製到保持空間,因爲是 h 所以是覆蓋的。

第三句是 $!d ,也就是說除了最後一行,每行在第三句時會刪除模式空間中的內容,也就是說不打印到屏幕。而到最後一行不刪除時,sed會打印到屏幕!

所以整個sed 命令的流程就是首先讀第一行到模式空間,然後賦值到保持空間,然後刪除模式空間的內容;再讀第二行到模式空間,此時就需要先將保持空間的內容(即第一行)追加到此時模式空間的內容之後,也就是說此時的模式空間內容爲“^line2\nline1$”,再將此內容複製到保持空間中,最後刪除模式空間的內容;再讀第三行到模式空間,還是先將保持空間的內容取回並追加到當前模式空間的內容之後,所以此時是“^line3\nline2\nline1$”,此後將此內容複製到保持空間後刪除模式空間;...此時我們也可以看出來,每一次執行G時,就是把當前讀到的行順序都顛倒了!直到最後一行時,不刪除模式空間的內容,所以打印出來的也就是顛倒完順序的狀態!


3.3 Sed 進階之標籤

爲了使 sed腳本真正的"自由",sed還允許在腳本中用":"設置標籤,然後使用"b"和"t"命令進行流程控制。其中,"b"表示"branch",爲分支命令使用起來很類似C語言中的goto 語句, "t/T"表示"test",是測試後跳轉的命令。


同樣,我們先來看看 sed 的manpage中對這三個函數的定義:
       b label
              Branch to label; if label is omitted, branch to end of script.   即跳轉到標籤;如果標籤未定義,則分支會結束腳本。
       t label
              If a s/// has done a successful substitution since the last input line was read and since the last t or T command, then branch to label; if label is omitted, branch to end of script.    即自從上一行讀或者上一次執行t/T命令後,如果s/// 成功替換,那麼 t 會跳轉到標籤;如果標籤未定義,則分支會結束腳本。
       T label
              If  no  s///  has  done a successful substitution since the last input line was read and since the last t or T command, then branch to label; if label is omitted, branch to end of script.  This is a GNU extension.  與上面的t 命令剛好相反,如果 s/// 未成功替換,T會跳轉到標籤;如果標籤未定義,則分支會結束腳本。



那麼標籤是如何定義的呢?標籤以冒號開始,冒號與標籤名之間不允許有空格或者製表符,標籤最後如果有空格的話,也會被認爲是標籤的一部分

再來說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


t命令的格式和b命令是一樣的:
[address] t [label]
它表示的是如果滿足address的話,sed腳本就會根據t命令指示的標籤進行流程轉移。而標籤的規則和上面講的b命令的規則是一樣的。下面也給出
一個例子:
s/pattern/replacement/
t break
command
:break

而上面已經說過,T命令和t 命令的使用基本上一模一樣,只不過 t 對應的是在s/// 匹配時跳轉,而 T 在 s/// 不匹配時才跳轉

那麼使用起來如何?我們看個小例子,但是不做那麼複雜了阿:

long@zhouyl:/tmp$ sed -e ":la s/o/O/; /o/b la" test 
hellO ,   wOrld
my name is zhOuyunlOng
$what's   yOur name?
long@zhouyl:/tmp$ sed -e ":la s/o/O/;s/zhou/Zhou/ ; t la" test 
hellO ,   wOrld
my name is zhOuyunlOng
$what's   yOur name?
long@zhouyl:/tmp$ sed -e ":la s/o/O/;s/zhou/Zhou/ ; T la" test 
hellO ,   world
my name is zhOuyunlong
$what's   yOur name?



4. 小結

Sed 是學習Shell 時不得不學的利器,在各處的腳本中,你也難免會看到她的身影。本文雖然不能全面介紹Sed,但是我力求詳盡,希望對你有用!

看了這麼多,終於快結束了,我承認你不可能只花了十分鐘,這是我的錯,但我不是標題黨,我只是希望你有信心能夠學習好Sed!既然你都看到這了,說明你和我都成功了!謝謝,如果你還想深入,歡迎學習最下方的課外閱讀!


看文容易,寫文難,且看且點贊~謝謝。


下期我們介紹sed 的兄弟 AWK,敬請期待。轉載請註明出處,謝謝~吐舌頭


==========================

引用:

【1】《Shell基礎十二篇》

【2】酷殼網:《sed 簡明教程

【3】關於一些概念之類的可能有部分參考百度百科OR wiki!

【4】 http://club.topsage.com/thread-2372858-1-1.html

==========================

課外閱讀:

【1】 sed的GNU官方使用文檔

【2】 SED單行腳本快速參考 / sed1line (很多大神的sed命令哦)

【3】 Sed 手冊



發佈了90 篇原創文章 · 獲贊 434 · 訪問量 106萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章