歡迎您光臨本站 註冊首頁

Unix/Linux Shell編程實戰:使用嵌入文檔Here Documents

←手機掃碼閱讀     火星人 @ 2014-03-04 , reply:0

Unix/Linux Shell編程實戰:使用嵌入文檔Here Documents


 引  言
 嵌入文檔(Here Documents)技術是Unix/Linux平台中腳本語言BASH提供的一個特徵,是用於在當前腳本內部處理重定向的一種手段。由於Unix從設計之初就是遵循「使用小而簡單的工具進行無縫的集成」的理念,完成一項工作是需要大量的文本處理工具、流處理工具進行合作,通過標準輸入、輸出進行連接,將一個工具的處理結果轉向另一個工具進行加工,直到得到最終的處理結果。因此,I/O重定向(redirection)是SHELL腳本的一個重要語言特徵。但是,重定向技術也使整個處理過程顯得凌亂、瑣碎,對於初學者往往把握不好重定向的使用時機,甚至濫用重定向,而使得整個腳本里存在大量的中間文件,極大的損害了腳本語言的「優雅」程度。這是SHELL腳本學習過程中的一個常見問題。
 為解決這個問題,SHELL腳本在設計上儘可能的減少中間文件,增加了「Here Documents」這一特徵。Here Documents提供了在腳本內部保存原始文件,以及在腳本內部提供重定向的機制,是精簡腳本的有效方式,也是反映腳本編程水平的重要特徵。在各種資料中「Here Documents」有不同的翻譯名稱,例如「嵌入文檔」、「內部文檔」、「現場文檔」等等。為了兼顧字面上的相近和使用上的形象,本文使用「嵌入文檔」這一名稱,並通過若干實例展示這一功能的使用方法。
 
 一、問題的提出
 事情是這樣的,在8月初,筆者負責某信息學競賽考試伺服器的準備和維護工作(http://gait.buaa.edu.cn:8765),在考試結束需要將伺服器上的考試成績數據導出來,發送給競賽主辦方。伺服器上安裝的競賽考試系統是在Linux下開發的專用系統,其中保存的成績數據都是採用自定義格式的文本文件,並且由於系統開發並不是非常完善,沒有提供很好的導出成績的功能。而主辦方當然希望看到一個直觀的Excel文件的總成績表。因此,需要緊急的手工處理一下,將伺服器上的文本文件轉換成Excel表出來。在這種情況下,當然首選的是SHELL腳本文件。
 在考試系統裡面,成績的統計結果已經可以方便的在一個統計工具的WEB頁面里查看。這個統計工具當初設計的還是非常實用的,可以根據考試人員的需求,在頁面上查詢考試人員的成績、分數,等等。但是這個工具當時實現時存在一些問題,它只是列出了成績信息,而沒有把考試人員的一些基本信息列出來,包括考試人員的聯繫方式等註冊屬性。但是這個信息還是要給主辦方的,而且要求都顯示在同一個Excel文件中。改程序已經來不及了,因此這次只能手工處理配置文件了。
 根據歷次的經驗,對於這種批量文本的轉換處理,使用SHELL腳本的效率還是非常高的。因此立即著手進行。
 
 二、需求和目標  
 首先要解決的是弄清考試系統里保存的數據文件都是什麼格式。通過與系統的開發人員相切磋,從系統中找出了與本次處理相關的文件格式。
 
 2.1 考生基本信息文件
 
 在當前目錄下有所有考生的基本註冊信息文件,每個考生保存為一個文件。在系統中,考生有唯一標識是一個ID,是一個32位整數,使用這個整數保存成該ID的"%8X.REG"形式的文件名(當時使用這種格式主要是為了顯示的直觀和規整)。
 例如,我本人的ID是4,那麼我的存儲文件名為「00000004.REG」。
 信息文件的內容如下所示:
 
 --------------------------------------------------------
 考生基本信息文件(00000004.REG)
 --------------------------------------------------------
 1        loginid:jgj
 2        pwd:kkaACXiDLyk=
 3        repwd:kkaACXiDLyk=
 4        name:靳國傑
 5        sex:男
 6        id:130229197911092212
 7        birthday:19791109
 8        school:北航
 9        class:BY0306
 10        phone:01082317614
 11        mobile:13691250730
 12        address:北航逸夫館
 13        code:100083
 14        mail:[email protected]
 --------------------------------------------------------
 
 關於文件格式的說明:
 1.        上面這個文件中共包括14行,每一行形如「註冊屬性名稱:屬性值」,即冒號左邊是欄位名稱,冒號右邊是值。該文件一共14行,存儲了每個人員的14項註冊信息。這些信息都是要給主辦方的。
 2.        通過名稱可以直觀的看出,註冊屬性包括登錄名、密碼、確認密碼(與密碼相同)、姓名、性別、身份證號、出生日期、學校、班級、電話、手機、地址、郵政編碼、郵箱。
 在本次考試中一共註冊了650人,因此,註冊文件共有650個,文件名是從00000001.REG到0000028A.REG。
 
 2.2 匯總文件
 
 為了給競賽主辦方的人員一個清晰的材料,需要將這650個文件匯總,形成一份總表TOTAL.CSV,每人一行,15個欄位各佔一列。為方便起見,同一行中各欄位以「~」分隔,從而能夠方便的導入到Excel中,形成給主辦方的最終結果。匯總文件TOTAL.CSV如下表所示:
 
 --------------------------------------------------------
 匯總文件(TOTAL.CSV)
 --------------------------------------------------------
 ID~登錄名~密碼~確認密碼~姓名~性別~……
 00000001~bootluck~QkxldMs0JllDRj5pIczVIw==~QkxldMs0JllDRj5pIczVIw==~Sun~男~……
 00000002~xinghe~RA9S0UZEIHVDRj5pIczVIw==~RA9S0UZEIHVDRj5pIczVIw==~xinghe~男~……
 00000003~alice607~9sCyToqCsQd5ouKr/39oaw==~9sCyToqCsQd5ouKr/39oaw==~alice607~女~……
 00000004~jgj~kkaACXiDLyk=~kkaACXiDLyk=~靳國傑~男~……
 ……
 ……
 ……
 ……
 ……
 ……
 0000028A~holybear~a1aACXiDLyk=~a1aACXiDLyk=~李聖雄~男~……
 --------------------------------------------------------
 
 
 關於文件格式的說明:
 1.        上面這個文件中共包括651行,第一行是所有註冊欄位(以「~」分隔)。之後的每一行對應一個考生的所有註冊信息。
 2.        通過名稱可以直觀的看出,註冊屬性包括登錄名、密碼、確認密碼(與密碼相同)、姓名、性別、身份證號、出生日期、學校、班級、電話、手機、地址、郵政編碼、郵箱。
 
 3.3 匯總要求
 
 因此,在這個工作中,需要對現有的數據文件進行如下的匯總過程,以形如最終提交的匯總文件。演演算法如下:
 ① 對每個考生信息文件,抽取冒號右邊的信息,得到所有註冊欄位名;抽取冒號右邊的信息,得到所有屬性值;
 ② 對抽出的14個欄位,以適當的分隔符分隔(比如「~」),拼成一行,總為匯總文件的首行;
 ③ 對每一個考生的屬性值,以「~」分隔,形成一條考生信息記錄,作為匯總文件的一行;
 ④ 通過編寫的Shell腳本,對所有650個考生信息文件執行上述過程,最終形成一個有651行的CSV(逗號分隔欄位)文件。
 形成的CSV文件導入到Excel中就是要發給主辦方的文件。由於CSV導入Excel的操作比較簡單,這裡就不再贅述,下面重點講解Shell腳本的編寫過程。
 
 三、技術基礎
 我們採用Red Hat Linux和Unix下面廣泛使用的BASH來編寫腳本。首先講解有關Here Documents的語法基礎和基本使用方法。
 
 3.1 語法結構
 在命令行中執行man bash,可以找到BASH手冊中有關Here Documents的語法定義,其一般形式如下:
 
    Here Documents
        This  type  of  redirection  instructs the shell to read input from the
        current source until a line containing  only  word  (with  no  trailing
        blanks)  is seen.  All of the lines read up to that point are then used
        as the standard input for a command.
 
        The format of here-documents is:
 
               <<[-]word
                       here-document
               delimiter
 
        No parameter expansion, command substitution, arithmetic expansion,  or
        pathname expansion is performed on word.  If any characters in word are
        quoted, the delimiter is the result of quote removal on word,  and  the
        lines  in the here-document are not expanded.  If word is unquoted, all
        lines of the here-document are subjected to parameter  expansion,  com-
        mand  substitution,  and arithmetic expansion.  In the latter case, the
        character sequence \

is ignored, and \ must be used  to  quote
        the characters \, $, and `.
 
        If the redirection operator is <<-, then all leading tab characters are
        stripped from input lines and  the  line  containing  delimiter.   This
        allows  here-documents within shell scripts to be indented in a natural
        fashion.
 
 為了便於說明,這段說明翻譯成中文如下所示:
 
 #man bash
 
    Here Documents(嵌入文檔)
        Here Documents作為重定向的一種方式,指示shell從源文件的當前位置開始讀取輸出,直到遇到只包含一個單詞的文本行時結束。在該過程中讀到的所有文本行都將作為某一個命令的標準輸入而使用。
        here-documents的使用形式:
 
               <<[-]word
                       here-document
               delimiter
 
        Shell將不對word 進行任何參數和命令替換、表達式計算,以及文件名擴展。如果在word中出現被引號包含的字元,則delimiter 將被視為在word中去除引號所包含部分的結果,並且here-document中的文本將不被擴展。如果word不包含引號,則here-document中的所有文本都將進行常規的參數擴展、命令替換、表達式計算。在後一種情況下,字元序列\將被忽略, 並且必須對元字元\, $, 和`使用\進行轉義。
        如果重定向操作符是<<-, 則輸入行和delimiter行中的所有前綴TAB字元都將被忽略。這樣源代碼中的here-documents就可以按照優雅的嵌入方式進行對齊。
 
 
 3.2 技術的應用
 下面我們考察一下在Shell腳本中使用Here Documents的必要性所在。Shell腳本對於文本文件的處理是非常方便的,因此實現本文所要完成的工作還是比較簡單的,只需對所有文件進行一次循環過程。但是問題在於,從①-②,②-③的每一部,實際上都是使用cut、echo、cat等進行流處理,每個處理過程的輸出作為下一步驟的輸出。這裡面自然的要使用重定向的方法。在以前對BASH用的不熟的時候,往往笨拙的使用臨時文件存儲這些中間結果。比如: 00000004.REG -> 00000004.cut -> 00000004.line -> all.txt。這樣處理的過程中,代碼顯的非常雜亂,而且有時候不得不寫出多個腳本來處理重定向,這種情況使得問題更為加重。
 因此,這次下決心要將代碼寫的精簡一些。在BASH內部可以使用Here Documents進行重定向,使流的輸入輸出都是在同一腳本內進行的,不用拆開成多個腳本,也省略了大量的中間文件,使代碼非常清晰、緊湊。
 
 Here Documents的一般形式如下:
 
 
 0001  commands <<ID
0002     here documents
 .......     ......
 000n  ID<\n>
 
 
 在上面這個語法描述中,0001行的commands是任何SHELL命令。ID是一個字元串標識,標誌著嵌入文檔的開始(習慣上,常用的標識是EOF,等等)。從0002行可以寫任何文本,這裡寫入的內容都將作為標準輸入傳送給commands。在最後一行寫標識符ID,代碼嵌入文本結束。
 
 在使用Here Documents編寫腳本的時候注意以下兩點:
 
 1.        在000n行上,必須只有一個ID,也就是說,ID前面不能有空格,ID後面必須是回車符。否則SHELL處理時會出錯。這一點在實際時要尤為注意。
 2.        嵌入文檔內可以寫普通文本(比如說,寫程序的使用Usage),也可以是標準的SHELL語句。如果是SHELL語句,要使用反引號(`)括起來,這時候語句的標準輸出也將作為嵌入文檔送給commands。這個特性非常有用。
 
 下面舉一個簡單的例子:
 
 
 $ nl -b a  aa.sh
      1  #!/bin/bash
      2
      3  cat <<EOF
     4    Now:
      5    `date`
      6  EOF
 $ sh aa.sh
   Now:
   三  7月 26 19:23:24 CST 2006
 上面是一個最簡單的例子,但是已經基本概括了Here Documents的使用方法。在aa.sh中,EOF作為嵌入文檔的標識ID。4、5行中的Here Documents是一行標準文本「Now」和一個命令date的輸出。兩部分輸出送給了cat命令去執行。
 
 
 四、問題的解決
 通過反覆的編寫、調試,基本上最終的腳本go.sh如下表所示:
 
 
 $ nl -b a go.sh
      1  #!/bin/sh
      2
      3  echo "ID~"`grep title  reg_prop.xml  | cut -d\" -f2 | ./toline.sh`
      4  for v in 0*.REG; do
      5    echo -n "$v~"
      6
      7    while true; do
      8      read line
      9
     10      if [ "$?" == "1" ]; then
     11        break
     12      fi
     13
     14      echo -n $line"~"
     15    done  <<EOF
    16    `cut -d: -f2 $v`
     17  EOF
     18
     19    echo
     20  done
 
 由於SHELL腳本起源於上個世紀70、80年代,因此可讀性比當前的編程語言一般要差一些,因此下面對這個腳本中的內容作一下簡單的說明:
 
 1. 第4行-20行的for循環是對650個文件依次進行處理。(對於分隔符,本來是想用分號,或者逗號,或者空格。結果這些字元在註冊信息里全出現了,後來選了「~」號)。
 
 2. 第7-15行是while循環,執行的基本功能是把每個信息文件拼成一行。
 
 3. 16行是執行cut命令,提取文件中冒號右邊的內容,把輸出作為Here Documents送給while循環。
 
 4. 在while循環中,第8行是使用read命令,每次讀取一行,使用「~」拼成一行。注意這裡有個特殊的處理:因為在信息文件中某一項註冊項可能是空的,這樣16行的輸出可能中間有空行。因此,判斷read的結束不能使用if [ "$line" == ""] 的方法。經過調查,發現可以從read的返回值獲取是否到達行末的狀態。如果是遇到Ctrl-D結束,那麼返回值是1。如果是正常的回車鍵結束,即使沒有任何內容,返回值也是0。因此,正確的判斷是 if [ "$?" == "1" ];
 
 5. 17行的EOF是代表Here Documents的結束。注意此處不能像寫C程序一樣,為了美觀而把EOF向右縮進,因為SHELL規定EOF必須是該行唯一出現的內容,連空格都不能有。因此,這個EOF就暫時突出在這一行了。(通過將輸入操作符改為「<<-」可以解決這個問題,但是這裡也無傷大雅)。
 
 6. 文件中的toline.sh是自已編寫的另外一個常用腳本,用於把輸入的若干文本行拼成一行,並且以「~」分隔。這個腳本的實現比較簡單,建議留給讀者自己練習編寫。
 五、總結
 Shell腳本是進行伺服器維護和日常管理的專用語言,是每一個Unix/Linux伺服器管理員必須掌握的基本功。鑒於這種工具對文本文件處理的方便性,在應急處理一些工作時也是首選工具,本文中工作要求就是一個典型的應用。在Shell腳本的眾多語法結構中,Here Documents可以說是比較深入的內容了,因此在網上的資料中並不算十分常見,即使是在Red Hat內置的大量腳本中,這種技術也不是用的特別普遍。但是深入掌握一種語法結構必將大大提高工作效率。在本文的形成過程中參考了網上其它一些文檔和資料,讀者可以參考更多的關於Shell腳本深入編程的文章,掌握這一利器,為我所用。

[火星人 ] Unix/Linux Shell編程實戰:使用嵌入文檔Here Documents已經有282次圍觀

http://coctec.com/docs/service/show-post-35952.html