用sed展開文件內容

最近對sed產生了濃厚的興趣,如同一位作者所說:絕大部分人只會使用s命令,很多人也只有在替換的時候纔想到sed。


直到頭幾天,一個羣裏有人提問,如何用sed顯示一個文件的末尾3行,一個人給出了下面這個代碼:


sed -n -e ':a;$!{N;4,${s/^[^\n]*\n//};${p;q}};b a'

是不是感覺看這個代碼跟看天書一樣?反正我看了半天不解,就存下來了。後來仔細看了一些sed的說明文檔,終於略懂皮毛。

上面這個命令是這樣的:

:a;    #建立一個名叫a的label

$!{.....}  # $符號表示最後一行,!表示不執行後面的命令,所以這個是除了最後一行都執行括號內的命令。

N;    # 將下一行讀入緩衝區

4,${......}  # 對第四行至末位行執行括號內的命令

s/^[^\n]*\n//   #  這個命令是最常見的s替換命令,是一個正則,含義是將一行(包含最後回車符)替換爲空

${p;q}  # 對最後一行執行花括號的內容,p命令是打印緩衝區內容,q命令是退出sed

b a   #  b命令是break,a是前面的那個label,跳轉到label處

所以綜合起來,這個命令就是說,執行一個循環,循環中除了最後一行,其餘的行都是將下一行內容添加到緩衝區,並且從第四行開始到最後,每次把緩衝區最前面的一行刪掉,到達最後一行時,打印並退出循環。


所以說,sed並不難,關鍵在於它的命令都是單個字符,零丁一看,很晦澀。其實刪除緩衝區還有更方便的命令D,所以可以改成:

sed -n -e ':a;${p;q};N;4,$D;b a'

這樣是不是更簡短一些呢?


後來我又想到了一個需求,就是在做ACM題的時候,經常重複造輪子,有時候覺得一段代碼寫過,但是不記得放到哪了,或者懶得找了,就自己又寫一遍。如果寫成公共組件,每次只要include一個頭文件該多好啊,但是提交的時候,又要替換成實際代碼,很麻煩,我試過“gcc -E”選項,生成的預處理文件比較大,因爲他把系統頭文件也展開了,並且輸出格式也不友好,一般OJ不認的。所以我用C語言寫了一個展開的工具,只展開#include "xxx"這樣的,而不展開#include <xxx>這樣的。


再後來小小董@aikilis提醒我,可能sed也可以做這件事,我就埋頭鑽研,終於被我弄成功了,輸出結果和我C語言的版本一樣,但是隻用了兩行,很激動!


最開始比較困擾我的是怎麼在sed內部打開一個新的文件,用r命令限制很多,至少我目前遇到的,它後面只能接一個固定參數,不能是一個可變的,而且不能用分號結束r命令,它會統一認爲是文件名,只能換行。後來我發現s命令有一個e選項,可以調用shell執行目前pattern space的文本內容,所以事情就變得簡單了。

sed -e 's/^#include[ \t]\+"\(.*\)"/cat \1/e'

就是遇到開頭是#include "haha.h"這樣的字符串,將文本內容替換爲cat haha.h,然後調用shell執行它。其中'\1'是正則匹配裏面第一個圓括號的內容。

如果僅僅是這樣,那麼當haha.h裏面有#include "hehe.h"的時候,是無法繼續展開的,所以我們要用到遞歸,可是遞歸的話,直接在這一行裏面寫,那這行真的是無限長了,所以考慮用sed的另一個特性-f參數,可以將命令寫在一個文件裏,直接執行文件裏的命令。這個命令文件就是一個函數封裝,可以遞歸調用,不妨起名叫expand.sed,內容是:

s%^#include[ \t]\+"\(.*\)"%cat \1 | sed -f "/path/to/expand.sed";%e

注意因爲路徑裏含有/符號,我就用%作爲s命令的分隔符了,這個和前面那個差不多,只不過cat後面加了一個管道,繼續調用sed命令進行處理,而且使用的命令就是這個expand.sed本身。

然後在外面封裝一個腳本調用就OK了。

$ cat /path/to/myexpand
#! /bin/sh -
sed -f /path/to/expand.sed $1

注意我的腳本叫myexpand,這是因爲expand是shell的內建命令,所以你的程序千萬不要叫expand,否則不會調用到的。


update 2014-07-21

那個取最後三行的代碼,還可以再短

sed -n -e ':a;$p;$q;N;4,$D;b a'

expand裏面用到了cat,如果頭文件不在當前路徑,會產生問題,需要用一個腳本替換命令cat,這個腳本實現的功能就是在環境變量INCLUDE中所包含的路徑中去查找頭文件,當然,需要實現將你常用的頭文件路徑放到環境變量INCLUDE中。

find `echo $INCLUDE |tr ':' ' '` -name $1 |head -1






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