next up previous contents
Next: MySQL 匯入判斷 Up: 匯入優化 Previous: 使用 grep 讀取 XML   Contents

XML 資料讀取優化

  1. 取消標籤資料抽取函式的使用,資料總筆數計算也不需要記錄行號,故不使用陣列,直接查詢總筆數,存放在變數 num 做為迴圈上限。
    num=$(echo "$4" | grep '^[1-9][0-9]*$') || num=$(egrep '<(choose|noregister|stop)>' $1 | wc -l)
    echo "num=$num"
    
    if ! num=$(echo "$4" | grep '^[1-9][0-9]*$') ; then
        num=$(egrep '<(choose|noregister|stop)>' $1 | wc -l)
    fi
    echo "num=$num"
    
  2. 先截取一筆選課資料放到變數 xml。直接以 grep -m$i crs_key $1 -A6 找到包含其後面 6 行的 i 筆選課資料,再以 tail -n7 取得最後一筆選課資料,存放於變數 xml 中。 特別注意﹗資料來源為 DOS 格式,必須使用 dos2unix 指令將其轉成 UNIX 格式,否則無法放在單一變數中
        xml=$(grep -m$i crs_key $1 -A6 | tail -n7 | dos2unix)
    
  3. 從 xml 變數中找到標籤 crs_no 的內容。
        crs_no=$(echo $xml | sed 's?.*crs_no>\(.*\)</crs_no.*?\1?')
    
  4. 以 for 迴圈逐一讀取每筆選課資料,再分別判斷每一標籤內容,可以不受 XML 來源資料標籤順序變動影響。
    for ((i=1;i<=$num;i++)); do
        xml=$(grep -m$i crs_key $1 -A6 | tail -n7 | dos2unix)
    
        contextid=$(echo $xml | sed 's?.*crs_key>\(.*\)</crs_key.*?\1?') ;id=$contextid
        acy=$(echo $xml | sed 's?.*acy>\(.*\)</acy.*?\1?')  
        sem=$(echo $xml | sed 's?.*sem>\(.*\)</sem.*?\1?')
        acysem=$acy$sem
        crs_no=$(echo $xml | sed 's?.*crs_no>\(.*\)</crs_no.*?\1?')   
        sbj_name=$(echo $xml | sed 's?.*sbj_name>\(.*\)</sbj_name.*?\1?')  
        lastname=$(echo $xml | sed 's?.*stuid>\(.*\)</stuid.*?\1?') ; username="s$lastname"
        scr_kind=$(echo $xml | sed 's?.*scr_kind>\(.*\)</scr_kind.*?\1?')  
    
        if grep -q -m1 -A7 "<crs_key>$id" $2; then
            echo -e "crs_key=$id\tcrs_no=$crs_no\tstuid=$username\tscr_kind=$scr_kind\tsbj_name=$sbj_name"
        fi
    done
    
  5. 經測試,使用這樣的方式,腳本以背景執行已正常運作,速度也更快。因每次標籤內容的抽取,都需要使 sed 指令過濾字串。雖然一次指令執行時間非常短,但選課資料有幾十萬筆,累計時間還是相常可觀。為加快速度,再省去這個動作,以 grep -o '>.*<' | sed 's/[><]//g'sed 's?.*>\(.*\)<.*?\1?g',一次濾除所有標籤,並將資料以陣列方式存放在變數 arra 中。
        arra=($(grep -m$i crs_key $1 -A6 | tail -n7 | grep -o '>.*<' | sed 's/[><]//g'))
        arra=($(grep -m$i crs_key $1 -A6 | tail -n7 | sed 's?.*>\(.*\)<.*?\1?g'))
    
  6. 以 for 迴圈逐一讀取每一選課資料,雖可直接使用陣列名稱,繼續程式,但節省的時間相當少。在以下的測試中,僅從 1m 20s 降到 1m 19s,差距非常小,且為維持程式可讀性,所以還是將陣列中的每個標籤內容,分別存入各變數中。
    for ((i=1;i<=$num;i++)); do
        arra=($(grep -m$i crs_key $1 -A6 | tail -n7 |  sed 's?.*>\(.*\)<.*?\1?g'))
        contextid=${arra[0]}; id=${arra[0]}
        acy=${arra[1]}
        sem=${arra[2]}
        acysem=$acy$sem
        crs_no=${arra[3]}
        sbj_name=${arra[4]}
        username="s${arra[5]}";lastname=${arra[5]}
        scr_kind=${arra[6]}
    
        if grep -q -m1 -A7 "<crs_key>$id" $2; then
            echo -e "crs_key=$id\tcrs_no=$crs_no\tstuid=$username\tscr_kind=$scr_kind\tsbj_name=$sbj_name"
        fi
    done
    
  7. 不過這個方法存在一項缺點,就是 XML 檔中的標籤不可更動,不管是標籤順序更動或增減都必須修改程式。
  8. 以資工系的選課資料測試,不包含寫入 MySQL 資料庫時間。第一種採用函式方式讀取(簡稱函式),速度慢且無法背景執行。 第二種先將一筆選課放在變數中(簡稱變數),再分別讀取標籤內容資料,速度已從 11m 55s 降到 4m 9s。 第三種以陣列方式一次去除標籤(簡稱陣列),速度最快,但也因此受限標籤順序,當 XML 來源資料標籤變動時,必須調整程式。在 XML 來源格式不會變動的情況下,以速度考慮,故採用此方法。
      執行速度 優點 缺點
    函式 11m 55s 函式可攜性,程式較具彈性。 速度很慢,且背景執行時函式讀取錯誤。
    變數 4m 9s 不受 XML 標籤順序變動影響。 必須多花費一些時間。
    陣列 1m 20s 一次處理,速度最快。 XML 標籤順序變動,程式必須做調整。



2017-08-04