sed修鍊系列(三):sed進階應用程式之實現視窗滑動技術,sed視窗

來源:互聯網
上載者:User

sed修鍊系列(三):sed進階應用程式之實現視窗滑動技術,sed視窗

本文目錄:
1.什麼是滑動視窗(slide window)技術
2.實現視窗滑動
  2.1 通過"s"命令滑動視窗
  2.2 藉助保持空間暫存視窗
  2.3 將視窗維護命令"s"替換成"D"
  2.4真正的大招
  2.5 維持視窗方法論
3.最佳搭檔:"N"、"P"和"D"命令

1.什麼是滑動視窗(slide window)技術

一圖勝千言。

在中,資源管理員的高度固定為正好裝下10行檔案名稱,如果想要顯示第11行,就要下拉捲軸一行的距離,使得11行正好能顯示出來。但這時,最舊的第一行就會被踢出當前可視視窗。

滑動視窗的意思大致就是如此。維護一個視窗,當向視窗添加新資料時,舊的資料就被剔除出去,保證視窗的大小固定。當然,還有動態大小不固定的視窗,此時根據其他規則來判斷是否要剔除舊資料以及剔除哪些舊資料。

在sed的進階用法中,視窗滑動技術作用非常大,這也是"N"、"D"和"P"重要的原因。sed以模式空間為"主戰場",以保持空間為"副戰場"。所以sed要維護"視窗"只能通過模式空間,因為只有在模式空間中才能決定是否要踢掉舊資料以及踢掉哪些舊資料。但很多時候還會輔以保持空間,將每次操作完的視窗資料暫存到保持空間,並在下一個迴圈中將資料從保持空間拿回來維護一番。

下面是一些樣本和視窗技術的用法說明。

2.實現視窗滑動

假如想要輸出檔案a.txt的前10行。這很簡單。

sed '10q' a.txt

由於"q"命令在退出sed程式前會輸出模式空間內容,所以第10行也會被輸出,如果使用的是"Q"命令,則應該為:

sed 11Q a.txt

難題來了,想輸出倒數10行要怎麼實現,倒數15行?倒數20行?。再通用化一些,怎麼能實現tail工具的倒數行查看功能呢。

2.1 通過"s"命令滑動視窗

由於sed是"一往無前,絕不回頭"的串流器,任何一份輸入資料流只要被讀取過絕對不會再次被讀取。

再者,sed採用行號計數器即時計數,每讀取一行,計數器就加1。因此sed在讀取到最後一行前,不知道後面還有多少行,也不知道什麼時候才是最後一行。直到讀取到輸入資料流的最後一行,sed為該行打上"$"標記,表示這是最後一行。"$"只是一個標記符號,並非行號,行號只在計數器中記錄,因此無法通過"$"來計算出倒數幾行,例如"$-1"是錯誤的寫法。這意味著,sed無法直接輸出倒數的行。要輸出倒數多少行,必須通過視窗來實現。

既然說到了行號,順便提一提。行號的匹配過程比Regex的匹配效率要高的多,因為行號是記錄在記憶體中的,只要比較下運算式中的行號值和計數器記錄的值就可以了。而Regex匹配的時候要經過編譯、匹配等工作,而且sed的Regex引擎匹配的效率並不如想象中的那麼好,特別是使用了".*"結合了其他運算式的時候。因此,批量處理大量檔案,特別是大檔案時,能用行號盡量用行號。

回到正題。例如,要輸出倒數10行,這個視窗就一直維持在10行的固定大小。當讀取到最後一行時,可以通過"$"符號來判斷這是否是最後一行,是的話就輸出該視窗,否則不輸出該視窗的資料。

通常,這樣的問題會藉助保持空間來臨時儲存視窗的資料,但此處僅依靠模式空間也能維持一個固定行數的視窗。如下:

