您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關使用bash解析xml的案例分析的內容。小編覺得挺實用的,因此分享給大家做個參考。一起跟隨小編過來看看吧。
最初的需求是希望bash能提供完整成熟的xml解析工具來解析xml,但是并沒有找到這樣的工具。后來在StackOverFlow上找到一個簡單的處理xml的方法,即:
rdom () { local IFS=\> ; read -d \< E C ;}
方法只有一行!(當然,兩條語句應該算是兩行……)
當然,這也只能處理最簡單原始的xml,不能處理帶屬性的,不能有注釋等等。
由于樓主過于懶惰,不想引入(學習)新的腳本語言,所以打算改造上面的方法。
改造之前,先來解釋一下上面那行語句的意義。
其實很簡單,這行命令的作用就是讀取<與下一個<之間的字符
(xml中,如果在節點本身之外存在<或者>,屬性值含有空格,則函數失效,所以我們假設xml中沒有此情況)
有了上面的假設,那么兩個<字符直接,就一定會有一個>字符,>將read讀取的內容分為兩部分,分別記做E和C,舉個簡單的例子:
<tag>value</tag>
第一次執行rdom時,read讀取到<即結束了,所以E和C都是空字符串。
第二次執行rdom時,read讀取到的內容為:tag>value,然后是<字符,read結束。所以E=tag;C=value
第三次執行rdom時,read讀取到的內容為:/tag>到下一個<或文件末尾。所以E=/tag,C為空白符。
所以這種方式并不實用,我們想支持帶屬性的節點,我們也不想刪除xml中的注釋,我們甚至還想解析xml的聲明,我們……好了,我們想的太多了。我們還是看看能做些什么吧。
我們可以看出,<>里面的部分是作為整體賦值給E的,那么解析屬性就要對E做手腳。
(我們假設xml中,在節點本身之外存在沒有<和>,屬性值中也沒有空格)
下面我們來操作一下,首先先引入一個輸入空格,用來顯示層級的函數echo_tabs
echo_tabs() { local tabs=""; for((i = 0; i < $1; i++)); do tabs=$tabs' ' #4個空格 done echo -n "$tabs" #一定要加雙引號 }
然后我們來解析xml中的聲明,就是下面這部分
<?xml version="1.0" encoding="utf-8"?>
聲明與其他標簽閉合方式不同,并且尖括號內兩端是?,所以這里要把它與普通節點區分。
read_dom() { #備份IFS local oldIFS=$IFS local IFS=\> #字段分割符改為> read -d \< ENTITY CONTENT #read分隔符改為< local ret=$? local ELEMENT='' #第一次執行時,第一個字符為<. #所以read執行完畢,ENTITY和CONTENT都是空白符 if [[ $ENTITY =~ ^[[:space:]]*$ ]] && [[ $CONTENT =~ ^[[:space:]]*$ ]]; then return $ret fi # ENTITY = ?xml version="1.0" encoding="utf-8"? #解析xml聲明,并非普通節點,閉合方式與節點不同 if [[ "$ENTITY" =~ ^\?xml[[:space:]]*(.*)\?$ ]]; then #使用正則去除問號和xml字符 ENTITY='' ELEMENT='' #不是普通節點 ATTRIBUTES="${BASH_REMATCH[1]}" #獲取聲明中的屬性 else #普通節點 ELEMENT=${ENTITY%% *} #獲取節點名稱,如果ENTITY中有空格,則第一個空格前面部分即為節點名稱 ATTRIBUTES=${ENTITY#* } #獲取節點所有屬性,如果ENTITY中有空格,則第一個空格后面部分為所有屬性(#2和#4,#4情況下,會多出/) fi }
下面我們來解析注釋。注釋讓人煩惱的地方是,注釋內可以包含尖括號!這里只做最簡單處理,只解析不含尖括號的注釋!
if [[ "$ENTITY" = \!--*-- ]]; then #不檢查注釋 return 0 fi
現在我們看xml中最關鍵的部分
我們知道,CONTENT為節點的內容,顯示出來就可以了
if [[ ! "$CONTENT" =~ ^[[:space:]]*$ ]]; then echo -n CONTENT=$CONTENT fi
節點自身屬性都在ENTITY中,所以我們需要將節點名稱與屬性分開,然后再提取屬性名和屬性值
我們分別處理下面幾種形式的節點
<test a="1"/> <test></test> <test>abc</test> <test/>
我們之前已經將節點名稱與屬性分開了
ELEMENT=${ENTITY%% *} #獲取節點名稱,如果ENTITY中有空格,則第一個空格前面部分即為節點名稱 ATTRIBUTES=${ENTITY#* } #獲取節點所有屬性,如果ENTITY中有空格,則第一個空格后面部分為所有屬性(#2和#4,#4情況下,會多出/)
但是上面的ATTRIBUTES變量會有個小問題,稍后說明
ELEMENT如果以/開頭,那么這是讀取到節點的閉合標簽了
ELEMENT如果以/結尾,那么這是一個空標簽,類似<test/>
其他情況ELEMENT均為節點名稱,但是讀取<test a="1"/>這類標簽時,ELEMENT沒有問題,ATTRIBUTES是以/結尾,也就是說,這時,標簽已經閉合,并且我們需要將/從ATTRIBUTES末尾刪除
#!/usr/bin/env bash #只適合解析簡單xml,若屬性值帶有空格,注釋中含有尖括號等,則無法解析 #下面情況可以正常解析 #0.<?xml version="1.0" encoding="utf-8"?> #1.<test>Only For Test</test> #2.<application # android:label="@string/app_name"> #3.<test/> #4.<uses-permission android:name="android.permission.BLUETOOTH" /> #Attribute=Attribute Name #VALUE=Attribute Value #ELEMENT=Element Name #CONTENT=Element Content #接受一個int層級參數,層級從0開始 echo_tabs() { local tabs=""; for((i = 0; i < $1; i++)); do tabs=$tabs' ' #4個空格 done echo -n "$tabs" #一定要加雙引號 } read_dom() { #備份IFS local oldIFS=$IFS local IFS=\> #字段分割符改為> read -d \< ENTITY CONTENT #read分隔符改為< local ret=$? local ELEMENT='' #第一次執行時,第一個字符為<. #所以read執行完畢,ENTITY和CONTENT都是空白符 if [[ $ENTITY =~ ^[[:space:]]*$ ]] && [[ $CONTENT =~ ^[[:space:]]*$ ]]; then return $ret fi #第二次執行時,分為下面集中情況 #0.<?xml version="1.0" encoding="utf-8"?> #此時read結果為?xml version="1.0" encoding="utf-8"? #CONTENT=若干空白符 #1.<Size>1785</Size> #此時read結果為Size,所以ENTITY=Size,CONTENT='1785' #第三次read結為/Size,所以ENTITY=/Size,CONTENT=若干空白符 #2.<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> #此時read結果為ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/", 所以ENTITY=tListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/",CONTENT=同#1 #3.<test/> #此時read結果為test/,所以ENTITY=test/,CONTENT=若干空白符 #4.<test name="xyz" age="21"/> #此時read結果為test name="xyz" age="21"/,所以ENTITY=test name="xyz"/,CONTENT=若干空白符 #5.<!--q1--> #此時read結果為!--q1--,所以ENTITY=!--q1--,CONTENT='' # ENTITY = ?xml version="1.0" encoding="utf-8"? #解析xml聲明,并非普通節點,閉合方式與節點不同 if [[ "$ENTITY" =~ ^\?xml[[:space:]]*(.*)\?$ ]]; then #使用正則去除問號和xml字符 ENTITY='' ELEMENT='' #不是普通節點 ATTRIBUTES="${BASH_REMATCH[1]}" #獲取聲明中的屬性 else #普通節點 ELEMENT=${ENTITY%% *} #獲取節點名稱,如果ENTITY中有空格,則第一個空格前面部分即為節點名稱 ATTRIBUTES=${ENTITY#* } #獲取節點所有屬性,如果ENTITY中有空格,則第一個空格后面部分為所有屬性(#2和#4,#4情況下,會多出/) fi if [[ "$ENTITY" = \!--*-- ]]; then #不檢查注釋(#5) return 0 fi if [[ "$ELEMENT" = /* ]]; then #節點末尾 #1第三步 tabCount=$[$tabCount - 1] echo_tabs $tabCount echo END ${ELEMENT#*/} #刪除/ return 0 elif [[ "$ELEMENT" = */ ]] || [[ $ATTRIBUTES = */ ]]; then #3或#4 empty=true #節點沒有子節點,也沒有value(自身為閉合標簽) if [[ $ATTRIBUTES = */ ]]; then #如果是#4情況 ATTRIBUTES=${ATTRIBUTES%*/} #將末尾的/刪除,提取所有屬性 fi echo_tabs $tabCount echo -n ELEMENT=${ELEMENT%*/}' ' elif [ ! "$ELEMENT" = '' ]; then #第一次執行時,ENTITY和CONTENT都是空串 echo_tabs $tabCount echo -n ELEMENT="$ELEMENT"' ' #輸出節點名 tabCount=$[$tabCount + 1] #新節點 else echo -n "XML declaration " #ELEMENT為空,不計算層級 fi local empty=false #沒有子節點,沒有value IFS=$oldIFS #屬性之間由空白符分割,恢復IFS,IFS默認為空格/換行/制表符 local hasAttribute=false #節點是否有屬性 for a in $ATTRIBUTES; do #循環所有屬性 #echo ATTRIBUTES=$ATTRIBUTES ' -+-+-+- ' if [[ "$a" = *=* ]] #情況#2和#4 then hasAttribute=true ATTRIBUTE_NAME=${a%%=*} #提取屬性名 ATTRIBUTE_VALUE=`tr -d '"' <<< ${a#*=}` #提取屬性值并去掉雙引號 echo -n ATTRIBUTE=$ATTRIBUTE_NAME VALUE=$ATTRIBUTE_VALUE' ' #輸出屬性名/屬性值 fi done if [[ ! "$CONTENT" =~ ^[[:space:]]*$ ]]; then echo -n CONTENT=$CONTENT fi if [ "$empty" = true ]; then echo echo_tabs $tabCount echo -n END ${ELEMENT%/*} #刪除/ # echo -n ' (empty node)' fi echo return $ret } read_xml() { local tabCount=0 #用來格式化輸出,計算節點層級 while read_dom; do : done < test.xml } read_xml
對下面xml執行此腳本
<?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2010 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.test"> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" /> <application android:name=".TestApplication" android:icon="@drawable/icon" android:label="@string/app_name"> <meta-data android:name="com.google.android.backup.api_key" android:value="AEdPqrEAAAAIbiKKs0wlimxeJ9y8iRIaBOH6aeb2IurmZyBHvg" /> <test>Only For Test</test> <test></test> <test>abc</test> <test/> <activity android:name=".cardemulation.AppChooserActivity" android:finishOnCloseSystemDialogs="true" android:excludeFromRecents="true"/> <service android:name=".handover.HandoverService" android:process=":handover" /> </application> </manifest>
輸出結果為
感謝各位的閱讀!關于使用bash解析xml的案例分析就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。