2015-08-15 70 views
0

我想用awk查找包含在xml標籤中的名稱值,如果它是1個字符串但是當有空格awk將每個單詞作爲新記錄拆分時,我可以指定值。 另外我的xml沒有換行符。在shell腳本中搜索xml標籤值和空格

我的輸入文件是:

<?xml version="1.0"?><?xml-stylesheet href="catalog.xsl" type="text/xsl"?><type><retail><custdata><Nm>John Smith</Nm><pstaddr1>10 north block</pstaddr1><pstaddr2>boeard st.</pstaddr2><ctry>HR</ctry></custdata><custdata><Nm>Jeff Blanks</Nm><pstaddr1>263 Jef. St.</pstaddr1><pstaddr2>3rd Avenue</pstaddr2><ctry>HR</ctry></custdata></retail><corporate></corporate></type> 

我試圖使用方法:

#!/bin/bash 
    for a in $(ls /usr/cycle1/sample/*.dat) 
     do 
     for c in $(awk 'BEGIN{ FS="[<>]";RS="</"; }; /Nm/{ print $2; }' "$a") 
     do 
     echo $c 
     done 
    done 

輸出我得到的是:

John 
Smith 
Jeff 
Blanks 

而不是

John Smith 
Jeff Blanks 

我可能會做記錄分隔符錯誤,但因爲我是新來awk不知道什麼是確切的問題。你可以幫忙嗎?

回答

0

感謝埃德和查爾斯我使用了組合方法和簡單的AWK,這對我很有用。它也是非常有效的工作

awk 'BEGIN { RS="<pstaddr1>"; FS="[<>]";}; /Nm/{ print $2 }' /usr/cycle1/sample/*.dat 
1

不要使用諸如awk之類的工具來解析XML - 使用真正的XML解析器。在這種情況下,XMLStarlet:

for f in "/usr/cycle1/sample/"*.dat; do 
    xmlstarlet sel -t -m '//Nm' -v . -n <"$f" 
done 

如果你有安裝了--xpath一個版本,你也可以考慮使用xmllint

for f in "/usr/cycle1/sample/"*.dat; do 
    xmllint --xpath '//Nm' <"$f" 
done 

簡易方法無法正確解析意見;不能正確解析帶有用實體轉義表示的特殊字符的字符串;無法正確解析CDATA部分......或者更直白地說,「無法正確解析XML」。


這就是說,只是修復現有的代碼bash的錯誤,並獨自離開了XML的解析錯誤:

for a in "/usr/cycle1/sample/"*.dat; do 
    while IFS= read -r name; do 
    echo "$name" 
    done < <(awk 'BEGIN{ FS="[<>]";RS="</"; }; /Nm/{ print $2; }' "$a") 
done 

爲了解釋:

  • echo $name是越野車:它將$name的每個組件分成一個單獨的單詞,glob-擴展每個單詞,並將每個全局擴展結果作爲單獨的參數傳遞給echo。相反,請使用echo "$name"將單詞保持在一起。
  • for file in $(ls /path/to/*.dat)是越野車。除了打破可以解釋爲包含IFS中字符的glob表達式和文件名的文件名之外,它還依賴於圍繞ls處理包含不可打印字符的文件名的定義不明確的行爲。有關更多詳細信息,請參閱ParsingLs
  • for c in $(awk ...)是越野車,當你想要做的是讀個人awk。首先,它在IFS中將awk內容按字符分開,默認情況下包括其他類型的空白。但是,即使您通過重新分配IFS來解決此問題,它也會在glob擴展時出現錯誤,並且會不必要地造成內存不足。請參閱BashFAQ #001以瞭解從流中讀取的最佳做法,另請參閱DontReadLinesWithFor

最後:這裏有一個方法,在本土打擊 「作品」(與所有常見的不可阻擋的解析的XML-用正則表達式警告):

re='[<]Nm[>]([^<>]+)[<][/]Nm[>](.*)' 
for f in "/usr/cycle1/sample/"*.dat; do 
    content=$(<"$f") 
    while [[ $content =~ $re ]]; do 
    printf '%q\n' "${BASH_REMATCH[1]}" 
    content=${BASH_REMATCH[2]} 
    done 
done 

或者,一個內存高效的變體 - 同樣,不是一個真正的XML解析器,並且由於相同的處理而受到嚴重的輸入限制。

for f in "/usr/cycle1/sample/"*.dat; do 
    next=0 
    while IFS= read -r content; do 
    if ((next)); then 
     printf '%s\n' "$content" 
     next=0 
     continue 
    fi 
    case $content in 
     Nm) next=1; continue ;; 
     *) continue ;; 
    esac 
    done < <(tr '[<>]' '\n' < "$f") 
done 
+0

我同意你和xmlstarlet是我的偏好,但它是生產服務器,我有有限的選擇。不幸的是,由於所有的繁文I節,我無法安裝它。 – aj1981

+0

這就是爲什麼我也給出了一個解決圍繞awk的bash的答案。 –

+0

...但是,如果(看起來)awk也是越野車,那就要求有些不同。 –

0

假設你的輸入文件總是那麼有規律,這可能是你所需要的:

$ awk -v RS='<\\/?Nm>' '!(FNR%2)' file 
John Smith 
Jeff Blanks 

以上使用GNU AWK多焦RS我以爲是罰款,因爲你已經在您發佈的示例中使用該功能。

決不做for a in $(ls /usr/cycle1/sample/*.dat) - 你會使用for a in /usr/cycle1/sample/*.dat代替,但你並不需要這個循環,因爲在所有的awk可以打開多個文件:

awk -v RS='<\\/?Nm>' '!(FNR%2)' /usr/cycle1/sample/*.dat 

如果由於某種原因,你需要一個shell變量設置爲AWK輸出(!這幾乎肯定是一個標誌你會做一些非常糟糕的腳本的其餘部分),你可以這樣做:

$ IFS=$'\n' array=($(awk -v RS='<\\/?Nm>' '!(FNR%2)' file)) 
$ echo "${array[0]}" 
John Smith 
$ echo "${array[1]}" 
Jeff Blanks 

或各種其他的東西......

如果你沒有GNU工具,你不能由於內存限制一次讀取整個單行文件到awk中,你可以模仿以上:

$ sed -e 's/<Nm>/\ 
/g' -e 's/<\/Nm>/\ 
/g' file | 
awk '!(NR%2)' 
John Smith 
Jeff Blanks