#!/usr/bin/sed -nf# 先讀取8行,加上自動讀取的一行共9行N;N;N;N;N;N;N;N# 判斷是否是最後一行,如果不是則讀取下一行並踢掉最前面的一行:1N$!s/[^\n]*\n//;t1# 讀取到最後一行後,輸出視窗中的內容p

為了在模式空間中保持固定行數的視窗,只能讓所有動作在一個sed迴圈內完成(因為SCRIPT迴圈結束時會清空模式空間),因此必須藉助標籤迴圈跳轉。在上面的樣本指令碼中,首先讀取了8行,加上自動讀取的一行,模式空間中共9行,這正是我們需要維護的視窗。隨後,使用一個迴圈判斷標籤,先讀取一行,再判斷該行是否是最後一行。如果不是,則剔除掉視窗中的第一行,這樣視窗就一直維護在固定的9行大小。直到讀取最後一行,這時替換命令失敗。最後視窗中的10行內容被輸出。

既然通過視窗能輸出倒數10行,顯然輸出倒數第10行也是很簡單的,只需將上面的"p"改成大寫的"P"即可。

那要是想輸出倒數15行、20行甚至是50行,也要這麼寫嗎?先不說要寫一大堆的"N"命令,僅僅視窗太大的問題就會導致效率極速下降。假如一個檔案有1000行,要輸出倒數20行,從第20幾行開始每次做"$"匹配的時候都要從模式空間中的20多行搜尋,這相當於處理了一個1000*20=20000行的檔案。當然,行號匹配直接比較計數器的值,沒有這種顧慮,但如果真的是Regex匹配,效率必然極速下降。

這時,保持空間就可以派上用場了。但需要注意的是,雖然使用保持空間可以簡化處理邏輯,但因為兩個buffer空間的資料交換過程都會對效能有一絲絲影響。所以一般來說,用一個buffer空間實現比藉助兩個buffer空間效率要高那麼一點點,特別是大量處理大檔案同時還要交換大量資料的時候,效能差距就比較明顯。

2.2 藉助保持空間暫存視窗

上面的例子的思路是讀取一些初始行數填充視窗,在視窗快要達到目標大小時使用標籤迴圈判斷功能來維持固定大小的視窗滑動過程。

如果藉助保持空間,可以將每次滑動後的視窗資料暫存起來,在讀取了下一行時,將其追加回來再處理。

例如上面的例子藉助保持空間實現,語句如下:

#!/usr/bin/sed -nfH10,${g;s/[^\n]*\n//;h}$p

由於藉助了保持空間,因此整個過程無需在一個sed迴圈內完成。上面的過程將通過sed迴圈的自動讀取來填充視窗,並將其追加到保持空間。當填充到10行後,將其從保持空間拉回模式空間,並使用"s"命令滑動該視窗,滑動結束後再次將其放回保持空間。直到最後一行滑動結束後,輸出最終視窗的內容。

注意,上面的數字是10,而不應該是11,這是"H"命令導致的結果,因為每次H執行時都會在保持空間尾部先追加一個"\n",即使最初保持空間為空白時也會追加。這使得從讀取第10行並執行了H後,保持空間將有共11行,其中第一行為空白。

2.3 將視窗維護命令"s"替換成"D"

考慮"D"命令的特性:刪除模式空間的第一行,並進入下一個SCRIPT迴圈。因此,除非模式空間已經沒有內容了,否則"D"命令會一直SCRIPT迴圈直到模式空間中沒有能被D命的地址匹配的內容。

為了方便說明"D",舉個簡單的例子:壓縮連續空行。還有壓縮相鄰重複行,即去除重複行。

echo -e "1\n2\n\n3\n4\n\n\n5" | sed '$!N;/^\n$/!P;D' echo -e "1\n2\n3\n3\n4\n4\n4\n4\n4\n5" | sed -r '$!N;/^(.*)\n\1$/!P;D'

