標籤:
AWK是Unix下的一款功能強大的文字格式設定化和抽取工具。利用這個工具,可以對複雜的文本檔案進行整理,提取其中的全部或者部分資料,按照需要的格式予以顯示。需要說明的是,AWK的強大功能只針對純文字檔案。對於帶有很多不可顯示字元的二進位資料檔案,單憑AWK就無能為力了。這時我們需要其他工具的協助。
在Unix下,還有一個工具叫做OD,其全稱是“display files in octal format”,也就是說它能將各種檔案以8進位的方式顯示出來。如果設定不同的選項,它還能將檔案以16進位方式顯示。
此外為了方便處理,我們還需要用到另外一個工具,sed。這也是一個Unix下的傳統文本處理工具。在這裡我們主要用到它的文本替換功能。
通過組合以上三種工具,我們就可以完成我們用AWK處理二進位資料檔案的任務了。
筆者手中有一個資料檔案,FXT,其資料結構如表 1所示。
Table 1
| 起始位 |
長度 |
說明 |
| 0 |
8 |
帳號 |
| 8 |
7 |
金額 |
| 15 |
3 |
操作員號 |
如果用普通的文字編輯器開啟這個資料檔案,看到是一串數字和一堆難以理解的字元。根本就無法分辨金額是多少。為了方便讀者理解這個檔案,我們用od來查看這個檔案 (見List1)。
List 1
# od -An -v -tx1 FXT
32 35 38 35 36 30 30 39 00 00 05 00 00 00 0c 31
30 31 0a 32 35 38 30 30 32 33 34 90 20 20 80 20
20 0d 31 30 32 0a …
稍微解釋一下Od的命令參數意義。-An表示不在每行左邊顯示位移量;-v 表示每行都要顯示;-tx1表示輸出時以16進位方式輸出,一次一個位元組。
根據資料結構定義,我們可以看出前面8個位元組(32 35 38 35 36 30 30 39)代表帳號,而且帳號部分是由可顯示的ASCII碼組成的,翻譯後的結果就是25856009;接下來7個位元組(00 00 05 00 00 00 0c)代表金額。最後的c代表credit,就是貸方的意思。它所代表的實際金額是500,000.00。緊接著的3個位元組代表操作員號,也是由可顯示的 ASCII碼組成的。0a是分行符號的ASCII代碼,表示一條記錄結束。
可以看出,正是由於金額部分是由不可顯示的ASCII碼組成,導致了無法用常規方法來提取資料檔案中的資料。
那麼應該如何利用以上的工具來處理這類資料檔案,並且按照可以理解的方式來產生新的純文字的資料檔案呢?
OD 已經將整個資料檔案清楚地顯示出來了。它輸出的格式不符合我們的要求。比如說,本來在一行的記錄,先在給分成了幾行;本來連在一起的字元,現在中間出現了空格。這樣AWK就不好處理了。所以,為了使AWK能夠方便的處理,我們在正式提取資料之前,必鬚生成AWK可以處理的中間檔案。
從List1可以看出,OD在顯示檔案時,每一行前都有一個定位字元,而且原來記錄之間的分行符號也變成了對應的ASCII碼0a。那麼我們的任務就要去掉定位字元,而且要恢複正確的記錄間的分行符號。這一步可以通過以下命令完成。
# od -v -An -tx1 fxt |sed ‘s/ //‘|sed ‘s/0a/,/‘|awk -f org.awk >fxt.a
OD顯示出的結果通過管道被送給sed。Sed將每行開頭的定位字元先去掉,然後將0a字元替換為逗號。然後交給org.awk做格式化處理。
# cat org.awk
BEGIN{
ORS=""
}
{
for(i=1;i<17;i++)
{
if($i==",")
printf("\n");
else
printf("%s", $i);}
}
通過定義ORS=””,我們可以保證輸出的每個欄位之間沒有分隔字元。(ORS=Output Record Separator)。然後,檢查每一行中是否存在逗號,如果有則輸出一個分行符號。這樣我們就把od顯示的結果轉化成以下內容。
# cat fxt.a
32353835363030390000050000000c313031
32353830303233349020208020200d313032
怎麼樣,這樣的格式就順眼多了吧。
產生了中間檔案,下一步就應該做正式的資料提取工作了。在實際工作中,很可能原始資料檔案中的資料結構和我們需要轉換的目標資料結構不完全一致。這時除了提取資料,還要在輸出格式上進行一番加工。例如以上的資料檔案,如果要按照以下資料結構(Table 2)輸出的話,那麼就可以參照程式ck1.awk。
Table 2
| 起始位 |
長度 |
說明 |
| 0 |
12 |
帳號 |
| 12 |
1 |
, |
| 13 |
1 |
借貸標誌,0表示貸,1表示借 |
| 14 |
1 |
, |
| 15 |
14 |
金額 |
| 29 |
1 |
, |
| 30 |
3 |
操作員號 |
# cat ck1.awk
BEGIN{
FS="\n"; }
function trans(s)
{
if((a=substr(s,1,2)-30)<0)
a=0;
for(i=3;i<length(s);i+=2)
{
if((b=substr(s,i,2)-30)<0)
b=0;
a=(a b);
}
return a;
}
{
actno=trans(substr($1,1,16));#帳號
amt=substr($1,17,13);#金額
cdflag=substr($1,30,1); #借貸標誌
if(cdflag=="d")
cdflag=1;
if(cdflag=="c")
cdflag=0;
oper=trans(substr($1,31,6));#操作員號
printf("%-12s,%s,%014d,%s\n", actno,cdflag,amt,oper);
}
一個AWK程式分成三個部分,開始,中間處理和結束部分。開始部分用BEGIN{}表示,結束部分用END{}表示,而中間部分用{}圍起來即可。BEGIN部分是在正式處理開始之前,做的一些準備工作。而END就是在全部記錄都處理完畢之後,做的一些掃尾的工作。
大家可以看到,AWK的文法和C語言很相近,還可以定義函數。而事實上AWK的確在很多地方上都類似於C,所以對C語言有基礎的讀者應該很快就能掌握AWK的用法。
首先,在開始部分,我們將欄位分隔符號FS設為\n,就表示將一行作為一個欄位來處理。
其次,我們定義了一個函數trans。其功能主要是將用ASCII碼方式表示的數字字元給還原成正常的數字形式。數字0-9的ASCII碼對應為 30-39。所以要將一個ASCII碼轉化成對應的數字,只要將該ASCII碼減去30即可。例如1的ASCII碼為31,那麼31 - 30 =1 就很快得出了31所對應的數字。Trans函數正是應用了這一原理,將ASCII碼轉換成對應的數字,然後將這些數字串連起來,形成帳號或者操作員號,返回給調用者。
在正式處理部分,我們大量的運用了substr函數。這是個內建函數,是用於提取字串中的部分字元。要記住,通過OD的轉換之後,我們這裡用2位元字代表一個位元組。所以帳號雖然是8個位元組,而我們提取時,要取16個位元組。
此外,由於帳號和操作員號現在是以ASCII方式表示,所以需要通過trans函數翻譯一下。而金額部分由於是直接由16進位ASCII碼值來表示了,反倒不用再翻譯了。
輸出時用到了printf函數。這個函數的用法和標準的C語言printf函數用法完全一樣。根據輸出資料結構的要求,printf對帳號和金額進行了修飾,使得帳號靠左對齊且長度為12個位元組,用空格對後面不足部分進行填充;而金額部分用前置字元為零填充。
執行以上程式,就得到了我們想要的結果。
# awk -f ck1.awk fxt.a
25856009 ,0,00000050000000,101
25800234 ,1,00000208020200,102
巧用AWK處理位元據檔案