這兩個命令的思路都是以視窗為模型(我是這麼認為的。自從有了視窗的概念,任何涉及"NDP"的命令我都將其認為是視窗,這樣容易理解多了)。以"N"命令讀入下一行到視窗中,如果視窗中的兩行不重複,就輸出第一行並執行"D"剔除第一行,於是滑動了視窗,並進入下一個SCRIPT迴圈。直到視窗中出現重複行,將一直迴圈滑動這大小為2行的視窗但不輸出,直到不重複了才輸出前面相鄰重複行的最後一行。

由此也可以看出,其實即使是單行或雙行的模式空間也都算是一個視窗,只不過這個視窗維護起來比較靈活。

以第二個去除重複行的命令來說,大致流程如下:其中d表示該行未被"P"輸出且被"D"刪除了,p表示被"P"輸出後再被"D"刪除。

1p2p3d3p4d4d4d4d4p5p

回到正題。"D"命令其自身實現了一種特殊的條件式SCRIPT迴圈,而"s"命令要實現這樣的迴圈只能通過標籤判斷的方式來實現,除非藉助保持空間。正因為如此,"D"命令也能剔除視窗中的舊資料實現視窗滑動。

使用"D"很多時候可以簡化指令碼的複雜性,但其絕對無法替代"s"命令的維護行為。因為D命令的迴圈範圍固定為一整個SCRIPT迴圈(正如上面壓縮重複行的樣本,每次都回到SCRIPT的下一個迴圈從頭開始),而"s"命令藉助標籤跳轉可以實現在任意大小的範圍內迴圈。因此,"D"命令實現視窗滑動時,在通用性上不及"s"命令。

仍然以輸出倒數10行資料為例。藉助"D"實現的語句如下:

#!/usr/bin/sed -nf1h2,10H10g$p10,${N;D}

其中前3行只是為了填充一個10行的視窗放在模式空間中(填充視窗的方法實在很多,所以只要知道這3步是幹啥的就行),從第11行開始這3行就沒有任何作用了。重點在最後一行的10,,${N;D},這是"NPD"的絕佳組合之一,通過"N;D"輕鬆地維持了一個固定大小的視窗:讀一行刪一行。直到最後一行被"N"讀取,才被"$p"輸出。之所以"$p"要放在"D"的上一行,是因為"D"總是會回到SCRIPT的頂端,所以它後面的命令是不會執行的。

2.4 真正的大招

呀,原來視窗這麼好用?但是不要鑽入了sed的牛角而蒙蔽了雙眼。說實話,通過sed自身實現很多較為複雜的需求並不那麼簡單,費神又費力,反而結合其他文本處理工具來實現要簡單的多的多。

就以上面的例子來說,輸出倒數10行,結合shell變數簡單的不得了。

total=`wc -l <filename`sed -n $((total-9))',$p' filename

這樣想輸出任意哪些行號的行都可以簡單至極。以上只是一個樣本,結合其他可用的工具,同樣能輕鬆地實現sed自身比較複雜的需求。因此,這是最終的"大招"。

另外,上面的樣本中引號加的位置很奇怪,這是sed結合shell的難題。很多人可能都遇到過這個問題,在網上也沒有很好的解釋,因為這不是sed的問題,而是shell解析的特性。

2.5 維持視窗方法論

綜合上面幾個樣本,維持視窗分為兩種情況:

看上去,第一種情況比較容易些。確實如此,第一種情況不用多次考慮兩個buffer之間的資料交換。並且,第一種情況的效率更高,因為在達到視窗大小之後,再也無需和保持空間交換資料。

3.最佳搭檔:"N"、"P"和"D"命令

經過前面視窗滑動的樣本,也能發現"N"和"D"是一個絕佳搭檔,它倆配合能實現完美的視窗滑動,而且相比於藉助保持空間暫存視窗的方法,它倆的邏輯非常清晰。

前面說了"N"和"D"的結合,加上"P"呢?單獨的"N"和"P"結合沒什麼好說的,可能出現的情形太多了。

但"P"和"D"的結合卻有一層固定的意義:根據匹配模式判斷是否輸出多行模式中的第一行,然後踢掉該行,並回到SCRIPT迴圈頂端。這很可能是在維護一個大小為兩行的視窗。格式通常為:

[Address]P;D

再同時結合"N",作用就更明顯了,維持一個視窗,並判斷是否要輸出該視窗的第一行,然後滑動視窗。格式通常為:

[Address1]N;[Address2]P;D

很多時候,Address是省略掉的。P命令前的Address完全是條件判斷語句,判斷是否要輸出。N命令前的Address1如果存在,最大的可能是"$!",這表示當最後一行已被讀取過(無論是sed迴圈自動讀取的、n命令讀取的還是N命令讀取的),直接跳過該命令。如果不加"$!",則沒有下一行可供讀取時,將直接輸出模式空間(除非指定了"-n")並退出sed程式。

是否在N前加"$!",對結果的影響很大。但想判斷是否要加,難度還是挺大的,至少要對sed何時輸出模式空間內容了如指掌。可以閱讀sed修鍊系列(一):花拳繡腿之入門篇。

最後,需要說的是"N"和奇偶數行的關係。"N"命令在無法讀取下一行時將輸出模式空間內容並退出sed,萬一讀到的最後一行是奇數行,又或是偶數行,會怎樣影響結果呢?可能很多人(包括我自己)都琢磨過這個問題,也被這個問題困惑了很久而不得其解,這個問題甚至放進了info sed手冊的Bug報告段中進行專門的解釋。

其實根本不用考慮最後一行是奇數行還是偶數行,無論最後一行是奇數行還是偶數行,sed根本不管這個邏輯,它只記得最後一行是否被讀取過,如果讀取過,則在N命令處就退出sed。所以,真正需要考慮的是N命令前是否要加"$!",它決定了sed是在N處退出還是繼續執行後面的命令。但有一種情況必須要考慮奇偶性,當"N"結合了其它讀取行的操作(命令"n"或sed的自動讀取)時,因為其餘任意一個讀取動作都會改變"N"讀取的行的奇偶性。

例如,分別輸出輸入資料流的奇數行和偶數行。考慮以視窗模型實現的話,這是很簡單的。

seq 1 10 | sed 'N;P;d'       # 輸出奇數行seq 1 10 | sed '1!{N;P};d'   # 輸出偶數行seq 1 10 | sed -n '1!{N;P;d}'  # 輸出偶數行,但需要考慮奇偶性seq 1 10 | sed -n '1!{$!N;P;d}'  # 輸出偶數行,不需考慮奇偶性

前兩個命令都很容易理解,第三個命令卻需要考慮奇偶性。因為視窗是從第二行開始填充的,所以視窗資料被"d"刪除前,其內第2行總是奇數行,例如"(2,3)"是一個視窗,"{4,5}"是一個視窗。當最後一行是奇數時,其必定是被"N"讀取的,這不會影響結果。但如果最後一行是偶數,則此行必定是被sed自動讀取的,使得sed在"N"命令處就結束了,其後的"P"就無法執行。這時,可以考慮在"N"前加上"$!",即第四條命令。

當然,更簡單的方法如下:

seq 1 10 | sed 'n;d'seq 1 10 | sed '1!n;d'

再例如,要刪除檔案的倒數第2行。如果知道視窗的概念,這一切都很容易:維持一個2行的視窗(N和D就夠了),當發現最後一行被讀取後,不輸出其前一行即可。以下是實現語句:

sed 'N;$!P;D' filename

注意這裡的"N"在處理最後一行時的作用,它輸出了最後一行,且讓sed程式結束,但這一行是自動輸出的,而非"P"輸出的,所以會受"-n"影響。如果改為"$!N",則最後一行被讀取後,將直接連續執行兩"D",即同時刪除了倒數2行。

 

回到系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7048359.html

轉載請註明出處:http://www.cnblogs.com/f-ck-need-u/p/7496916.html註:若您覺得這篇文章還不錯請點擊下右下角的推薦,有了您的支援才能激發作者更大的寫作熱情,非常感謝!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.